Hướng dẫn chơi ctf web app

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:

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

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,