[Tết CTF 2019] — Web write-ups

Áo đẹp quá :(

Cũng một thời gian dài từ khi mình tập tành chơi CTF mình chọn web vì nó dễ tiếp cận, nhưng bắt đầu một thời gian mình lại nhận ra kiến thức mình chưa đủ có, mình cần rèn thêm tư duy, rèn khả năng lập trình ( thời điểm lúc này chỉ mới ở mức cơ bản như hello world :( ), và mình quyết định chọn Crypto để theo đuổi sau đó vừa có thể rèn tư duy cũng như khả năng lập trình. Những lần cay cú vì giải không ra challenge lại kiên trì tiếp tục, những lần try hard vì để tiến bộ, hay cả những lần bị “ăn chửi” cũng làm mình có tinh thần hơn. ( “what doesn’t kill you makes you stronger” ).
Lần này do giải không có Crypto nên quyết định quay lại làm web try hard, nhưng mà may mắn thay có đến 2 bài dính Crypto nên mình cũng dễ dàng tiếp cận và hoàn thành. Cùng với đó có sự hộ trợ của a @Jang đã giúp mình hoàn thành 2 bài php. Sau đây là phần trình bày của mình:

1. FILE

http://139.180.219.222:8004/

Xem sơ qua thì trang web không có gì đặc biệt, chỉ có 1 file ảnh, mình quyết định dùng tool scandir thử, mình tìm thấy 1 github khá hay: https://github.com/WangYihang/SourceLeakHacker

Scan 1 hồi thì có 1 file hay ho như .DS_Store , lúc này nhận ra ngay rồi do .DS_Store gặp từ mates round 2 , rồi 35C3 junior, rồi tìm thêm 1 github khác: https://github.com/lijiejie/ds_store_exp

Và kết quả:

Và ta có flag: TetCTF{__DS_Store__seems_sad__}

2. IQTest2

http://45.76.148.31:9004/
index page

View source thì thấy ?is_debug=1 => show source

Source here: https://pastebin.com/raw/MvGsV3bx

Đại loại là ứng với 1 câu hỏi nếu trả lời đúng thì server sẽ gen ra 2 cookie là saved và hash cho level tiếp theo, và đây là cách server gen cookie:

mình đọc đến chỗ set hash là biết ngay là Hash Length Extension, các bạn có thể đọc wu của mình về bug này wu ViettelStore Mates s2 Round5 hoặc UIT Hacking Contenst để hiểu thêm.

Đây là script solve của mình: https://gist.github.com/ducnhse130201/ef451668ad37fd7dce8aa85779e1e4c3

3. TSULOTT2

http://149.28.144.129/

Dạo 1 vòng xem từng tính năng và source thì ở ‘/market’ ta thấy:

Ban đầu có $1000, ta cứ bet vô tội vạ, ăn thì có source đọc không thì reset(), ez game, code 1 xíu và ta có source, code lấy source: https://pastebin.com/raw/vutbND5B, và source đây: https://pastebin.com/raw/ficiNjc1

Bạn nào mà thử scandir thì cả cái list đều response về [200], do flask ấy =))), ban đầu có chút bối rối nhưng lấy được source rồi thì ko dại gì mà scandir nữa, review code thôi

Sau khi review code, mình thấy có vài chỗ đáng ngờ:

  1. AES CBC bit flipping là có thể, insecure unpad function

2. Vì game bet tiền nên liệu có thể cheat không? chẵng hạn bet tiền âm nếu thua => được cộng lên, nhưng tác giả đã filter mất r :sad:

3. buy_ticket()

buy_ticket() gen ticket theo form mình đã biết “number=?;bet=?” + AES CBC => bit flipping là khả thi

4. Xem qua hàm get_length() và check_bet()

Liệu có thể bypass check_bet() không?

chỉ cần control được hàm get_length() là ổn đúng không? Chẳng hạn như thế này đây:

  • bet = ‘ 10000000000’ ( space + 10000000000), get_length() sẽ trả về 0, nhưng int(space + 10000000000) = 10000000000 (chỗ này chỉ cần request bet=99000000000 cho pass bet.isnumeric() ở buy_ticket(), sau đó bit flipping là được)
  • money chỉ cần làm sao cho thành $0 thôi, bet $1000 và thua
  • Lúc này check_bet() return true rồi, chỉ việc request đến khi nào trúng jackpot là có tiền mua flag :hehe:

Nhưng, cách này của mình khả thi nếu không có dòng này

fuck strip(), xóa dấu cách :sad:, lại tạch

Sau cả một ngày stuck thì mình nhận được hint từ @chung96vn là xem changelog của python 3.6, vừa mở lên xem vài dòng đầu thì đã tìm thấy cái mình cần:

:3 vẫn idea cũ thôi nhưng không phải dấu cách mà là ‘_’. It’s a feature it’s not a bug :pepe:

