2019/04/07

AceBear CTF 2019 - Web Category


Lâu lắm tôi mới động đến CTF, một phần là phải cân đối thời gian, một phần là cảm thấy *noob*, nên là tranh thủ giải này để khởi động lại.
Nhìn chung, đối với mảng Web thì các đề lần này đều hay và chuẩn bị công phu, đặc biệt là có đề mất công chuẩn bị tới 4 tháng trước khi thi, kudos @Jang

Here is the list:
  • Web 💯
  • duudududduduud
  • Store Image Service
  • Store Image Service 2
  • Best band of Asia


Web 💯

Source được cung cấp kèm theo script setup.sh



Do trước đây tôi cũng từng sử dụng 1 script setup này để làm 1 challenge dạng abuse 3rd party pre-include (ví dụ phpmyadmin trong xampp) nên hơi mơ hồ là có thể phải làm gì đó liên quan đến ZendFramework và XAMPP.
Functional chứa trong index.php là chính. Bên cạnh đó trong file.php có định nghĩa của class SignObject.
Về chức năng:

  • Cho người dùng input vào app thông qua việc sign 1 file được upload từ phía người dùng
  • extension của file bắt buộc là 1 trong số các ext được định nghĩa sẵn.
  • các ext execute cho php :  .php .phtml .php3 .php4 .php5 .php7 đều được unregister bằng .htaccess bên trong uploads, có thể thấy vẫn thiếu .pht (php template) trong 1 số trường hợp server support, có thể xem các ext được register trong config apache
  • Folder "sign" để sign như tên gọi
    • sign.php: dùng php function unserialize để tạo SignObject và gọi hàm sign hoặc verify tùy yêu cầu.
  • Các filter chưa rõ mục đích, chắc là chặn mấy hàm insecure (CTF mà)
Dự đoán ban đầu: unserialize sẽ được dùng kèm 1 gadget để tạo 1 file bất kì hoặc RCE. May mắn thay PHPGGC: PHP Generic Gadget Chains (phpgcc) vừa bổ sung thêm gadget RCE cho ZendFramework 2.x.
Thử dùng luôn phpgcc để tạo payload bằng câu lệnh lấy phpinfo với arg = 4 để kiểm tra nhanh disabled_function : phpinfo ([ int $what = INFO_ALL ] ) : bool 
phpgcc ZendFramework/RCE3 phpinfo 4 > payload.txt
Nếu không sử dụng phpinfo mà là system thì cần thay đổi một chút trong payload.txt để bypass filter.
khi deserialize s:1:'A' và S:1:'A' thì đều cho kết quả như nhau, sự khác biệt là S là dạng encoded string, tức là có thể encode hex bên trong nó, tương đương S:1:'\41'. Ví dụ như encode system thành syst\65m
Sau khi test thành công trên localhost thì có một vấn đề là echo $signObj->sign(); yêu cầu $signObj sau khi unserialize cần là một SignObject, do gadget RCE của Zend dựa trên magic function __destruct nên không ảnh hưởng gì. Ta có thể làm như sau :
$signObj = 'O:10:"SignObject":1:{s:1:"c";'.$signObj."}";

Tuy nhiên test remote thì lại không được \m/. Theo chia sẻ của tác giả, do gadget chain được public release sát ngày deploy challenge nên có một sự chỉnh sửa nhỏ để không thể script kiddie mà lấy flag được. Như là đổi ext của vendor/ZendFramework-2.4.13/library/Zend/Json/Expr.php thành vendor/ZendFramework-2.4.13/library/Zend/Json/Expr.txt (do folder vendor có thể access public)
Payload bypass  RCE3 của phpgcc cho challenge (việc nghiên cứu thay đổi link trong chain thế nào không nằm trong phạm vi writeup này)

Payload trên là sau khi tôi đã có được shell và chạy loanh quanh host thì phát hiện flag ở /flag.jpg
Lạc trôi.....


duudududduduud

Bài này sử dụng 1 hướng có vẻ là unintented
Đề bài cũng cung cấp source challenge, nhưng có vẻ 62MB là khá .. nặng. Đầu tiên là kiểm tra .git xem có gì không và đúng là không có thật.
Về chức năng:
  • Upload file nếu session là admin
  • register/login dùng prepared statement
Phát hiện:
  • AES CBC không HMAC --> có thể dùng bit flipping để thay đổi dữ liệu sau decrypt
  • username sau khi decrypt dùng nối chuỗi để query từ db --> có thể bị sql injection
  • Kiểm tra điều kiện dữ liệu không giống nhau khi register: username <= 30 và login: username <= 40
  • Có được output sau khi decrypt.
