TetCTF 2019 writeups - phplimit revenge - phplimit revenge v2 - TSULOTT2 - IQTest2 - Easy Web Server

phplimit revenge


$GET['code'] sẽ được kiểm tra 2 lần trước khi được truyền vào hàm eval(), mục tiêu của chúng ta là vượt qua 2 lần kiểm tra đó để thực thi code mình mong muốn.

preg_replace('/[^\W]+\((?R)?\)/'''$_GET['code'])

Regex /[^\W]+((?R)?)/ sẽ khớp với chuỗi có dạng như 1 lời gọi 1 hàm không có đối số trong php.
Ví dụ: echo(), sha1(), ... và tên hàm chỉ gồm các kí tự alphabet + number.
Đồng thời có thể lặp lại pattern này nhiều lần.
Ví dụ: echo(sha1()), echo(sha1(rand())), ...

preg_match('/_|m|info|get/i',$_GET['code'])

Regex trên sẽ khớp với chuỗi có các chuỗi hoặc kí tự sau: _ m info get

Vậy để hàm eval() được gọi thì code của chúng ta phải là lời gọi hàm của 1 hoặc nhiều hàm lồng vào nhau, không chứa _ m info get và đồng thời phải kết thúc bằng ;

Ví dụ: echo(sha1());, echo(rand());, ...

Chúng ta giả sử rằng flag nằm trong 1 file nào đó trên server, vậy mục tiêu của chúng ta là:

  1. Tìm được tên file flag.
  2. Đọc file flag đó.

Tìm tên file flag
echo(serialize(scandir('.')));
Đoạn code trên sẽ liệt kêt tất cả file và folder trong thư mục hiện tại, nhưng chắc chắn sẽ không được thực thi vì chuỗi '.'
Chúng ta sẽ tìm hàm nào đó trả về chuỗi '.' thay thế cho việc truyền trực tiếp vào.
Ví dụ:
                           pi() -> 3.1415926535898
                strrev(pi()) -> 8985356295141.3
        ceil(strrev(pi())) -> 8985356295142
 chr(ceil(strrev(pi()))) ->
Ý tưởng là nếu trước khi gọi hàm chr thì chúng ta sẽ gọi sin strrev ceil nhiều lần thì ta sẽ có cơ hội có được .

Để cho thuận tiện, chúng ta sẽ deploy một server php để tạo chuỗi . theo cách đã nêu trên

