[ISITDTU CTF Final 2019] Web Write-ups

Lâu rồi cũng không viết write-ups hay blog, dạo gần đây mình cũng làm về web nhiều hơn crypto như trước, nên lần này mình sẽ write-ups về web. Cá nhân mình cảm thấy 3 bài web đợt ISITDTU Final này idea khá là hay và mình quyết định sẽ write-ups để chia sẻ solution cho mọi người cùng tham khảo. Cả 3 bài mình đều xin tác giả và làm lại sau giải, sau khoảng 1 tháng đợi các team đi thi write-ups nhưng mãi chẳng thấy nên thôi mình xin phép trình bày solution của mình luôn.

1. Multiweb

  1. Tóm tắt

Challenge cũng đã down rồi, nên thôi mình up source code tại đây phân tích vậy.

Tóm tắt sơ chức năng của web:

  • Trang web sẽ được config nginx để chạy 2 web cùng nhau: web 1 (port 80) chỉ có chức năng show trang index với vài tấm hình, và cấu hình deny khi access /flag, web 2 (port 8000) là trang web với source mình đã up như trên
  • Ở web 2 sẽ load ra những ảnh từ static và chuyển thành tag <img> qua 1 class như sau
Class Map
  • Chức năng thứ 2 của web 2 là save 1 message của user vào file được bắt đầu bằng 1 string cố định
save message
Class UserControl
  • Bên cạnh đó tác giả cũng define 1 stream mới là 1 hàm xor với key mình có thể control
define xor stream
  • Sau khi save message từ client thì server sẽ trả về cho ta đường dẫn của file đó, đồng thời ta cũng có thể xem 1 message bất kì nếu ta có đường dẫn của file đó (chỉ file có extension .txt)

2. Idea

  • Chúng ta có thể ghi file tùy ý nhưng path cố định theo 1 token mà server sẽ gen cho từng client
  • Có thể đọc tùy ý 1 file .txt nào đó trên server
  • Class Map lại có 1 method __destruct cho phép đọc content 1 file tùy ý (có check header xem phải là 1 image qua hàm exif_imagetype )

=> nếu chúng ta có thể bằng cách nào đó tìm được chỗ nào đó unserialize thì dễ dàng đọc được file tùy ý