Tôi sẽ để cái hình cơ bản mỗi khi động đến AES mode CBC ở đây. Đây là khi decrypt.
xét username dạng:
aaaa aaaa aaaa aaaa bbbb bbbb bbbb bb
bỏ đi các khoảng trắng, kết hợp với một giá trị padding hardcoded, khu vực *b* có thể chuyển thành truy vấn sql để thực hiện sql injection
Với giới hạn 14 bytes có thể thay đổi được:
aaaaaaaaaaaaaaaa
'=0 limit 01,1#
Với đoạn limit 01,1 tôi có thể thay đổi để brute giá trị nào có trường admin = true. Tuy nhiên sau khi brute thì khoảng 100 user đầu k có ai là admin cả.
Như vậy còn cách union mà thôi.

Nhìn vào hình ta thấy, quá trình cipher block (cb) 3 decrypt ra plaintext block (pb) 3 có thể bị can thiệp bằng cách flip bit ở cb2, tuy nhiên 1 bit thay đổi trên cb2 sẽ dẫn đến pb2 bị corrupt. Điều này có thể được khắc phục bằng các tiếp tục thực hiện bit flipping trên cp1 đến khi có pb2 tùy ý. Khi này có thể bỏ qua sự corrupt của pb1.
Tóm lại, nhờ feedback của input mà ta có thể control được pb3, pb2.
Lại xét đến giới hạn của username.

User chỉ có thể có tối đa : 30 kí tự khi đăng kí. Nhưng 1 cách magic, khi dùng prepared statement, tôi có thể làm username chứa tới 40 kí tự bằng cách chèn thêm kí tự space (0x20) vào cuối. Và username được set vào session lại là user's input chứ không phải từ database.
aaaaaaaaaaaaaaaa
bbbbbbbbbbbb****
******|thisis...


1 câu sql injection union như sau: 'union select 'a',1# cần đến 20 kí tự.
Khi này, kết hợp với magic của PHP, chia sql query thành 2 phần: phần sau để flip đoạn trước thisis thành a',1#
Gửi lên server để lấy ouput, decode base64 để lấy username hiện tại.
Tiếp tục bit flipping block 2 thành 'union select '
Cần chú ý đển username không chứa ' trong block 1, nếu không query sẽ bị lỗi.

