Post

Server-Side Template Injection (SSTI)

Server-Side Template Injection (SSTI)

Server-Side Template Injection (SSTI) — Practical Attacks and Defenses

Overview

In recent web application security assessments, while classic vulnerabilities like XSS and SQLi are becoming somewhat less dominant, injection vulnerabilities arising from server rendering logic, specifically Server-Side Template Injection (SSTI), are appearing frequently. This vulnerability occurs when a template engine renders user input without proper validation, potentially leading to consequences ranging from simple information disclosure to Remote Code Execution (RCE).

Core Mechanism of SSTI

SSTI occurs when template engines (Jinja2, Twig, Pug, EJS, etc.) directly evaluate user input while dynamically generating HTML on the server.

For example, consider the following code in a Flask application:

1
return render_template_string("Hello {{ name }}", name=request.args.get("name"))

If an attacker provides the input ?name={{7*7}} and confirms that the server renders the result as 49, the injection is active. At this moment, the template engine is executing the logic, and further exploitation via server code or object access becomes possible.

Detection and Exploitation Process

  1. Input Point Discovery: Inspect parameters, headers, and cookies to identify where user-controlled input is included in server-side rendering.

  2. Magic Payload Testing: Inject payloads specific to various template engines (e.g., {{7*7}}, ${7*7}, <%= 7*7%>) to check if mathematical operations are evaluated and reflected in the response.

  3. Escalation to RCE: If the template engine does not restrict access to internal objects or functions, attackers can escalate to OS command execution. For instance, in Python’s Jinja2, a payload like {{ self.__init__.__globals__.os.popen('id').read() }} can execute system commands.

  4. Bypass Techniques: If filtering is applied, attackers may use escape sequences (e.g., {{ config.update('os') }}) or Unicode encoding to bypass validation logic.

Defense Strategies

To fundamentally prevent SSTI, reliance on simple filtering is insufficient; a reevaluation of how template engines are utilized is required.

  • Separation of User Input and Templates User input should never be directly concatenated into a template string or generated dynamically. Instead, utilize the data binding features provided by the template engine to pass input as variables.

    Vulnerable Code (Python):

    1
    2
    
    template = "Hello " + user_input
    return render_template_string(template)
    

    Secure Code (Python):

    1
    
    return render_template("hello.html", name=user_input)
    
  • Use of Logic-less Templates Adopting logic-less template engines such as Mustache or Handlebars prevents code execution within the template, structurally blocking the risk of RCE via SSTI.

  • Sandboxing If complete code isolation is not feasible, enable sandbox mode at the template engine level. Additionally, leverage OS-level isolation such as Docker containers or chroot to minimize the blast radius should an injection occur.

  • Additional Security Controls During code reviews, strictly prohibit the use of dangerous functions like render_template_string, eval, and exec. Ensure that automatic escaping options (autoescape=True) are enabled by default. Furthermore, it is recommended to include normalized fuzzing for SSTI patterns during security testing.

Real-world SSTI Detection Points

Empirically, SSTI is frequently found in the following scenarios:

  • Customization features in CMS or SaaS backends (e.g., email templates, dashboard configurations).
  • Error page rendering where user input is reflected without sanitization.
  • Server-Side Rendering (SSR) structures using frameworks like React, Vue, or Nunjucks.

Most developers assume that “user input will not enter the server template directly,” yet these vectors are often exposed in logs, error handlers, and administrative panel configurations.


서버 사이드 템플릿 인젝션(SSTI) — 공격과 방어의 실제

개요

최근 웹 애플리케이션 보안 테스트에서 XSS, SQLi 같은 고전적인 취약점은 점점 줄어드는 반면, 서버 렌더링 로직에서 발생하는 인젝션 취약점, 특히 SSTI(Server-Side Template Injection)는 여전히 빈번히 등장하고 있습니다. 이 취약점은 템플릿 엔진이 사용자 입력을 제대로 검증하지 않고 렌더링할 때 발생하며, 단순 정보 노출에서부터 RCE까지 이어질 수 있습니다.

SSTI의 핵심 메커니즘