Nhưng không may là chẳng có hàm unserialize được sử dụng trong bài này. Năm 2017 có 1 attack vector mới được Orange Tsai ra trong HITCON CTF 2017. Đó là 1 file dạng phar ( https://secure.php.net/phar ) sẽ deserialize data nằm trong phần Phar meta-data khi được trigger bởi các hàm thao tác lên file. Các bạn có thể tham khảo blog dưới đây về các hàm có thể trigger được unserialize của Phar:

  • Và 1 điều lưu ý nữa là chúng ta có thể ghi data bất kì phía trước data của phar file (điều này có nghĩa chúng ta có thể tạo 1 image với header hợp lệ nhưng vẫn đảm bảo nó là 1 file phar), các bạn có thể tham khảo thêm tại blog sau: https://www.nc-lp.com/blog/disguise-phar-packages-as-images

=> Như vậy chúng ta đã có đủ dữ kiện để giải bài này theo từng step như sau:

  • tạo 1 file phar với serialized data là Object Map để đọc file tùy ý
  • chỉ cần send message với content phía sau file phar bỏ đi “ — — — — — — — BEGIN — — — — — — — \n” là ta đã ghi được 1 file phar hợp lệ
  • trigger phar unserialize qua bằng cách đọc file .txt (chức năng thứ 3 của web 2)
  • 1 vấn đề nhỏ cần giải quyết là trước khi convert content image thành <img> thì sẽ check xem file đó có phải là ảnh hay không qua hàm exif_imagetype
  • Lợi dụng xor stream mà tác giả đã define sẵn cho chúng ta, và hàm exif_imagetype chỉ check header (ví dụ như header “GIF” => ảnh gif), do 1 số file config sẽ có phần đầu sẽ không đổi nên ta có thể tùy chỉnh xor key để khi qua stream xor sẽ ra header “GIF”. Và chúng ta sẽ đọc được file tùy ý

script đọc content file /etc/nginx/sites-available/default:

https://gist.github.com/ducnhse130201/f6a633cbd04dc8f36162d655ae982a9a

Do server cũng down nên mình miêu tả sơ qua cách config nginx của tác giả như sau:

  • tác giả config nginx bị lỗi off-by-slash
https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf
  • bằng cách lợi dụng off-by-slash ta có thể đọc được file flag ở web 1 (ban đầu khi access /flag sẽ forbidden)

2. rg2

  1. Tóm tắt

Source code: https://drive.google.com/file/d/1CBMJKrlWECXSxcsQBFXv1xGd4csIERAb/view?usp=sharing

Lại 1 bài config bằng nginx và bị lỗi off-by-slash

  • lỗi này cho phép ta đọc được 1 số file cần thiết, trước tiên hãy đọc qua source mà tác giả cho chúng ta

Web có 3 route như sau:

  • /graphql : endpoint graphql
  • /initialize: cấp cho chúng ta 1 JWT với identity là “guest”
  • /flag: để có thể POST được vào route này thì cần JWT với identity “admin”, đồng thời cùng cần có các claims là username và password của admin

Sau khi có thể POST vào /flag thì có 1 challenge khác, đại loại là cần chúng ta SSRF để đọc content 1 service ở port 3333

2. Idea

Bài này ban đầu là 1 bài black-box nhưng theo mình biết thì vào giữa giờ cuộc thi thì tác giả cung cấp 1 phần source để bài này thành 1 bài whitebox. Cá nhân mình cũng cảm thấy lỗi off-by-slash khá là nhạy cảm để có thể nhận biết, ban đầu khi mình tiếp cận bài này mình cũng không biết lỗi đó nữa 😐.

Cách mình làm sẽ đọc app.py trước, xong xem app.py import các moudle nào thì mình sẽ đọc dần cái file đó, và dần ta sẽ có hết source.

Tóm lại ta cần có 3 vấn đề giải quyết như sau:

  • token admin
  • password admin
  • bypass filter SSRF
config.py

Dễ dàng lợi dụng lỗi off-by-slash đọc privatekey từ đó để sign 1 token khác với identity là admin. Nhưng ta vẫn cần thêm password admin để có thể POST vào /flag

Để ý thì server sử dụng graphql để query lấy data vào load lên, chú ý đoạn code sau

Dễ thấy thì chỉ cần token có identity là admin thì chúng ta có thể list ra thêm nhiều item khác (theo design của tác giả thì password admin sẽ là Item có id = 10)

Sau khi có password admin rồi thì ta chỉ còn 1 step là bypass SSRF để đọc flag

Tóm tắt filter:

  • input.split(“.”) == 4
  • input không chứ “127.”, “.0”, “.1”
  • nếu input chỉ chứa [a-zA-Z] và gethostbyname(input) == “127.0.0.1” thì fail
  • tác giả cũng code async để phòng trường hợp Race Condition

Ý tưởng ban đầu của mình là DNS Rebinding, như các bạn cũng thấy lần đầu sẽ gethostbyname() domain mình nhập vào, và lúc sau sẽ request 1 lần nữa với domain đó, nên DNS Rebinding khá là hợp lý. Nhưng mình bị fail do cần input thêm vào port 3333, lúc này khi qua gethostbyname() sẽ ném Exception và chúng ta không thể bypass được filter

Mãi về sau mình mới nhận ra trong 1 câu if:

  • if a and b:
  • nếu mà a fail thì sẽ không thực hiện b 😐

Vậy thì bây giờ sẽ chuyển hướng sang làm cho mệnh đề a (check [a-zA-z])fail thì sẽ không check mệnh đề b ( gethostbyname() )

Tạo 1 subdomain như sau (a.b.peterjson.xyz) với dạng enclosed alphanumerics ( https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Request%20Forgery#bypass-using-enclosed-alphanumerics) và trỏ subdomain này về 127.0.0.1 chúng ta sẽ làm cho mệnh đề a fail và sẽ không check mệnh đề b.

Cuối cùng ta có thể bypass SSRF filter và có flag

3. Laravel (Mình cũng không nhớ bài này tên gì 😐)

Source code: https://drive.google.com/file/d/10KfVraZtTQw0BDboWN_-_B5T13Pu_VEN/view?usp=sharing

  1. Từ 1 bài CTF khác

Đây là 1 bài unserialize của laravel khá hay version mới nhất 5.8, mặc dù gadget này cũng được update trên php ggc nhưng tác giả cũng filter 1 số keyword để làm cho bài này không quá dễ và mình nghĩ ý tưởng của tác giả sẽ là muốn mình tìm 1 gadget khác

Để khởi động và làm quen cho dạng bài này (tự viết gadget) mình có gặp thấy 1 bài CTF: https://github.com/phith0n/code-breaking/tree/master/2018/lumenserial

Sau vài ngày mính tìm hiểu thì khi làm các dạng bài như thế thì mình có rút ra vài kinh nghiệm như sau:

  • cố gắng tìm các magic method như __destruct, __wakeup, __toString của các class mà có nhiều atributes có thể control
  • Từ đó tìm tiếp các method liên quan để cuối cùng nhảy đến các hàm như call_user_func, call_user_func_array hoặc dẫn thẳng tới các hàm có thể ghi file hoặc gọi được command

Trước tiên mình sẽ lấy bài lumenserial làm ví dụ:

  • Idea để bắt đầu thứ nhất là tham khảo gadget có sẵn trong php ggc nếu không được thì sẽ phải hardcore hơn đó là tự tìm gadget khác
  • mình sẽ tham khảo php ggc thì thấy đa số các gadget đều đi từ magic method __destruct của class Illuminate\Broadcasting\PendingBroadcast
  • Dễ thấy thì ta có thể tùy ý controll $event và $events, điều này có nghĩa là ta có thể gọi method dispatch của bất kì class nào với argument ta đã controll
  • Ở đây sẽ có 2 hướng đi, thứ nhất là tìm trong source có class nào có method dispatch có thể lợi dụng được hay không (write-ups cho hướng thứ 1: http://m4p1e.com/web/20181224.html) và hướng thứ 2 sẽ trigger magic method __call ( https://www.php.net/manual/en/language.oop5.overloading.php#object.call)

Sau khi có cái nhìn sơ bộ về việc cần làm cho task này thì mình quay lại bài laravel trên

2. Tóm tắt

Chúng ta có 2 Controller chính:

HomeController
GenPayloadController

Nhìn sơ qua 2 Controller này thì cũng hiểu flow của bài này, chúng ta không thể gen ra 1 số random sao cho thỏa và lấy flag, và hướng cuối cùng chỉ có tìm gadget và lợi dụng unserialize

Tác giả cũng đã 1 số keyword như “Broadcast” , “Command”, “<?” , “?>” để bài này không trở nên quá dễ

3. Idea

Nhìn vào composer.json

  • laravel version mới nhất
  • và có thêm symfony

Mình cũng có thử qua gadget ghi file symfony 2 trong php ggc nhưng đã fail vì class này chưa được autoload (mình cũng không hiểu tại sao class đó lại không được load lên :( )

Giải thích sơ qua chỗ này, mình chọn stream base64 để pass chỗ check “<?” và “?>”. Trong base64 thì cứ 3 char sẽ encode thành 4 char, đó là lý do mình pad thêm ‘aaa’ phía trước và ‘aaa’ phía sau để khi decode sẽ vẫn giữ được payload của chúng ta

'<?php return ' + 'aaa' (len % 4 = 0) + base_encode(payload) + 'aaa;' (len % 4 = 0)

Lúc này mình thực sự betak và không biết tìm hướng đi tiếp như thế nào, thì may mắn thay tìm được 1 bài blog nói về gadget mới nhất trên laravel 5.8, https://mochazz.github.io/2019/08/05/Laravel5.8.x%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE/

Theo bài blog thì gadget thứ nhất cũng đi từ class Illuminate\Broadcasting\PendingBroadcast và tất nhiên đã bị filter, gadget thứ 2 thì đi từ Symfony (có vẻ là giống với bài ctf này rồi 😐)

Viết lại cái chain cũng không khó khăn mấy và đã RCE thành công, cạnh đây mình cũng muốn nói 1 gadget khác, do mình có ấn tượng với cái gadget bài lumenserial. Vô tình trong lúc viết lại chain thì thấy khá giống đoạn

$this->events->dispatch($this->event);

Cái form trên khá là giống $this->events->dispatch($this->event);

=> 1 gadget khác giống bài lumenserial nhưng đi từ Symfony

Đây là cả 2 chain để giải quyết bài nay: https://gist.github.com/ducnhse130201/f11cc01a945e6fb40ba5fbb88ed8b459

4. Kết

Cảm ơn BTC và 2 tác giả (Đình BảoSon Tran) đã mang lại 3 bài web hay và nhiều kiến thức cho bản thân mình. Một điều mình cảm nhận gần đây là đa số tài liệu + blog mình tìm hiểu đa số xuất phát từ Trung Quốc hay Đài Loan và đó là một nguồn kiến thức bổ ích.

Cảm ơn mọi người đã dành thời gian đọc write-ups của mình. Chúc mọi người cuối tuần vui vẻ. Peace

Nub-boi

Love podcasts or audiobooks? Learn on the go with our new app.