3

2020第五空间Final

 2 years ago
source link: https://blog.szfszf.top/article/47/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2020第五空间Final

2020年09月, 3437 views, #CTF #writeups

第五空间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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK