## https://sploitus.com/exploit?id=591AEE99-21FD-5A1C-8280-68F42EB93846
# Lab Demo CVE-2026-29000: pac4j-jwt Authentication Bypass
Môi trường Lab gọn nhẹ dùng để minh họa và học tập về lỗ hổng **CVE-2026-29000** (Authentication Bypass) ảnh hưởng đến thành phần `JwtAuthenticator` trong thư viện bảo mật **pac4j-jwt** của Java.
---
## 1. Tổng quan lỗ hổng (CVE-2026-29000)
Lỗ hổng xảy ra khi hệ thống được cấu hình hỗ trợ đồng thời cả **Ký số (JWS)** và **Mã hóa (JWE)** cho JSON Web Tokens (JWT).
### Cơ chế lỗi logic:
1. Khi máy chủ nhận được một token được mã hóa (JWE), nó sẽ tiến hành giải mã bằng khóa riêng tư RSA (`RSAPrivateKey`).
2. Sau khi giải mã thành công lớp vỏ ngoài JWE, máy chủ sử dụng thư viện Nimbus JOSE+JWT để đọc phần dữ liệu bên trong thông qua hàm `payload.toSignedJWT()`.
3. Trong thư viện Nimbus, nếu dữ liệu bên trong là một token hợp lệ có ký số (JWS), hàm sẽ trả về đối tượng `SignedJWT`. Tuy nhiên, nếu kẻ tấn công gửi một token không có chữ ký số (**PlainJWT** với Header `alg: none`), hàm này sẽ trả về **`null`**.
4. Trong các phiên bản lỗi của `pac4j-jwt` (dưới `5.7.9`), khối mã xác thực chữ ký bị ràng buộc bởi điều kiện `if (signedJWT != null)`. Vì `signedJWT` lúc này bằng `null`, máy chủ sẽ **silent-skip (bỏ qua hoàn toàn) việc kiểm tra chữ ký số** nhưng vẫn tin tưởng các trường dữ liệu (claims) trong payload để cấp quyền đăng nhập thẳng cho hacker!
---
## 2. Cấu trúc thư mục Lab
Lab đã được đơn giản hóa tối đa, gộp cả backend và frontend tĩnh vào một cổng duy nhất (`4567`) chạy trên nền tảng Java hoàn toàn ngoại tuyến (offline-ready):
```text
CVE-2026-29000-lab/
├── lab/
│ ├── pom.xml # File cấu hình Maven (SparkJava, Nimbus JOSE+JWT, Gson)
│ └── src/
│ └── main/
│ ├── java/com/demo/
│ │ ├── App.java # Điểm khởi chạy server, định tuyến API, sinh cặp khóa RSA
│ │ ├── VulnerableAuthenticator.java # Mô phỏng logic lỗi của pac4j
│ │ └── PatchedAuthenticator.java # Mô phỏng bản vá chống lỗi
├── run-lab.ps1 # Script PowerShell tự động thiết lập và khởi chạy
└── README.md # Hướng dẫn này
```
---
## 3. Yêu cầu hệ thống
* **Java JDK**: Phiên bản 17 trở lên (đã kiểm thử mượt mà trên JDK 24).
* **PowerShell** (Windows).
* *Không yêu cầu Node.js / NPM / Maven cài đặt sẵn* (Maven được tải dạng portable tự động).
## 4. Hướng dẫn khởi chạy
Bạn có thể khởi chạy môi trường Lab theo 2 cách sau:
### Cách 1: Sử dụng PowerShell (Chạy trực tiếp trên máy)
Mở PowerShell tại thư mục dự án này và chạy lệnh:
```powershell
powershell -ExecutionPolicy Bypass -File .\run-lab.ps1
```
> **Dọn dẹp:** Để tắt Lab, bạn chỉ cần quay lại cửa sổ terminal chạy script, gõ **`q`** rồi nhấn **`Enter`**. Tiến trình chạy ngầm sẽ được dọn dẹp tự động.
### Cách 2: Sử dụng Docker (Khuyên dùng - Tiện lợi & Cách ly)
Môi trường đã được đóng gói sẵn **Multi-stage Dockerfile** và **Docker Compose** tối ưu. Bạn chỉ cần chạy lệnh sau từ thư mục dự án:
* **Khởi chạy tự động với Docker Compose:**
```bash
docker compose up --build
```
* **Hoặc Build & Run thủ công bằng Docker CLI:**
```bash
docker build -t cve-2026-29000-lab .
docker run -p 4567:4567 cve-2026-29000-lab
```
Sau khi chạy thành công bằng bất kỳ cách nào, máy chủ sẽ lắng nghe và phục vụ API trực tiếp tại địa chỉ: **`http://localhost:4567`**.
---
## 5. Các bước thực hiện Demo lỗ hổng
### Kịch bản A: Đăng nhập hợp lệ (Legitimate Flow)
1. Trên giao diện Web, tại ô **1. Đăng nhập hợp lệ**, giữ nguyên Username `user_lucas` và Role `user`.
2. Click **Đăng nhập & Lấy Token**. Server sẽ trả về một token chuẩn dạng `JWE(JWS)` có chữ ký số và mã hóa đầu cuối.
3. Nhấp **2. Gửi yêu cầu truy cập Dashboard**. Quan sát phản hồi HTTP Status `200 OK` hiển thị quyền truy cập thành công dưới vai trò user thông thường (Không có flag).
### Kịch bản B: Khai thác bỏ qua xác thực (Vulnerable Mode Exploit)
1. Kiểm tra trạng thái máy chủ ở trên cùng phải ở chế độ màu đỏ **`VULNERABLE (pac4j < 5.7.9)`**.
2. Tại ô **2. Giả mạo & Tấn công (Exploit)**:
* Target Username: Nhập `admin`
* Target Role: Cấp quyền `administrator`
3. Click **1. Yêu cầu sinh Token giả mạo**. Server bổ trợ sẽ sinh ra một JWE chứa một PlainJWT (alg: none) không hề ký số.
4. Click **2. Gửi yêu cầu truy cập Dashboard**.
5. **Kết quả:** Đăng nhập thành công trực tiếp với quyền `administrator` (HTTP Status `200`) nhờ lỗ hổng bypass chữ ký, hiển thị thông điệp chào mừng quản trị viên và Flag bí mật:
`FLAG{CVE_2026_29000_PAC4J_BYPASS_SUCCESS}`.
### Kịch bản C: Thử nghiệm cơ chế phòng thủ (Patched Mode defense)
1. Click nút **Đổi chế độ máy chủ** ở góc trên để chuyển trạng thái sang màu xanh **`SECURE / PATCHED`**.
2. Nhấn nút **2. Gửi yêu cầu truy cập Dashboard** một lần nữa với token giả mạo trước đó.
3. **Kết quả:** Yêu cầu bị từ chối thẳng thừng với HTTP Status `401 Unauthorized` cùng thông báo lỗi bảo mật chính xác:
`Signature verification failed: JWE payload is not a signed JWT!`
### Kịch bản D: Tấn công tự động bằng Python Script (PoC)
Bên cạnh giao diện web, bạn có thể thực hiện cuộc tấn công tự động từ Terminal bằng script Python được viết sẵn (`poc.py` - không yêu cầu cài đặt thư viện ngoài):
1. Khởi chạy máy chủ Java (`run-lab.ps1`).
2. Mở một terminal mới trong thư mục dự án và chạy script:
```bash
python poc.py
```
3. **Kết quả khi máy chủ dễ bị lỗi (Vulnerable):** Script tự động kết nối lấy khóa công khai, sinh token JWE PlainJWT giả mạo vai trò `administrator`, chiếm quyền truy cập Dashboard thành công và in ra cờ Flag: `FLAG{CVE_2026_29000_PAC4J_BYPASS_SUCCESS}`.
4. **Kết quả khi máy chủ an toàn (Secure):** (Bấm đổi chế độ máy chủ sang Patched trên Web rồi chạy lại script). Script in ra kết quả cuộc tấn công bị chặn đứng (`HTTP 401`) kèm mô tả lỗi từ chối của máy chủ.
---
## 6. So sánh chi tiết mã nguồn dễ bị lỗi và Bản vá
### Đoạn mã lỗi (`VulnerableAuthenticator.java`):
```java
SignedJWT signedJWT = encryptedJWT.getPayload().toSignedJWT();
if (signedJWT != null) {
// Chỉ kiểm tra chữ ký NẾU inner token là JWS (khác null)
JWSVerifier verifier = new RSASSAVerifier(publicKey);
if (!signedJWT.verify(verifier)) {
throw new JOSEException("Invalid signature on inner JWT!");
}
return signedJWT.getJWTClaimsSet();
} else {
// NẾU inner token là PlainJWT (null), nhảy thẳng vào đây!
// Bỏ qua xác thực chữ ký hoàn toàn, tin cậy payload thô trực tiếp
PlainJWT plainJWT = PlainJWT.parse(payload.toString());
return plainJWT.getJWTClaimsSet();
}
```
### Đoạn mã bản vá (`PatchedAuthenticator.java`):
```java
SignedJWT signedJWT = encryptedJWT.getPayload().toSignedJWT();
if (signedJWT == null) {
// BẢN VÁ: Nghiêm cấm hoàn toàn token không chữ ký bọc trong JWE!
throw new JOSEException("Signature verification failed: JWE payload is not a signed JWT!");
}
// Bắt buộc luôn phải xác thực chữ ký số của JWS
JWSVerifier verifier = new RSASSAVerifier(publicKey);
if (!signedJWT.verify(verifier)) {
throw new JOSEException("Invalid signature on inner JWT!");
}
return signedJWT.getJWTClaimsSet();
```