Sau đi đọc thêm 1 số các writeup khác, tôi nhận ra:
  • Do IV trùng với key nên có thể bằng cách nào đó lấy được key (google), khi này encrypt nội dung tùy ý vẫn được
  • Lỗi khi dùng explode : explode luôn return array dù cho có tìm thấy key để split hay không :( dẫn đến flip thoải cmn mái không cần quan tâm đến cái đoạn "|this" làm gì
  • Do input feedback -> thích decrypt ra gì cũng được (vẫn trừ block đầu :P)
Sau khi nối thành chuỗi hoàn chỉnh thì đã là admin và chỉ cần upload file zip chứa php shell và manifest.json sao cho vượt qua điều kiện kiểm tra.
Ending? Không.
Có một điểm thú vị là challenge này sử dụng PCLZIP, có lỗ hổng cho phép kể cả không thỏa mãn phần check json thì vẫn có thể archive RCE :P Bạn sẽ không bị xóa nếu bạn không nằm trong thư mục bị xóa


Store Image Service

URL: http://storeimage.whitehat.vn/s3rv1c3/
Gợi ý là đọc file Fl4g.php
Để đọc file thì có thể là LFI, hoặc SQLi readfile, RCE...
Chức năng liên quan đến LFI có thể là upload file from url do nó hiển thị lại nội dung.
Thử với file:///etc/passwd
Thế này rồi thì đọc flag thôi còn chờ gì nữa :D Đoán là service chạy ở /var/www/html/ luôn


Store Image Service 2

URL: http://13.229.223.185/s3rv1c3-v2/
Đây là phiên bản fix lại từ bài trước, lần này thì không thể dùng file để lấy được source nữa, cách tốt nhất là leo thang từ version 1 để lấy source, phân tích và dự đoán thay đổi trên version 2

Theo source thì có vẻ như là v2 chỉ check không phải file, ngoài ra thì thả cửa các protocol khác cho curl sử dụng, kể cả gopher :D. Mặc dù tôi đã thử upload polyglot zip + jpg để include nhưng không được :?
Thử SSRF quét cổng local thấy port 8337 có content length response vượt trội.

Trên localhost:8337

Đây có lẽ là trang để khai thác lỗi SSRF qua parameter url=
Search apache axis exploit để tìm kiếm các exploit có sẵn, đừng bắt exploit anymore, plz :(
Có rất là nhiều bài liên quan nhưng chỉ có 1 bài cung cấp exploit cho apache axis là
Oracle PeopleSoft Remote Code Execution: Blind XXE to SYSTEM Shell (người viết bài này là cfreal, cũng là người phát hiện ra lỗi của Magento, hay Apache, exploit cả PHP :o )
Dùng cái script trong bài thì không exploit được luôn. Thế là lại phải ngồi chỉnh 1 tí. Lúc này còn gần 2 tiếng là hết giờ :(
Phần này tôi lười writeup :P

  • Setup 1 site tương tự, Axis2 nhìn có vẻ không giống lắm, setup Axis bản 1.4 viết bằng Java
    • I f*** hate Java, nhưng mà vì tiền thì tôi vẫn làm thôi :(
  • Sửa exploit cho giống, về cơ bản là thay các tham số, và dùng payload mới update trong blog phía trên.
    • Do dùng gopher thoải mái nên những gì không GET được thì ta dùng gopher://, vì curl hỗ trợ mà :D
Về sau là mất thời gian debug python3 vì messed up with byte and string :(
Và lỡ tay script kiddie :( class org.apache.pluto.portalImpl.Deploy not found

Do payload ghi log (chứa php code) ra file cần 1 path cố định, ghi vào folder upload bên trong path mặc định là /var/www/html/
Anyway, bỏ qua phiên bản 1 bị lỗi thì đây là một challenge rất thực tế. New era of SSRF!

Best band of Asia

Để làm được challenge này cần hoàn thiện 2 phần:

  • SQL Injection để leak sourcecode
  • Exploit

1. SQL Injection

Có thể dùng tool hoặc manual phát hiện ra lỗi SQL injection ở http://3.0.183.241/index.php?controller=image&act=detail&id=<inject_here>

Dùng sqlmap để khai thác lỗi, kiểm tra role của user hiện tại thì thấy đây là user 'root', tuy nhiên có sai sót là khi đọc /etc/passwd thì lại không được. Nên tôi đành ngoại cảm là giống như 1 số challenge tôi từng làm, thì chức năng download sẽ cho download file với filename extract từ database. Thử thì được thật
http://3.0.183.241/index.php?controller=image&act=detail&id=1111+union+select+'/var/www/html/index.php'+--+-

Tiếp theo là đọc xem source sử dụng, include những file nào để download về thôi.
Sau khi có được source, phần tiếp theo chỉ là code review,  nhưng có cả cấu trúc Database thì tốt hơn :D
2. Exploit:
Source code được viết rất sạch đẹp, mô phỏng một ứng dụng MVC, thì thông thường phần model là class, phần controller cũng là class.

  • Đối với model thì để thao tác với database là chủ yếu, không có gì đặc biệt.
  • Đối với controller thì ngoài lỗi SQL injection thì còn có:
    • controller_image.php : Bypass restricted area để upload file tùy ý, nhưng sẽ bị đổi đuôi file thành .jpg
    • Khi php kết thúc thực thi thì magic function __destruct trong controller_audio.php sẽ gọi function save trong model, nếu model đó là image.php, thì có thể tạo file tùy ý
Để tấn công unserialize mà không cần unserialize? Magic quá nhỉ, tuy nhiên các bạn không cần đũa thần của Harry Potter để hô phép trong trường hợp này, chỉ cần đọc paper  It's a PHP unserialization vulnerability Jim, but not as we know it, nhìn chung, bạn sẽ được cung cấp phương pháp phát hiện và khai thác lỗi php object injection mới lạ (hay vẫn gọi là unserialize) thông qua các functions php và stream wrapper "phar://"

Code gen phar

Cái ta còn thiếu đó là 1 nơi có thể dùng stream phar và 1 function có thể gọi input bất kì. Các function có khả năng khai thác là file_exists, getimagesize, ...
Đâu đó trong source cũng có nơi như vậy, fetchImagePage lấy response từ một trang bất kì, lọc ra source bên trong tag <img> rồi thực hiện getimagesize để kiểm tra tính hợp lệ của file ảnh.

Kết hợp lại thì có code test:

Further reading:
https://ctf.yeuchimse.com/acebear-security-contest-2019-duudududduduudstore-image-service-best-band-of-asia-web-100/

No comments:

Post a Comment