[Web]WannaGame 21/05/2021
Last updated
Was this helpful?
Last updated
Was this helpful?
Ta sẽ dựa vào lỗi nằm ngay hàm unserialize() để khai thác challenge. Lỗi này có tên là PHP Object Injection. Ta sẽ tiến hành khai thác lỗi này bằng cách cung cấp cho server những đầu vào không được lọc kĩ trước khi được xử lý bởi hàm unserialize(), kết quả là ta có thể tùy ý gán giá trị cho các object bên trong chương trình.
Ta để ý rằng các object trong class User được khai báo private nên ta không thể nào gán giá trị bên ngoài hàm được. Trong 2 class còn lại thì Show_color chứa một phương thức PHP magic là __destruct, bên trong phương thức này là một hàm call_user_func(). Hàm này có thể giúp ta gọi bất cứ hàm nào khác có trong PHP, ta sẽ lợi dụng điều này để gọi hàm system.
Ta sẽ thử nghiệm bằng cách tạo một object Show_color và gán giá trị theo mong muốn vào object này. Sau đó ta sẽ đưa object vừa tạo vào hàm serialize(), để khi cung cấp cho server thì dữ liệu này sẽ được đưa vào hàm unserialize() và trở lại dữ liệu gốc.
Chạy chương trình php có chứa đoạn mã trên, ta sẽ được payload như sau:
code=O:10:"Show_color":2:{s:5:"color";s:2:"ls";s:4:"type";O:8:"stdClass":1:{s:3:"adu";s:6:"system";}}
Dùng payload trên gán vào trường code của challenge và chạy, ta sẽ có được kết quả trả về là danh sách các file có trong thư mục là config.php và index.php. Điều này có nghĩa là ta đã thành công và có thể sử dụng bất cứ lệnh nào để khai thác server này.
Ta sẽ tiến hành chỉnh sửa payload trên để gọi lệnh strings đến file config.php:
code=O:10:"Show_color":2:{s:5:"color";s:30:"strings config.php | grep flag";s:4:"type";O:8:"stdClass":1:{s:3: "adu";s:6:"system";}}
Kết quả là ta sẽ có được flag của challenge.
Description:
Source:
Tóm tắt source code: Ý tác giả nói có 1 file flag đã được require. Truyền 1 param ?file
và server sẽ require file được truyền, mặc định không truyền gì sẽ require file etc/passwd.
Flag đang nằm trong flag.php
và ta muốn đọc nó thông qua require thì buộc dùng php wrapper
mã hóa dữ liệu bên trong để in ra màn hình.
PAYLOAD: ?file=php://filter/convert.base64-encode/resource=flag.php
Kết quả tất nhiên sẽ là không nhận lại được gì vì require_once
sẽ không require những file được được require trước đó!
Và có một cách để qua mặt require_once
với keyword: require_once symlink php bug.
Là một dân chơi ctf thì phải thông thạo 7 linux 1 triệu điểm và sẽ biết được rằng "current work dir" (cwd) là ./
và có 1 symlink dẫn đến cwd: /proc/self/cwd
(Thử trên mấy local sẽ biết :*).
Kết hợp 2 thứ vừa tìm được thì có ngay 1 payload để exploit:
PAYLOAD: ?file=php://filter/convert.base64-encode/resource=/proc/self/cwd/flag.php
Kết quả ôi thành bug ngờ @@ Không có gì xảy ra :). Lúc này dần mất niềm tin vào cuộc sống nhưng đọc kỹ lại thì mình đã làm trùng với BUG #38634 và bug này đã không thể dùng được nữa, ông tìm ra cũng không reproduce được.
Và lần này rút kinh nghiệm phải tìm BUG ID số lớn để mới mới xíu và tìm được #79980.
Giải thích bug: Khi resolve 1 path thì các symlinks của path này được "followed". Nhưng cái gì có giới hạn của nó để tránh vòng lặp vô hạn, giới hạn của symlinks được gọi là "LINK_MAX". Khi ta gọi đến 1 file nhưng path của file đó mình cứ lặp lại vượt ngưỡng "LINK_MAX" thì sẽ biến require_once
hoạt động chỉ như require
thôi!
PAYLOAD cuối cùng của mình (loop 21 lần so với trên doc chỉ tận 22 lần :v): ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
Như vậy thành công nhận được chuỗi base64:
Decode:
FLAG: flag{mu0n_r0j_m4_s40_c0n}
Description:
Source:
Bài này ta phải pass 3 round để có được flag:
Round1: 1 !== 2 && 1==2
Trong php, khi ta truyền 1 chuỗi bắt đầu với "0e" thì khi so sánh yếu (==) sẽ cho ra kết quả bằng nhau vì php sẽ hiểu đây là float number format strings, sau đó khi php so sánh yếu 2 chuỗi như vậy thì sẽ tự động chuyển thành so sánh dưới dạng số. Để không gặp lỗi này thì phải so sánh cứng (===).
Round2:3 !== 4 && md5(3) == md5(4)
Ta search với key word: magic md5 thì sẽ nhận được 1 list danh sách các chuỗi có 1 đặc điểm chung là sau khi hash đều trả về kết quả 1 chuỗi bắt đầu với "0e" và nhưng các giải thích trên ta cứ chọn 2 chuỗi bất kỳ.
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
Round3: 5!==6 && sha1(5)===sha1(6)
Với việc so sánh cứng thì ta không thể tận dụng trick ở round 2 nhưng ta là có 1 trick khác. Việc ta ép kiểu mảng cho 5 và 6 thì khi hash php sẽ không thể hash object và return giá trị NULL giúp ta dễ dàng qua mặt kiểm tra thứ 2. Ở điều kiện thứ 1 phải làm cho 2 array này khác nhau thì ta chỉ cần gán giá trị khác nhau cho mỗi mảng.
Minh chứng:
PAYLOAD: ?1=0e1&2=0e2&3=QLTHNDT&4=QNKCDZO&5[]=1&6[]=2
FLAG: flag{tjnk_d4u_nku_ckj3c_r4nq_kh0n_l4m_mjnk_d4u}
Đây là một bài LFI khá đơn giản, việc ta cần làm là bypass file_exists()
và xác định được tên của flag đang ở đâu, tên gì.
Ta thử với flag.txt nhưng báo lỗi file không tồn tại. Thử lại với flag.php thì xuất hiện 1 trang trắng chứng tỏ đây đúng là tên file cần tìm.
Bypass file_exists()
ta có thể sử dụng payload ./foo/../flag.php
để đọc nội dung file. Nhưng vì flag là file php nên ta không đọc được phải dùng php wrapper.
Payload: ?
f=php://filter/convert.base64-encode/resource=./foo/.. /flag.php
Và cũng thật khó hiểu khi dùng php wrapper ta hong cần dùng
./foo/../
để bypass hàmfile_exists()
mà nó sẽ tự động bypass luôn.
Sau khi dùng payload trên ta nhận được một chuỗi base64 sau khi decode ta nhận được flag:
Challenge này chặn khá nhiều thứ, dấu \\, dấu nháy đơn, nháy kép, $, @ rồi các câu lệnh linux như cat, ls, nl, head, less, more, tail, grep, dir… Nhưng tác giả đã filter thiếu rất nhiều command có thể tìm và đọc file khác.
Flag của mình đoán sẽ có chữ “flag” trong tên tệp nên dùng echo flag* để tìm tất cả các file có chữ flag trong tên.
Tìm thấy file flag rồi, mà tên flag còn dài hơn cả payload được chèn vào. Nhưng không sao, may mắn là ta không bị chặn dấu *. Mặc dù có vẻ như tất cả các lệnh dùng để đọc file đều đã bị chặn nhưng chúng ta vẫn còn lệnh strings để in ra các ký tự trong tệp tin.
Giờ sử dụng strings để đọc flag thôi.
Theo lời chia sẽ của tác giả đây là một bài mà tác giả đã không filer kỹ nên mới làm dễ như thế :)))
Bài này chính là bài nâng cấp sau khi author phát hiện ra lỗi lầm của mình ở web05 :))
Ta không thể sử dụng các lệnh thông thường để tìm và đọc file như trước.
Bài này đã nhường cho mình một con đường khó khăn nhưng là khả thi nhất: echo và sh.
Tóm tắt ý tưởng bài này như sau: Quyền của user hiện tại là www-data thấp nhất nên chỉ có quyền đọc ở hầu hết thư mục nhưng vẫn có quyền đọc, ghi, thực thi ở thư mục /tmp
. Ta sẽ sử dụng echo
để tạo file và dùng lệnh sh
để thực thi file đó. Như vậy đấy là cách rce của chúng ta, bắt đầu thôi.
Sau 7749 lần test trên máy local thì mình đã tìm được payloads thích hợp và viết 1 đoạn script để gửi request đến server.
FLAG: flag{adu_vjp_pr0_boi}
Ban đầu nhìn vào source cứ nghĩ phải bypass được mấy cái if để có flag nhưng 3p nghĩ là là không thể :)) để kiểm chứng điều đó thử lfi (ớ nhánh điều kiện !== admin) đọc file config thì thật sự trong đó hong có flag :v
Xác định lại 2 dữ kiện ở bài này: LFI, session_start()
Sau vài tiếng mài quần ở quán cf kế ktx mình đã tìm được một keyword cực kỳ xịn: from lfi to rce via php session.
WORKFLOW: Với 2 params name
và pass
, ta sẽ truyền code php cho name
để rce server ( đoạn code này sẽ được serialize rồi lưu vào file session_id trong path default của php) và path/session_id/file/stored cho pass
để thực hiện lfi (đoạn code php trên được userialize -> trigger).
Lưu ý: ở lần đầu tiên request lên thì code php được store vào session_id file. Khi đó ta thực hiện lfi để trigger file sẽ không có gì xảy ra cả, thử request lại thì sẽ trigger thành công (giải thích nôm na lần đầu là store, lần sau mới được call ra thành công)
Thử với <? phpinfo(); />
Giờ thì thực hiện rce để kiếm flag thì thấy 1 file ẩn /.pho_flag, thực hiện đọc flag
FLAG: flag{you_got_it_dude}
Reference:
Tham khảo:
vulnerability:
session: