Contents
Upcoming
Chia sẻ thật thì câu này mình làm cả buổi sáng :), lí do thì đơn giản thôi ban đầu mình chạy dirsearch
nhưng nó chỉ trả về hai endpoint này
Tới trưa mình chạy lại thì lại lòi ra /about
(endpoint này đóng vai trò quan trọng trong việc solved challenge này)
😡😡😡cay thật chứ
Overview:
Check source một lúc thì các bạn sẽ thấy một file .js
Thử call hàm readFlag
trên console, chỉ nhận lại một fake flag
Còn về endpoint /file
thì có vẻ như dùng để "bẫy", mình chỉ có thể đọc được duy nhất file /etc/passwd
Trở lại với endpoint tìm được từ dirsearch
:
Ở đây ta lại được "mở" thêm tính năng search, và sau một hồi fuzz mình nhận ra vuln sqli
Việc còn lại chỉ cần tìm tên table, column
Và flag
WAF-Deser
Source code: https://github.com/to016/CTFs/tree/main/SVATTT/2022/Qual/WAF-Deser/src
Theo mình cảm nhận thì challenge java này không quá dễ nhưng cũng không quá khó
Từ file docker-compose.yml
có thể thấy có 2 service web
và nginx
Ở file nginx.conf
, nginx server được cấu hình như sau
server {
listen 80;
large_client_header_buffers 4 3000; # Limit URI length upto 3000 bytes
location ~* H4sI {
return 403 'Deserialization of Untrusted Data Detected. (From real WAF with <3)';
}
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass "http://web:8080";
}
}
- URI length phải <= 3000 bytes
- Nếu có dính chuỗi
H4sI
thì sẽ return 403 - Thỏa mãn các điều kiện trên thì forward request đến service
web
chạy ở port 8080
Tiếp đó mình extract file waf-deser-0.0.1-SNAPSHOT.jar
ra và mở với Intellij IDEA
Từ thư mục lib, có thể thấy tác giả add vào commons-collections4-4.0.jar => dự đoán là deseri với chain cc4
Class User
package vcs.example.wafdeser;
public class User {
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public Integer getAge() {
return this.age;
}
}
Class UserController
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package vcs.example.wafdeser;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.GZIPInputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
public UserController() {
}
@GetMapping({"/"})
public String sayHello() {
return String.format("Hello ASCIS");
}
@RequestMapping(
value = {"/info/{info}"},
method = {RequestMethod.GET}
)
public String getUser(@PathVariable("info") String info, @RequestParam(name = "compress",defaultValue = "false") Boolean isCompress) throws IOException {
String unencodedData = this.unEncode(info);
String returnData = "";
byte[] data = Base64.getMimeDecoder().decode(unencodedData);
if (isCompress) {
InputStream is = new ByteArrayInputStream(data);
InputStream is = new GZIPInputStream(is);
ObjectInputStream ois = new ObjectInputStream(is);
try {
User user = (User)ois.readObject();
returnData = user.getName();
ois.close();
} catch (Exception var9) {
returnData = "?????";
}
} else {
returnData = new String(data, StandardCharsets.UTF_8);
}
return String.format("Hello %s", returnData);
}
private String unEncode(String s) {
return s.replaceAll("-", "\\r\\n").replaceAll("%3D", "=").replaceAll("%2B", "\\+").replaceAll("_", "/");
}
}
Nhận vào một chuỗi ở dạng base64 từ url /info/{info}
và gán vào giá trị cho PathVariable info
sau đó thực hiện base64 decode chuỗi này và return lại về giá trị sau đi đã decode
Đặc biệt hơn nếu GET param compress=true
, thì sẽ thực hiện thêm gzip decompress và sau đó deserialize với readObject()
=> Có thể áp dụng chain cc4 ở đây
Nhưng không đơn giản như vậy, vì nginx đã cấu hình nếu url match H4sI
-> 403, mà chuỗi H4sI
này lại là magic byte của gzip sau khi đã base64 encode 🥺, thế thì có cách nào để bypass không ?
Để ý trong class UserController
, tác giả dùng Base64.getMimeDecoder()
để decode chuỗi, mình search wiki về dạng MIME này của Base64 và sau một hồi lướt lên lướt xuống được dẫn tới link sau https://datatracker.ietf.org/doc/html/rfc2045
=> Có thể thêm một kí tự như tab %09
vào giữa H4%09sI
là xong
Một điểm nữa ở bài này đó là server không có curl nên mình chọn cách reverse shell để đọc flag, và payload reverse shell chọn từ link này (ở phía dưới có 2 link nữa, các bạn nên đọc để hiểu tại sao payload của họ lại như vậy)
Sau đó gzip compress + base64 encode dữ liệu từ file này (may là < 3000 bytes)
Ở phía server họ có xài hàm unEncode
nên mình tạo thêm hàm enEncode
để encode vài kí tự cho giống bên server
public static String enEncode(String s) {
return s.replaceAll("\\r\\n", "-").replaceAll("=", "%3D").replaceAll("\\+","%2B").replaceAll("/","_");
}
Sau đó urlencode
Và gửi payload lên server, nhớ vụ "%09" nhé
Kết quả
Lời kết
Thú thật thì mình không nghĩ sẽ giải ra câu java ở trên, bởi vì lúc solved thì cũng đã hơn 4h chiều, mình tốn khá nhiều thời gian vì gặp rắc rối với tool cũng như bị lan man vào mấy thứ vớ vẩn khác. Nhưng qua vòng thi khởi động này mình cảm thấy tự tin hơn hẵn, hiển nhiên vì lần này ngoài cái cái áo mới thì còn mang về được giải 😁. Hi vọng bản thân vẫn sẽ giữ vững phong độ cho cuộc thi chung khảo sắp tới.