Giải Wannagame này thực sự có những bài web rất hay và chất lượng, để có thể làm được 1 challenge mình đã mất 2 ngày do thiếu quan sát. Tuy rằng không thể giải kịp trong giờ thi, nhưng không sao, mình đã học thêm được nhiều cái, thì sau đây sẽ là write up cho giải WannaGame, mình có đi hỏi các anh và xem writeup của những người tham gia và đây là ý hiểu của mình về các cách giải đó.
Challenge cung cấp cho mình source code, challenge được viết bằng python và sử dụng một thư viện đặc biệt tensorflow
để đếm sao cho chuẩn xác
Mình sẽ test trên local trước, vì instance chỉ có 2 phút thui
Src code cho tính năng upgrade:
preminum key
có trong session id thì mình sẽ được set thuộc tính preminum = 1 và các tính năng còn lại sẽ được nâng cấplongtitude
và latitude
trong 1 json body post đặt vào 2 biến x và y, load một model, chính xác hơn là một thư mục nằm ở trong thư mục của người dùng mà mình có thể biết được thông qua session, MODEL được gắn hard code là counting_stars.h5
nên mình không can thiệp được việc nó có load được model khác. Sau đó đưa giá trị 2 biến x y vào mảng và tiến hành đoán :)) đoạn này quá toán học nên mình không hiểu lắm, nhưng sau đó biến predict sẽ cho ra kết quả và đó là số tỉ ngôi sao nằm trong bán kính 2808 mét quanh vị trí mà mình nhập vào (thiên văn học toán học vật lý học quá)Wow! The stars are aligning
mà không show lỗi ra cho userVẫn như trước đó, nó sẽ check xem mình có phải người dùng premium không, nếu không thì sẽ coi là người dùng bình thường và 30s mới dùng được tính năng này 1 lần như là bên trên, nên đoạn code đó mình bỏ qua
--spider -o -
-> Output đưa ra là Length của file và file có tồn tại hay không. Rồi website bắt regex đoạn đằng sau chữ Length và xem xem nó có chứa 1 thành phần hay không -> nếu chứa 2 thằng là 2 length -> 2 file -> không cóa đâu. Tiếp đến kiểm tra phần tử đầu tiên có độ dài lớn hơn 10000 hay không, nếu có sẽ trả về file quá to, tôi không thể nhậncounting_stars.h5
. Nếu trùng thì xóa model đó đi và copy lại một file counting_stars.h5
mới vào thư mục, đồng thời đưa ra thông báo đừng làm gì server tôi =)). Nếu như thành công sẽ báo về status 200 và lưu file vào hệ thốngGiờ bắt đầu từ thứ sussy baka nhất, là tính năng propose_model cho phép tải file từ URL bằng wget. Thì đầu tiên mình nghĩ đến việc blind command injection vào đoạn wget, nhưng cách đó không khả thi khi người ra đề không nối chuỗi mà để hẳn biến vào trong subprocess, cách viết như vậy không thể command injection:
Sau đó mình có một suy nghĩ ngây thơ là nếu như chỉ cần không phải là counting_stars.h5
, thì mình up webshell lên để lấy persistent =))). Nhưng không, mình không thể truy cập đến được trực tiếp thư mục models
của đề bài cho. Nên cách này cũng oẳng
Vậy chỗ nào mới trigger đến file ở trong thư mục này? Chỉ có chức năng counting_stars
làm việc đó, vì nó sử dụng load_model
để load file h5 nằm trong thư viện của chính mình
, suy ra để trang web thực thi file thì mình cần phải upload lên 1 file counting_stars.h5
chứa nội dung là câu lệnh mà mình muốn -> RCE. Hiện tại mình chưa có cách nào khác ngoài cách này vì mình không thể upload lên được 1 file khác và bypass đoạn file name, biến models đã được hard code là counting_stars.h5
và so sánh ở python quá chặt =)) nên mình từ bỏ ý định bypass file name mà tập trung vào trước hết là kiếm shell của file h5 đã
Mày mò một lúc không ra, đang trong cơn tuyệt vọng thì Chương bạn mình tìm ra được một chỗ viết shell và export ra một file h5, mình sẽ có thể trigger nó thông qua câu lệnh load_model
-> câu lệnh mà chức năng counting_stars sử dụng, chi tiết sẽ nằm ở đây, mình sẽ chỉ sử dụng nó thôi =)):
Đoạn code gen ra file h5 sẽ như này, mình sẽ test với câu lệnh id
:
Sau khi chạy đoạn code này nó sẽ gen ra file test.h5
mà khi load model đến nó sẽ trigger câu lệnh id:
Ngonnnn, vậy là mình được shell h5 rồi, giờ mình cần phải giải quyết đến vấn đề nếu file cùng tên nó sẽ xóa file và copy file counting_stars.h5
mới vào, xem lại đoạn code nào:
Nó tải về này, rồi nó check xem file đấy có phải counting_stars.h5
không này, rồi nó mới xóa, hmmmmmm
Đúng, nó tải về rồi nó mới xóa -> Race Condition
flask-unsign
Nhập đoạn premium key này vào là vấn đề của chúng ta sẽ được giải quyết, ngonnn.
Rồi, giờ thì câu hỏi đặt ra sẽ là race như thế nào? Ý tưởng đầu tiên là mình sẽ race upload file counting_stars.h5
này lên, và đồng thời là request trigger nó ở /couting_stars
để out bound ra ngoài bằng curl hoặc wget.
Tuy nhiên sau khi thử cả một buổi chiều không được thì mình khá là đuối. Sang hôm sau mình có đi hỏi và người anh Hưng Chiến đã chỉ mình cách race của anh ấy. Bây giờ mình sẽ upload 1 file bất kì ngay sau khi upload file counting_stars.h5
để khi check thì file bất kì đó mới là mới nhất và nó sẽ bỏ qua đoạn xóa file -> file mới của mình sẽ thay thế file cũ. Lúc này nhanh tay chèn ngay theo 1 request đếm sao để trigger model mới của mình -> Đọc flag -> Solve
Vậy thì mình đã gần như là hoàn chỉnh các bước rồi, tuy nhiên trong quá trình thực hiện mình liên tục dính lỗi status 304 khi wget file dựng trên apache và ngrok ra ngoài. Lý do là response header Last-Modified
, nếu header này vẫn thế ở lần wget thứ 2 thì wget sẽ không tải file mà để lại cái thông báo ở trong log như sau
Sau khi thử config lại apache nhưng vẫn không giòn, mình quyết định dựng 1 cái server php không có 1 cái gì hết để chứa file, khi đó thì sẽ không có header Last-Modified
-> không lỗi
Bắt đầu tiến hành khai thác, mình sẽ tạo file h5 như đã nói để trigger lấy flag, nhưng trước đó mình cần câu lệnh lấy flag vì instance quá nhanh để có thể lấy persistent:
Mình test trên docker để xem câu lệnh nào hoạt động, và mình đã thành công với
Vẫn sử dụng script trên, mình tạo file
Mình sẽ thử wget –spider file này để xem length của nó có thỏa mãn hay không
Vừa khít dưới 10000 luôn =))), shell của mình thỏa mãn điều kiện rồi
Mình dựng server chứa file bằng php với câu lệnh
Server được lập ra ở tab desktop nên có thể truy cập tới file lmao.txt
và counting_stars.h5
một cách dễ dàng. Dựng ở local nên hiện tại mình sẽ sử dụng ip của máy ảo mà chưa ngrok ra ngoài:
Như mình đã lấy ở trước, mình dùng premium key và upgrade lên để không bị giới hạn request:
Mình sẽ lấy session sau khi được gán premium=1 để nhét vào script python
Giờ mình sẽ có 3 luồng để chạy song song với nhau, đầu tiên là up file counting_stars.h5
lên, sau đó up file lmao.txt
để file h5 không bị xóa, và cuối cùng là lấy flag bằng cách sử dụng chức năng đếm sao ở tab /counting_stars
Script mình khai thác ở local:
Script sẽ thành công nếu như in ra được status 200 -> nghĩa là in ra chữ Please contact admin and told him to check your file
nên mình để in ra json respond của mỗi payload để quan sát cho dễ
Script đã thành công kha khá lần rồi nên mình dừng lại và check ở request repo.
Flag về đến bản rồi:
Để ý trong docker file model gốc (17.2 kB) đã bị thay thế bằng file model của mình (9.7 kB)
Exploit thành công
Sự việc diễn ra rất nhanh nên mình cũng không chụp được quá trình làm bài :))), nhưng để làm trên instance thì mính sẽ cần phải thay đổi các đường dẫn và session ở trong script trên (như tham gia Vượt Lên Chính Mình), đồng thời ngrok cổng 1234 của server php kia để instance còn tải được file h5 của mình về
Script khai thác của mình ở instance cũng tương tự như vậy thui:
Sau khi chạy thì flag về request repo của mình như sau:
Flag: W1{10_out_of_100_796a480d3968d4862419227123e01d70}