Home / Artikel / Web Security
Web Security

Data dari Pengguna Bukan Musuh: Cara Mencegah XSS di PHP dan WordPress

Cross-site scripting atau XSS sering muncul bukan karena aplikasi tidak punya firewall, melainkan karena data pengguna ditampilkan tanpa perlindungan yang sesuai konteks. Kenali perbedaan validasi input, escaping, dan s…

Data dari Pengguna Bukan Musuh: Cara Mencegah XSS di PHP dan WordPress

Sebuah kolom komentar, nama pengguna, judul artikel, atau parameter URL terlihat seperti data biasa. Namun begitu data itu ditampilkan kembali ke browser tanpa perlindungan yang tepat, ia bisa berubah menjadi pintu masuk cross-site scripting atau XSS.

XSS terjadi ketika browser memperlakukan data yang seharusnya hanya menjadi teks sebagai HTML atau JavaScript. Dampaknya tidak selalu berupa halaman yang berubah tampilan. Dalam kondisi tertentu, penyerang dapat menyalahgunakan sesi pengguna, menampilkan formulir palsu, memantau interaksi, atau menjalankan tindakan atas nama korban. OWASP mengingatkan bahwa XSS bukan satu masalah dengan satu obat; pertahanan harus disesuaikan dengan tempat data tersebut digunakan.

Kesalahan yang sering terjadi: menganggap semua data aman

Banyak aplikasi menerima data melalui formulir, URL, cookie, API, atau database. Pengembang kemudian merasa aman karena sudah memeriksa input saat data masuk. Padahal, data yang lolos validasi tetap perlu diamankan lagi ketika ditampilkan.

Bayangkan seseorang mengisi nama dengan teks biasa. Nilainya mungkin aman saat disimpan di database, tetapi konteksnya berubah ketika nama itu dimasukkan ke dalam atribut HTML, URL, atau kode JavaScript. Browser membaca setiap konteks dengan aturan yang berbeda. Karena itu, satu fungsi escape tidak cocok untuk semua tempat.

Validasi input menjawab pertanyaan: “Apakah data ini sesuai aturan aplikasi?” Escaping menjawab pertanyaan: “Bagaimana data ini ditampilkan sebagai data, bukan sebagai kode?” Sanitasi digunakan ketika aplikasi memang perlu menerima sebagian HTML, misalnya editor artikel atau komentar dengan format tertentu.

Prinsip utama: amankan data saat akan ditampilkan

Untuk teks biasa di dalam HTML, gunakan output escaping. Proses ini mengubah karakter khusus seperti tanda lebih kecil atau tanda kutip menjadi bentuk yang tidak ditafsirkan sebagai markup.

Dalam PHP, fungsi dasar yang umum dipakai adalah htmlspecialchars(). Contohnya:

<?php
$name = $_GET['name'] ?? '';
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');

ENT_QUOTES membantu mengubah tanda kutip tunggal dan ganda, sementara deklarasi UTF-8 membuat penanganan karakter lebih jelas. Namun, fungsi ini cocok untuk konteks teks HTML dan atribut sederhana, bukan untuk semua tempat.

Jangan menaruh data pengguna langsung ke dalam blok JavaScript, CSS, event handler seperti onclick, atau URL hanya karena sudah melewati htmlspecialchars(). OWASP menekankan bahwa encoding harus mengikuti konteks tujuan. HTML encoding tidak otomatis membuat nilai aman untuk JavaScript atau URL.

Pola aman di WordPress

WordPress sudah menyediakan fungsi escaping yang sebaiknya dipakai ketika output dibuat. Prinsipnya sederhana: lakukan escaping sedekat mungkin dengan saat data dicetak, bukan hanya ketika data disimpan.

  • esc_html() untuk teks yang ditampilkan di antara tag HTML.
  • esc_attr() untuk nilai atribut seperti class, title, atau value.
  • esc_url() untuk URL yang dipakai pada atribut seperti href dan src.
  • wp_kses() atau wp_kses_post() ketika aplikasi memang perlu mempertahankan HTML yang diizinkan.

Contoh yang lebih aman untuk judul dan tautan:

<h2><?php echo esc_html( $title ); ?></h2>
<a href="<?php echo esc_url( $url ); ?>">
<?php echo esc_html( $label ); ?>
</a>

Hindari pola seperti echo $title; jika nilainya berasal dari pengguna, metadata, API, atau sumber lain yang belum benar-benar Anda kendalikan. Database internal juga bukan alasan untuk menganggap data selalu bersih. Data bisa saja masuk melalui impor, plugin lain, akun admin yang diretas, atau proses lama yang memiliki celah.

Escaping bukan berarti menghapus semua HTML

Kesalahan lain adalah memakai satu pendekatan untuk seluruh konten. Jika pengguna hanya boleh memasukkan nama, gunakan validasi ketat dan tampilkan sebagai teks. Jika pengguna boleh menulis artikel dengan format tebal, tautan, dan daftar, menghapus semua HTML akan merusak fungsi yang memang diinginkan.

Dalam situasi tersebut, gunakan sanitasi dengan daftar elemen dan atribut yang diperbolehkan. Di WordPress, wp_kses_post() dapat membantu mempertahankan subset HTML yang lazim digunakan dalam konten, tetapi tetap perlu memperhatikan kebutuhan aplikasi dan pembaruan keamanan.

Sanitasi juga bukan proses sekali seumur hidup. Pustaka sanitasi harus diperbarui, dan hasil yang sudah disanitasi jangan dimodifikasi lagi dengan cara yang dapat memasukkan kembali konten berbahaya.

Periksa juga XSS yang terjadi di browser

XSS tidak selalu berasal dari template PHP. Kode JavaScript di browser juga dapat membuat masalah, terutama saat mengambil nilai dari URL, location.hash, atau input pengguna lalu memasukkannya ke innerHTML.

Untuk menampilkan teks biasa melalui JavaScript, lebih aman memakai textContent:

const message = new URLSearchParams(location.search).get('message') || '';
document.querySelector('#message').textContent = message;

Berhati-hatilah dengan innerHTML, document.write(), eval(), dan atribut event handler. Jika aplikasi benar-benar perlu merender HTML dari pengguna, gunakan sanitizer yang dirawat dan dikonfigurasi dengan benar, bukan sekadar menghapus kata <script>.

Yang bisa dilakukan sekarang

  1. Daftar semua tempat aplikasi menampilkan data dari pengguna: halaman HTML, atribut, URL, JavaScript, email, dan respons API.
  2. Pasangkan fungsi escaping dengan konteksnya, bukan dengan asal datanya saja.
  3. Di WordPress, ganti output langsung seperti echo $value dengan fungsi yang sesuai, misalnya esc_html() atau esc_attr().
  4. Cari penggunaan innerHTML, document.write(), dan event handler inline di JavaScript.
  5. Tambahkan pengujian untuk karakter seperti <, >, tanda kutip, dan nilai yang menyerupai URL atau HTML.
  6. Gunakan Content Security Policy sebagai lapisan tambahan, bukan sebagai pengganti perbaikan kode.

Intinya, data pengguna tidak harus dianggap sebagai musuh dan tidak selalu perlu dibuang. Yang penting adalah menjaga agar browser memahami data tersebut sebagai data. Validasi membatasi apa yang boleh masuk, sanitasi membersihkan HTML ketika HTML memang diperlukan, sedangkan escaping memastikan data tidak berubah menjadi kode saat ditampilkan.

Sumber & bacaan lebih lanjut

Jelajahi juga

– Rio Yotto @rioyotto