Mates CTF Session #3 Round 2 Writeup

Mình tham gia vòng này trong team pwnjutsu với vị trí phụ trách mảng Web. Và rất tiếc là lần này team mình thiếu đi mất một pwner chủ chốt gánh team là anh Quang Thái nên bị thọt, cuối cùng đành chịu xếp hạng 4 :(.

Some Node

Đây không phải là một bài Web, mà là một bài RE, nhưng do mình đang stuck nặng với các bài web và đề này cho dịch một đoạn mã javascript (nodejs) đã obfuscated đúng sở trường của mình nên mình làm luôn.

Đề cho file sau: https://gist.github.com/yabeow/b2f4a5ca6702a62b6738a259bda792b5

Nhìn thì rối rắm, nhưng thật ra chỉ là encode hex, một thủ thuật khá cơ bản trong obsfucate, mình dùng tool này để decode ra xem sao:

https://gist.github.com/ChiChou/7cac61e6923979bf1cd8

Tiếp tục decode, ta chỉ cần thay hàm eval thành console.log là dễ dàng xem được source code được thực thi bởi hàm eval.

Chỉnh code lại cho đẹp xíu, và thành quả sau khi deobfuscate:

Tới đây thì bạn còn phải thay các biến như b('0x10'),... để dễ đọc, tuy nhiên với bài này chỉ cần hiểu được dòng lệnh for là đủ. Hàm for này chạy như sau:

for (var d = 0; d < input.length; d += 2) {  
   a = SomeThing(input[d]);
   b = SomeThing(input[d+1]);
   if (!SomeThing(a, b)) {
      console.log('KO');
   }
}

Cơ bản bài này làm khó người đọc với một đống hàm như BigNumber, lúc đầu mình sai lầm khi ngồi dịch hết đống code cũng như tìm cách hoạt động, sau đó đọc hiểu được dòng for trên thì thấy có thể brute force được, với 2 kí tự thì khá là nhanh.

Để chắc chắn mình chạy thử file này, để chạy file này phải set up môi trường nodejs và cài thư viện bignumber.js:

yarn init  
yarn add bignumber.js  
(mình thích dùng yarn hơn npm :D)
node node.js  

Do mình biết được các kí tự đầu của flag là matesctf{, nên mình thử nhập vào lần lượt mỗi 2 kí tự thì thấy: nếu sai thì chương trình sẽ trả về KO, nếu đúng thì chương tình sẽ không trả về KO. Nên từ đây mình viết code brute force, và viết ngay trên javascript luôn cho dễ:
(Mình đã đổi lại hàm nhập vào thành hàm trả về kết quả true/false)

Và đây là phờ lắc:

matesctf{OMG_I'm_s0_t1r3d_0f_m4k1ng_l0ng_fl4g}

Responsive Tournament Bracket

(Vì BTC đã close đề nên mình không chụp lại hình được)

Bài này mình stuck khá nặng vì không tìm được hướng làm. Nhưng sau khi có hint của BTC. là dir search files/.* thì mình đã tìm ra hướng. Lúc đầu mình cũng đã để ý tới cái link files/.* này, có vẻ link này do code python xử lí để đọc dữ liệu từ đường dẫn trên và in ra nội dung, dẫn tới lổ hổng LFI. Tuy nhiên chỉ đọc được file ở thư mục root của đề và mình thử fuzz mãi không ra file nào nên thọt. Và cũng nhờ hint của BTC, mình tìm được cái tool này:
https://github.com/maurosoria/dirsearch

Fuzz qua tool, mình tìm được:
files/.DS_Store, đây là một file ẩn của MacOS, có thể chứa thông tin các file trong thư mục. Tiếp tục xài tool python-dsstore để đọc:

Ta đã tìm được tên file .py chưa code của server bài trên, và qua đường dẫn files/325uy32g6u3h4iu6hi43njjjhj.py, có được source code như sau:

Đoạn base64 kia chính là kết quả của các trận đấu. Đề cho truyền vào parameters GET data là dữ liệu trận đấu (base64) và theo description của đề bài và đoạn hàm checkVNvodich thì ta chỉ cần làm sao để Vietnam vô địch là lấy được flag, mình thực hiện một công đoạn rất eazy đó là thay đội Hàn Quốc trong trận chung kết thành Việt Nam, (ước gì mình cũng có thể làm như vậy ngoài đời :(...) Để chạy được vào hàm xử lý ?data kia thìa GET parameters là state cũng phải đúng với trên server. Nếu bạn chạy local thì biến state này sẽ là 123, mình chạy thử payload sau trên local thì lấy được flag:

?state=123&data=base64_dữ_liệu_trận_đấu_đã_chỉnh_sửa

Tới đây thì mình đã hí hửng chuẩn bị đăng nhập vào submit flag rồi. Tuy nhiên đời không như là mơ, chạy server của đề bài thì không ra flag, tại sao, vì sao em hỡi?...

Tại vì đoạn code này đây :(

Vì trên local của mình không có file /opt/id nên state của mình lúc này là 123, tuy nhiên trên server thì giá trị này là bí mật, mình không thể biết được. Lổ hổng LFI ở trên cũng không làm gì được vì chỉ đọc được file trên thư mục root của đề bài.

Tới đây thì mình nhận ra mình vẫn chưa sử dụng một chức năng khác của bài là get info của một trận đấu cụ thể nào đó, thử tìm hiểu chức năng này hoạt động như thế nào:

Cơ bản là đề cho phép lấy thông tin của từng trận đấu qua link info/id_trận_dấu, và còn cho phép lấy attr của trận đấu đó qua param ?field, ví dụ info/id_trận_đấu?field=score thì sẽ chỉ trả về tỉ số của trận đấu.

Tuy nhiên để tìm được giá trị state đúng, ta còn cần xem qua đọc code init dữ liệu trận đấu như sau:

ư

Có thể thấy biến prev_hash này đang lưu giá trị bí mật ta cần tìm, và biến này lại được lưu vào attr __prev của trận đầu tiên, điều này có nghĩa ta chỉ cần đọc attr __prev của trận đầu tiên trong giải là có thể biết được giá trị này. Kết hợp với chức năng trên, ta có:

info/id_trận_đấu_đầu_tiên?param=__prev

Tuy nhiên đời không như là mơ tập 2, vì __prevprivate attr (OOP) trong python, nên ta không thể truy cập trực tiếp được :(

Tới đây thì bạn hay giải bài về python trong các giải CTF cũng biết rằng python có rất nhiều thủ thuật khá vi diệu, và _dict_ là một trong số đó:

Mình đã biết được giá trị state đúng với server của BTC, tiến hành submit payload và lấy được flag:

matesctf{DS_Stor3_And_python_Class}

P/S: Mình rất vui khi giải ra bài này vì chỉ ít giờ sau đó, Việt Nam đã chính thức đăng quang ngôi vô địch giải AFF 2018. Và với CTF, mình hi vọng Việt Nam cũng sẽ tiếp tục đoạt lại được danh hiệu này. (2014 - 20??) ???

Seal niêm phong