第五空间awd没web。。。解题模式3道web就做出了一道,一道要wasm逆向做不来,还有一道问了smi1e师傅感觉有点脑洞。

Hard node

有源码,但是主办方近一个小时忘放了。。。

关键代码:

app.js

const express = require('express');
const bodyParser = require('body-parser');
const proc = require('child_process');
const request = require('request');
const ip = require("ip");
const manage = require("./manage.js");
const path = require('path');

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

app.use(express.static(path.join(__dirname, 'public')));

//stop hackers
const disallowedKeys = [
    '__proto__',
    'prototype',
    'constructor',
    'eval','proccess','root','global','exec','!','fs'
];

function isValidPath(segment){
    disallowedKeys.forEach(evilWord => {
        if(segment.toString().indexOf(evilWord)!==-1){
            return false
        }
    });

    return true;
}

app.post('/add', (req, res) => {
    let ip = req.ip;
    console.log(ip.m);
    if (ip.substr(0, 7) == "::ffff:") {
        ip = ip.substr(7)
    }
    console.log(`method:${req.method},serverip:${server_ip},ip:${ip}`);

    if (ip != '127.0.0.1' && ip != server_ip) {
        res.status(403).send('Not Edit from Local!');
    }else{
        if(req.body.userName && req.body.nameVal){
            let username = req.body.userName;
            let nameVal = req.body.nameVal;

            if (!isValidPath(username) || !isValidPath(nameVal)) {
                username = 'username';
                nameVal = 'guest';
            }

            manage.set(object, username, nameVal);
            console.log(ip.k);
            console.log(object);

            res.send(`
            <h1>Edit Success</h1>
            <a href="/admin">View Admin Page</a>`)
        }else{
            res.send('param error');
        }
    }
});

app.get('/admin',(req,res)=>{
    if(manage.get(object,'username','guest') === 'admin'){
        console.log('Current User:'+object.username)

        const child = proc.fork(`${__dirname}/public/user.js`,['admin']);
        child.on('message', (body) => {
            res.status(200).send(body);
        });
        child.on('close', (code, signal) => {
            console.log(`subproccess ended with ${signal}`);
        });

    }else{
        res.status(403).send('Only Admin Can View this');
    }
})

app.get('/getContent',(req,res)=>{
    res.sendfile(`${__dirname}/public/guest.html`);
})

app.get('/', (req,res) => {
    // console.log(req.body)
    let uri = req.query.url? req.query.url: 'http://127.0.0.1:3000/getContent';
    console.log(uri)

    try{
        request.get(uri,(err,response,data)=>{
            if (!err && response.statusCode == 200) {
                res.send(data);
            }else{
                console.log(err);
            }
        })
    }catch(e){
        console.log(e);
    }finally{
        console.log('Make Server Continue Running');
    }

});

var object = {username:'guest'};
var server_ip = ip.address();


app.listen(3002);
console.log(`${server_ip} is starting at port 3000`)

manage.js

const isObj = require('is-obj');

var manage = {
    getPathSegments: function(path) {
        const pathArray = path.split('.');
        const parts = [];

        for (let i = 0; i < pathArray.length; i++) {
            let p = pathArray[i];

            while (p[p.length - 1] === '\\' && pathArray[i + 1] !== undefined) {
                p = p.slice(0, -1);
                p += pathArray[++i];
            }

            parts.push(p);
        }

        return parts;
    },

    get: function(object, path, value) {
        if (!isObj(object) || typeof path !== 'string') {
            return value === undefined ? object : value;
        }

        const pathArray = this.getPathSegments(path);

        for (let i = 0; i < pathArray.length; i++) {
            if (!Object.prototype.propertyIsEnumerable.call(object, pathArray[i])) {
                return value;
            }

            object = object[pathArray[i]];

            if (object === undefined || object === null) {
                if (i !== pathArray.length - 1) {
                    return value;
                }
                break;
            }
        }

        return object;
    },

    set: function(object, path, value) {
        Object.keys(Object.prototype).forEach(function(Val){
            if(!Object.hasOwnProperty(Val)){
                delete Object.prototype[Val];
                console.log(`${Val} is delete`);
            }
        })

        if (!isObj(object) || typeof path !== 'string') {
            return object;
        }

        const root = object;
        const pathArray = this.getPathSegments(path);

        for (let i = 0; i < pathArray.length; i++) {
            const p = pathArray[i];

            if (!isObj(object[p])) {
                object[p] = {};
            }

            if (i === pathArray.length - 1) {
                object[p] = value;
            }

            object = object[p];
        }

        return root;
    }    

}

module.exports = manage 

有4个路由

/add
/admin
/getContent
/

/有SSRF、/add需要内网才能访问、/admin需要将全局变量object的username熟悉覆盖值变为admin才能进入。

/的SSRF使用的是request.get,而/add需要发送POST包,查看request的文档https://github.com/request/request

可以利用har覆盖请求的方法

image-20200913235609831

简单分析manage.js, 在set方法中存在原型链污染漏洞

image-20200914000043917

那么我们就可以通过SSRF向/add发送POST包,进行原型链污染。在结合/admin中的child_process.fork,我们可以污染env熟悉实现RCE,具体可以看这里https://xz.aliyun.com/t/6755。

最后要进入/admin我们需要覆盖全局变量object的username属性,可以直接使用manage中的set覆盖username

首先,覆盖admin

image-20200914001104979

然后污染env变量

image-20200914001129821

最后访问/admin执行命令

image-20200914001210342