Jika XSS Reflected itu seperti pantulan cermin (datang dan perg saat itu juga) , maka XSS Stored itu seperti coretan di tembok.
Penyerang tidak cuma menumpang lewat, tapi dia menuliskan kode jahat ke dalam database server, karena kodenya tersimpan disana maka setiap kali ada orang lain yang datang mengunjungi halaman itu, browser mereka akan otomatis membaca coretan tersebut dan menjalankan perintah jahatnya.
Analogi XSS Stored
Bayangkan ada sebuah buku tamu di resepsionis hotel:
- Kondisi Normal: Tamu menulis "Hotelnya Bagus!!" di buku tamu. Pengunjung lain yang membaca tulisan itu hanya akan tersenyum.
- Serangan XSS Stored: Penyerang menuliskan mantra sihir di buku tamu tersebut yang bunyinya "Siapapun yang membaca tulisan ini, berikan kunci kamar kalian kepada saya!!".
- Dampaknya: Setiap kali ada tamu baru atau staf hotel yang membaca buku tamu tersebut, mereka secara tidak sadar (terkena sihir) akan menyerahkan kunci kamar mereka kepada si penyerang.
Dampak XSS Stored
- Masal: Satu kali serangan bisa mengenai ratusan hingga ribuan pengguna yang mengunjungi halaman tersebut.
- Diam-diam: Korban tidak perlu mengklik link aneh. Mereka hanya mengunjungi website resmi yang sudah "tercemar".
- Pencurian Data: Sangat efektif untuk mencuri session cookie semua orang yang login, termasuk admin website.
Fitur XSS Stored Di DVWA

- Kolom Nama (name): Tempat memasukkan nama pengunjung.
- Kolom Pesan (Message): Tempat memasukkan komentar atau pesan.
- Tombol Sign Guesbook: Untuk mengirim pesan ke database.
- Tombol Clear Guestbook: Khusus utuk menghapus semua pesan di database (sangat berguna jika website sudah berantakan akibat percobaan sendiri).
- Area Tampilan Pesan: Bagian bawah yang menampilkan daftar semua orang yang sudah mengisi buku tamu.
XSS Stored DVWA
- Level Low
Pada level ini website benar-benar polos tidak ada filter atau pembersihan input pengguna. Akibatnya, jika memasukkan kode javasript website akan menganggap kode tersebut adalah bagian dari sisi halaman yang harus dijalankan.

Gambar diatas menunjukkan jika input dari pengguna adalah teks biasa atau bukan kode jahat javascript.

Jika yang dimasukkan adalah script javascript seperti <script>alert('LEVEL LOW SUCCES')</script> maka ketika Sign Guesbook di klik pop-up akan muncul.

Setelah itu terlihat ada penambahan daftar user, dimana user ke 2 dengan nama Dimas pesan nya kosong dikarenakan adalah script javascript yang dijalankan di halaman website tersebut.

Saat halaman di inspeksi terlihat script yang dimasukan pada user dimas.
Kerentanan Level Low
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>- Level low bersifat output tanpa filter, apapun yang tersimpan di database akan langsung dijalankan oleh browser sebagai kode aktif.
- penggunaan mysqli_real_escape_string yang malah tujuannya mencegah SQL Injection jadi XSS tetap lolos.
2. Level Medium
Pada level ini sudah diterapkan beberapa filter untuk name dan massage tapi filter yang digunakan sangat spesifik dan mudah dikelabui.

Pada level ini jika script javascript diterapkan di kolom massage, maka tidak akan terjalankan. Dikarenakan pada variabel massage diterapkan htmlspecialchars yang akan menghapus karakter berbahaya seperti <script>

Maxlengt pada gambar diatas perlu diubah jika ingin memasukkan script kedalam kolom inputan "Name" jika tidak maka jumlah huruf yang bisa dimasukkan akan dibatasi. Setting ke 100

Perhatikan pada user dua yang Name berisi sebuah script. Kenapa kode javascript tidak terjalankan, dikarenakan pada kolom inputan Name diterapkan fungsi str_replace yang sifatnya case sensitivity yang berarti harus dikombinasi dengan huruf kapital pada <script>.
Hasilnya jika script dimasukkan pada kolom Name dengan script <Script>alert('LEVEL MEDIUM SUCCES')</script>
Karentanan Level Impossible
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>Meskipun di level ini diterapkan fungsi seperti str_replace dan htmlspechialchars tetapi polanya sangat mudah terlihat dan mudah dikelabui.
3. Level High
Pada level ini digunakan fungsi Regular Expression (Regex) yang ketat untuk mendeteksi kata "script".

Jika script javascript dimasukkan pada kolom Name, maka hasil yang tampil adalah seperti pada gambar. Dikarenakan pada variabel name diterapkan fungsi preg_replace yang menolak pada pola kalimat atau kata (<script>).

Tetapi jika script javascript dimasukkan pada kolom massage hasilnya script akan ditampilkan sebagai teks biasa karena adanya fungsi htmlspechialchars yang bertujuan menetralkan karakter khusus menjadi teks biasa.

Jika script yang dimasukkan pada kolom name dengan <img src=x onerror= alert('LEVEL_HIGH_BERHASIL')> maka pop-up akan muncul.
Dikarenakan pada kolom Name menggunakan fungsi preg_replace maka digunakan tag image <image> untuk menghindarinya.
Kerentanan Level High
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>Celah utama pada level ini adalah memanfaatkan penggunaan tag image pada kolom Name dikarenakan sanitasi yang digunakan adalah preg_replace dengan fokus pada <script> jadi penggunaan <img> akan lolos dan berhasil jalan.
4. Level Impossible
Pada level ini prinsipnya "Jangan percaya siapapun". Semua input yang masuk, baik itu di kolom nama maupun pesan, dianggap sebagai teks biasa yang berbahaya jika dijalankan langsung di browser.

Jika kedua kolom di isi dengan kode Javascript hasilnya akan ditampilkan sebagai teks biasa, dimana muncul beberapa karakter aneh seperti <, #309 itu adalah fungsi kerja dari htmlspechialchars().
Keamanan Level Impossible
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>- Penggunaan htmlspechialchars() disemua kolom yang menetralkan karakter HTML menjadi teks seperti < dan >.
- Validasi Token Anti-CSRF untuk memastikan bahwa data yang masuk ke buku tamu benar-benar dikirim langsung oleh pengguna asli melalui formulir resmi website.
- Fungsi bindParam() yang juga mengamankan dari serangan SQL Injection dimana data pengguna dipisahkan dari perintah database sehingga penyerang tidak bisa memanipulasi perintah SQL untuk mencuri data.