SSTI는 서버에서 동적으로 HTML을 생성할 때 템플릿 엔진(Jinja2, Twig, Pug, EJS 등)이 사용자 입력을 그대로 평가(evaluate)하는 상황에서 발생합니다.
예를 들어 Flask 애플리케이션에서 다음과 같은 코드가 있으면:

1
return render_template_string("Hello {{ name }}", name=request.args.get("name"))

공격자는 ?name={{7*7}}이라는 입력을 통해 서버에서 49가 렌더링되는지를 확인할 수 있습니다. 이 순간 이미 템플릿 인젝션이 동작하고 있으며, 이후 서버 코드나 객체 접근을 통해 더 깊은 단계의 공격이 가능합니다.

탐지 및 악용 과정

  1. 입력 포인트 탐색:
    파라미터, 헤더, 쿠키 등 사용자 제어 입력이 서버 렌더링에 포함되는지 점검합니다.
  2. 매직 페이로드 테스트:
    각 템플릿 엔진별 페이로드({{7*7}}, ${7*7}, <%= 7*7%> 등)를 삽입하여 연산 결과가 반영되는지 확인합니다.
  3. RCE로의 확장:
    템플릿 엔진이 내부 객체 또는 함수 접근을 제한하지 않을 경우, 예를 들어 Python Jinja2의 경우 {{ self.__init__.__globals__.os.popen('id').read() }} 형태로 OS 명령 실행까지 가능합니다.
  4. 우회 기법:
    필터링이 적용된 경우 이스케이프 우회({{ config.update('os') }}), 유니코드 인코딩 등을 이용해 검증 로직을 회피합니다.

방어 전략

SSTI를 근본적으로 방지하기 위해서는 단순한 필터링보다는 템플릿 엔진의 사용 방식 자체를 재검토하는 접근이 필요합니다.

  • 사용자 입력의 템플릿 분리
    사용자 입력값을 템플릿 문자열 안에서 직접 연결하거나 동적으로 생성하지 말아야 합니다. 대신, 템플릿 엔진이 제공하는 데이터 바인딩 기능을 이용해 입력값을 변수로 전달해야 합니다.

    취약한 코드 (Python):

    1
    2
    
    template = "Hello " + user_input
    return render_template_string(template)
    

    안전한 코드 (Python):

    1
    
    return render_template("hello.html", name=user_input)
    
  • Logic-less 템플릿 사용
    Mustache, Handlebars 등과 같은 logic-less 템플릿 엔진을 사용하면 템플릿 내부에서 코드 실행이 불가능해져 SSTI로 인한 RCE 위험을 구조적으로 차단할 수 있습니다.

  • 샌드박스 환경 구성
    완전한 코드 격리가 어렵다면 템플릿 엔진 단에서 샌드박스 모드를 활성화하고, OS 레벨에서는 Docker 컨테이너나 chroot와 같은 격리 환경을 활용해 인젝션이 발생하더라도 피해 범위를 최소화해야 합니다.

  • 추가적인 보안 제어
    코드 리뷰 단계에서 render_template_string, eval, exec 등과 같은 위험 함수 사용을 금지하고, 자동 이스케이프 옵션(autoescape=True)을 기본으로 설정합니다. 또한 보안 테스트에서는 SSTI 패턴을 포함한 정규화된 fuzzing을 수행하는 것이 좋습니다.

실제 운영 환경에서의 SSTI 탐지 포인트

경험적으로 SSTI는 보통 아래 상황에서 많이 나타납니다.

  • CMS 또는 SaaS 백엔드의 커스터마이징 기능 (예: 이메일 템플릿, 대시보드 구성).
  • 에러 페이지 렌더링 시 사용자 입력을 그대로 노출하는 부분.
  • 서버-사이드 렌더링(SSR)을 사용하는 React/Vue/Nunjucks 구조.

대다수 개발자가 “사용자 입력이 서버 템플릿 안에 들어가지 않을 것”이라 가정하지만, 실제로 로그나 에러 핸들러, Admin 패널 구성 등에서 자주 노출됩니다.

This post is licensed under CC BY 4.0 by the author.