Đầu tiên chúng ta sẽ scan các file trong thư mục hiện tại!
echo(serialize(scandir(dot));image1-5

Tiếp theo dùng hàm readfile để đọc file well_play_get_flag_here.php
Chúng ta sẽ dùng thêm hàm end để truy xuất phần tử cuối cùng của mảng trả về của hàm scandir
echo(readfile(end(scandir(dot))));

readfile

Flag: TetCTF{_Limbo_Escaped!Welcome_back_to_Real_Life}
Script: exploit.py



phplimit revenge v2

Source code gần giống bài trước, chỉ thêm strlen rand path vào regex.
Sử dụng script ở bài trước, chúng ta cũng tìm thấy và đọc được 1 file, nhưng đó không phải là file chứa flag.image1-3
Theo như gợi ý thì flag sẽ nằm ở thư mục cha của thư mục hiện tại, tức là ..
Chúng ta sẽ dùng hàm next để truy xuất chuỗi .. từ mảng trả về của scandir và scan 1 lần nữaimage2-2
Đến đây chúng ta không thể dùng hàm readfile để đọc file well_play_take_fl4g_here.php được như bài trước, vì file này không nằm chung với thư mục chúng ta đang làm việc hiện tại (working directory).
Để đọc được file well_play_take_fl4g_here.php chúng ta phải chuyển thư mục làm việc sang cùng với file trên bằng hàm chdir
Đoạn code này hơi dài dòng nên khó hiểu, chúng ta phải thật cẩn thận, ý tưởng là thêm hàm chdir vào trong đối số hàm pi() (do pi() không có đối số nên không ảnh hưởng tới kết quả hàm pi())image3
Bingo!

Flag: TetCTF{Hey___PhP___Master_}
Script: exploit.py



TSULOTT2

image1-6
Đây là một trang bet, chúng ta chọn một số từ 0 đến 50, nếu trúng thì ta sẽ được số tiền ta đã đặt vào, nếu thua thì ta sẽ mất số tiền đó.
Sau khi xem qua các trang thì chúng ta biết:

  1. Khởi đầu chúng ta có $1000 trong tài khoản.
  2. Có thể reset số tiền trong tài khoản về mặc định ($1000).
  3. Tiền dùng để mua: flower - $500, source - $2000 và flag - $1.337.000.000

Với $1000 ban đầu chúng ta hoàn toàn có thể bet lên $2000 để mua source trong 1 lần thắng, nếu thua thì mình reset lại số tiền.

Script: autobet.py

image3-1
Với cookie trên ta có thể dùng để mua source
image4

Source: source.py

Phân tích source code chúng ta phát hiện ra vài điều thú vị:

  1. Data trước khi mã hóa bằng AES có dạng như sau: number=%s;bet=%s;session=%s
  2. Chúng ta có thể control được giá trị session lúc tạo ticket
  3. Có thể bypass được hàm check_bet (nếu server sử dụng python3, mình vô cùng bất ngờ khi nhận được điều này từ 1 thành viên trong team)python3 bằng chuỗi 1_000_000_000_000, vì với chuỗi này hàm checkbet chỉ xem như 1, nhưng hàm int() lúc thay đổi session['current_money'] sau khi bet lại xem như 1000000000000

Mục tiêu của chúng ta sẽ là: bypass hàm checkbet để chúng ta có thể bet số tiền lớn hơn số tiền hiện tại chúng ta đang có bằng chuỗi 1_000_000_000_000
Nhưng lúc mua thì ta không thể bet với số tiền này lúc mua ticket vì hàm isnumeric()image5

Giải pháp: chúng ta sẽ chọn number và bet sao cho giá trị của session nằm riêng block với các biến khác rồi bỏ 1 block đầu tiên và tạo lại IV sao cho chúng ta có thể tạo lại giá trị của biến bet từ block 2.
Ví dụ:
number=10;bet=100000000;session=ssssssssssssssss
number=10;bet=10number=1;a=;bet=ssssssssssssssss
Bây giờ chúng ta chỉ cần set cookie session=1_000_000_000_000 thì chúng ta có ticket với giá trị bet là $1000000000000 mặc dù tài khoản của chúng ta chỉ có $1000
number=10;bet=10number=1;a=;bet=1_000_000_000_000[padding]

Sau khi có được ticket này thì chúng ta chỉ cần dùng nó để bet đến khi nào win được 1 lần thì chúng ta sẽ có đủ tiền để mua flag (giống như cách lúc đầu ta dùng để mua source).
image2-3
Bingo!

Flag: TetCTF{Pyth0n__3___hahahah4hahaha}
Script: exploit.py



IQTest2

image1-7
Chúng ta có thể lấy source php thông qua tham số is_debug

Source: index.php

Để có được flag chúng ta phải trả lời 13 câu hỏi, những câu đầu rất đơn giản, nhưng càng về sau thì hầu như chẳng thể trả lời được nữa.

Level 1: Có vẻ dễ nhỉ
level1

Level 7: Thôi, không trả lời nữa, đọc code để tìm lỗi 😅
level7-1

Mỗi level sẽ có seed tương ứng được lưu trong $GLOBALS['seed_key']
Khi ta trả lời đúng level 1 thì seed của level2 sẽ được lưu trong cookie để xác định level tiếp theo, kèm theo đó là hash md5(seed + secret key)
Nếu seed chúng ta gửi lên server không có trong seed_key thì level sẽ được gán bằng level1 bằng cách dùng explode + parse_str, điều đặc biệt là chúng ta control được quá trình explode + parse_str này, dẫn đến chúng ta có thể overwrite biến level tùy ý.
owerwritelv
Ý tưởng là thế, nhưng đầu tiên seed được băm với secret key rồi check với hash chúng ta gửi lên, không có secret key thì chúng ta không thể qua được bước băm này.
Để qua được bước này chúng ta sẽ dùng hash length extension attack để có thể tạo ra hash hợp lệ mà không cần đến secret key.

Tool: hash_extender

Tool trên sẽ dùng seed và hash được biết trước (lấy từ các level đầu mà ta có thể trả lời được) để tính ra được hash của seed mới gắn thêm vào mà không cần secret key.
Vì không biết độ dài của secret key nên chúng ta sẽ bruteforce (data và hash mình lấy từ level7)
genhash
attack

Dùng cookie trên ta có thể vào được level 13 để lấy flaggetflag

Flag: TetCTF{Happy_new_Y3aR!!H3re_Your_Flower}
Script: exploit.py



Easy Web Server

Source: challenge.rar

Đây giao diện của trang loginimage5-1
Đăng nhập thử với password aaaaimage4-1
Wow, in ra password khi nhập sai luôn!

Mở file service bằng IDA xem thử thế nào.
Lần theo các routing ở hàm main ta tìm ra được hàm xử lý các request tới /login
image7
Trong hàm này password sẽ được đọc từ file secret/password và lưu vào biến s
Còn password chúng ta send lên server lúc login sẽ được lưu ở v8, sau đó được copy 256 byte vào dest, s và dest sẽ được so sánh với nhau bằng hàm strcmp. Một điều quan trọng nữa là s và dest cách nhau 256 byte trên stackimage1-10
Vậy nếu chúng ta nhập password dài 256 byte thì dest sẽ không có null byte, nên dest và s sẽ thành 1 chuỗi liên tục và khi login không thành công thì server sẽ trả về password chúng ta vừa nhập, cũng đồng nghĩa ta sẽ leak được password.image3-2
Đặt breakpoint và debug thửimage9

Password dài 255 kí tự A ta thấy dest còn 1 kí tự null
image8image10
Nhưng với password dài 256 kí tự
image11image12

Bingo!

Password: MTUhXldFI1RHQVQjWUVIR1EjXldBRlFXVFRRI15AI0Ah

Sau khi đăng nhập, trong trang admin có api POST /chung69vn
Ta cũng lần theo các routing ở hàm main để tìm ra hàm sử lý các request tới api này
Hàm này sẽ lấy data mình gửi lên (POST thông qua biến info) write vào file info/ip/ip.txt với ip là remote ipimage13
Ngoài ra còn có 1 api nữa để đọc file trên là GET /info?ip=image14
Chúng ta sẽ tận dùng hàm snprintf để truncation path, trước đó thì ip mình gửi lên thông qua GET sẽ được kiểm tra với remote ip bằng hàm strncmp với độ dài của remote ip nên chúng ta dễ dàng bypass bằng cách gửi lên ip bắt đầu bằng remote ipimage15
image16
Bingo!

Flag: TetCTF{Th4nk_4_pl4ying_my_ch4ll3ng3s}

Show Comments

Get the latest posts delivered right to your inbox.