Bỏ qua

SSTI1

Introduction

PicoCTF 2025

Category: Web Exploitation

Write-up date: 18/03/2025

Question: I made a cool website where you can announce whatever you want! Try it out!

Additional details will be available after launching your challenge instance.

Point: Easy

Vulnerability

Recon

Theo đầu bài, trang web này có thể tấn công dựa vào lỗi SSTI. Khi ta truy cập vào đề bài, author cho chúng ta một input để nhập announce mình mong muốn. Sau khi nhập bùa thử chữ cái nào đó thì đề bài trả ra đúng chữ cái đó.

Chức năng nhập input của web

assets/images/ssti1.png

Thông báo sau khi nhập input

announce.png

Khi chúng ta mở devtools lên thì thấy server trả về response với header Server: Werkzeug/3.0.3 Python/3.8.10 vậy nên chúng ta có thể dễ dàng loại bỏ được tất cả các template của ngôn nghĩ khác ngoại trừ python. Và bởi vì Werkzeug lại là một engine của Flask phần trăm khá lớn là Template này được viết bằng Jinja2

Ngoài ra, chúng ta còn có thể xác định bằng cách:

template.png

Hoặc sử dụng TInjA

Giải thích sơ qua về lỗi

Server-side template injection (SSTI) là dạng lỗ hổng cho phép kẻ tấn công inject các payload (tạo bởi chính ngôn ngữ template đó) vào các template, và chúng được thực thi tại phía server. Trong đa số trường hợp xảy ra lỗ hổng SSTI đều mang lại các hậu quả to lớn cho server, bởi các payload SSTI được thực thi trực tiếp tại server và thường dẫn tới tấn công thực thi mã nguồn tùy ý từ xa (RCE - Remote Code Execution).

SSTI - Viblo

Exploit

Mục tiêu chính của SSTI là chúng ta có thể thoát ra ngoài sandbox template của hệ thống, từ đó truy cập được vào các hàm bên ngoài sandbox và từ đó có thể láy được toàn bộ thông tin hệ thống, database, vân vân. Để làm được điều đó thì trước tiên chúngt a cần truy cập được một trong hai method là __subclasses__ (toàn bộ template của python) hoặc là __global__.__builtins__ (chỉ riêng template của jinja)

Mình quyết định lựa chọn __subclasses__ bởi nó có thể áp dụng với nhiều template hơn.

Trước tiên, để có thể truy cập được method, __subclasses__, ta cần phải truy cập được vào class <class 'object'>, mà để truy cập vào được class object, trước hết ta cần truy cập vào được những global object, có thể kể đến như

''
[]
{}
request
dict
config

Sau khi đã truy cập được vào những global object, chúng ta đến bước tiếp theo là truy cập được class <class 'class'> bằng cách sử dụng __class__ để từ đó sử dụng__base__, hoặc là mro()[-1] __base__ là gì? __mro__ là gì? để truy cập class <class 'object'>.

Mình sẽ sử dụng payload {{''.__class__.mro()[-1]}} để truy cập class <class 'object'>

Sau khi truy cập được class <class 'object'> việc cuối cùng của chúng ta là vượt sandbox bằng cách sử dụng method __subclasses__ để tìm được tất cả các hàm con đã được định nghĩa trong python, từ đó có thể đọc, viết file hoặc thậm chí là chạy lệnh.

Mình sẽ list tất cả các hàm bằng được định nghĩa trong trương trình bằng cách sử dụng {{''.__class__.mro()[-1].__subclasses__()}}

classes.png

Để thuận tiện nhất thì mình sẽ sử dụng hàm <class 'subprocess.Popen'> để chạy các câu lệnh shell và in được ra flag. Vì __subclasses__ trả cho chúng ta một list các hàm nên giờ chúng ta cần tìm index của <class 'subprocess.Popen'>. Dựa vào code python ta hoàn toàn có thể tìm thấy được index của thằng popen là 356.

def ssti_caculate_function():
    list_class = '[Thay list các class vào đây]'[1:-1].split(", ")
    return list_class.index("<class 'subprocess.Popen'>")

Dựa vào thông tin trên, kết hợp với docs của subprocess, ta có payload cuối cùng {{ ''.__class__.mro()[1].__subclasses__()[356]('cat flag',shell=True,stdout=-1).communicate()[0].strip() }}

flag.png