Chốt ý tưởng:

  • request với number = 1, bet = 1000000000000000
  • ta có 16 bytes đầu ticket như thế này:
    ticket = “number=1;bet=100”
  • bit flipping để 16 bytes đầu thành:
  • ticket = “number=1;bet=10_”
  • lúc này check_length(bet) = 2 => ta cần chuẩn bị sao cho money của mình sao cho check_length() = 2 và lớn hơn 10 => check_bet return True
  • Việc còn lại request đến khi trúng jackpot là dư tiền mua flag
  • Have fun

Đây là script solve của mình: https://gist.github.com/ducnhse130201/5a65d8cda1ed6d1adc8d95adc153a3a1

4. phplimit revenge

http://139.180.219.222/

Sau khi mình giải xong bài TSULOTT2, mình cũng khá stuck với bài này, vì tác giả filter khá độc, để pass hết các check thì phải thỏa mãn những điều kiện sau:

  • code phải chứa ‘;’
  • phải có dạng function a(b(c())), và only [A-Za-z0–9], điều này có nghĩa là không thể truyền parameters cho function
  • không được chứa các keyword sau: ‘_|m|info|get’

Mình hoàn toàn không biết làm như thế nào, bới tung các function của php5 xem các hàm nào thỏa các điều kiện trên và thử, và vào buổi trưa đẹp trời với sự giúp đỡ của a @Jang, a @Jang tìm được path với payload:
http://139.180.219.222/?code=echo(realpath(defined(assert())));

Và đây là lời giải thích:

It’s php

Có được path rồi thì:
http://139.180.219.222/?code=echo(serialize(scandir(realpath(defined(assert())))));

thấy flag rồi đọc thôi:

http://139.180.219.222/?code=echo(readfile(end(scandir(realpath(defined(assert()))))));

5. phplimit revenge v2

http://45.76.181.81/

Vẫn giống như bài trước như tác giả filter thêm các keyword: strlen,rand và path, điều này có nghĩ payload cũ không xài được nữa do có realpath, chợt a @Jang nãy ra 1 ý tưởng hay là thay vì scandir(realpath) thì bây giờ scandir(‘.’) cũng sẽ có kết quả tương tự, vậy làm sao gen ra được ‘.’ từ các hàm mà vẫn qua filter, it’s math time =)))

meme time =)))
damn !!!

Và ta có payload:
http://45.76.181.81/?code=echo(serialize(scandir(chr(floor(rad2deg(sin(cos(cos(asinh(pi()))))))))));

Nhưng,

well_play_but_flag_not_here.php :sad:
Okay go to directory’s parent directory

Thay vì scandir(‘.’) thì ta scandir(‘..’) thôi, mà bây h không lẽ dùng skill gen ra ‘..’ từ pi() hay sao? =))), nope, trong scandir(‘.’) đã có ‘..’ rồi =))):

http://45.76.181.81/?code=echo(next(scandir(chr(floor(rad2deg(sin(cos(cos(asinh(pi()))))))))));

Và flag đây rồi:

http://45.76.181.81/?code=echo(serialize(scandir(next(scandir(chr(floor(rad2deg(sin(cos(cos(asinh(pi()))))))))))));

Nhưng mà readfile không được vì phải truyền đúng path, phải là ‘../ well_play_take_fl4g_here.php’ mới đúng, một lần nữa a @Jang nghĩ ra thêm 1 ý đó là dùng chdir(), change directory về ‘..’ xong ta readfile flag là xong, nhưng điều này có nghĩa payload phải như thế này:
chdir(‘..’);readfile(); (lúc này phải thêm 1 ‘;’ nữa sẽ không pass được if 1)

Again, it’s php, it’s magic,

Sẽ như thế nào nếu: pi(chrdir(‘..’)) sẽ vẫn trả về giá trị của pi, nhưng ta sẽ chdir thành công, có nghĩa là không cần thêm 1 dấu ‘;’

Và cuối cùng ta cũng có flag với final payload:
http://45.76.181.81/?code=echo(readfile(end(scandir(chr(floor(rad2deg(sin(cos(cos(asinh(pi(chdir(next(scandir(chr(floor(rad2deg(sin(cos(cos(asinh(pi()))))))))))))))))))))));

Great challenges,

Conclusion: Cảm ơn các a @tsug0d, @chung96vn & @hocsama đã đầu tư tổ chức 1 game CTF đầu năm fun và hay như vậy, Cảm ơn a @Jang đã ngẫu hứng sp em để có thể làm các bài Php trên :v, Cảm ơn a @nghiadt đã không ngủ 1 hôm để làm pwn, đúng là máu thì chơi tất mà =))), Anyway cảm ơn mọi người đã dành thời gian đọc write-ups của em, chúc mọi người một năm mới an lành, thành công trong công việc và vững bước trên con đường đã chọn.

Peace,

Nub-boi