Cara Mengatasi Serangan XSS di Codeigniter

Yakin aplikasimu sudah aman dari serangan XSS?

Kalau saya tidak bisa 100% yakin, karena tidak ada sistem yang 100% aman!

Hehe, minjam kata-kata hekel πŸ˜„

Pada kesempatan ini, kita akan belajar tentang cara melindungi aplikasi web–terutama yang dibuat dengan Codeigniter–dari serangan XSS.

Selain itu, kita juga akan belajar melakukan serangan XSS agar tahu cara menghindarinya.

Baiklah..

Mari kita mulai!

Apa itu XSS?

XSS merupakan singkatan dari cross site scripting.

Alasan singkatan yang digunakan XSS bukan CSS, karena CSS sudah digunakan untuk cascade style sheet.

Jadi X untuk singkatan kata Cross.

XSS merupakan salah satu bentuk serangan pada aplikasi web yang dilakukan dengan menginjeksi kode javascript dari sisi client. 1

Dampak yang bisa diakbiatkan XSS:

  • Keamanan aplikasi web dapat di-bypass;
  • Data user dapat dicuri;
  • Halaman yang terkena XSS bisa dijadikan media Phising;
  • dan lain-lain.

Cara Mengatasi XSS di Codeigniter

“Untuk menangkap pencuri, kita harus berpikir seperti pencuri”

Mari kita pelajari bagaimana cara melakukan serangan XSS agar bisa menghindarinya.

1. Reflected XSS

Ini adalah jenis serangan XSS yang sering dilakukan attacker.

Biasanya terjadi pada input dari parameter query string yang kita tampilkan.

Contoh:

Misalkan kita punya fitur pencarian seperti ini:

<?php

class Search extends CI_Controller
{
	public function index()
	{
		$data["keyword"] = $this->input->get("keyword");

		// do search and render result to view
		$this->load->view('search', $data);
	}
}

Kemudian pada view, kita menampilkan $keyword yang diinputkan seperti ini:

<p>Hasil pencarian dengan kata kunci <?= $keyword ?></p>
<p>Hasil: ...</p>

Sekilas tidak ada masalah dengan kode ini.

Tapi coba inputkan keyword dengan kode Javascript seperti ini:

index.php/search/?keyword=<script>alert('hacked!')</script>

Maka hasilnya:

contoh hasil xss reflected

Bagaimana cara mengatasi serangan ini?

βœ… Solusinya:

Gunakan fungsi htmlentities() pada view untuk menampilkan variabel $keyword.

<p>Hasil pencarian dengan kata kunci <?= htmlentities($keyword) ?></p>
<p>Hasil: ...</p>

Maka sekarang halaman ini tidak akan bisa diserang XSS.

hasil fungsi htmlentities

Tapi jangan senang dulu..

Masih ada jenis serangan XSS yang lainnya:

2. Stored XSS

Stored XSS adalah serangan XSS yang dilakukan dengan memasukan injeksi XSS pada input yang disimpan ke database.

Sebenarnya tidak harus database, yang penting input itu disimpan di suatu tempat.

Nantinya, saat ditampilkan di halaman view.. kode injeksi XSS-nya akan dieksekusi.

Contohnya:

Misalkan kita membuat fitur untuk aplikasi lowongan pekerjaan. Input yang kita butuhkan untuk menambahkan lowongan baru sebagai berikut.

  • name nama atau judul lowongan kerja;
  • job_image_url alamat URL poster;
  • job_url alamat pendaftaran lowongan kerja.

Bentuk kode di Controller-nya seperti ini:

πŸ“œ controllers/Jobvacancy.php

<?php

class Jobvacancy extends CI_Controller
{
	public function addjob()
	{
		if ($this->input->method() === "post") {
			$data["job"] = $this->input->post();

			// di sini biasanya dilakukan penyimpanan ke database

			return $this->load->view("list_job", $data);
		}

		$this->load->view('add_job_form');
	}
}

..dan berikut ini kode untuk view-nya.

πŸ“œ views/list_job.php

<h1><?= $job["name"] ?></h1>
<img src='<?= $job["job_image_url"] ?>' />
<br>
<a href='<?= $job["job_url"] ?>'>Link Pendaftaran</a>

πŸ“œ views/add_job_form.php

<form action="" method="post">
	<label for="name">Vacancy Job name</label>
	<input type="text" name="name" >
	<br>
	<label for="job_image_url">Image URL</label>
	<input type="text" name="job_image_url">
	<br>
	<label for="job_url">Job URL</label>
	<input type="text" name="job_url">
	<br>
	<input type="submit" value="Simpan">
</form>

Ini hasilnya saat kita inputkan data yang benar.

hasil add job

Sekarang coba berikan input untuk name atau judul lokernya dengan kode javascript.

<script>alert('hacked')</script>

Untuk URL kita kosongkan aja dulu..

input xss-injection

Maka hasilnya:

hasil xss job vacancy

Serangan ini sama persis seperti XSS Reflected, bedanya injeksi pada seraingan ini akan disimpan di database.

Pada contoh di atas memang kita tidak menggunakan database. Tapi dalam prakteknya, pasti kita akan menyimpan ke database untuk input data ini.

Lalu solusi yang bisa kita lakukan apa?

βœ… Solusi:

Solusinya sama seperti XSS Reflected, yakni kita harus menampilkan output dengan fungsi htmlentities().

Maka pada view πŸ“œ list_job.php dapat kita ubah seperti ini:

<h1><?= htmlentities($job["name"]) ?></h1>
<img src='<?= htmlentities($job["job_image_url"]) ?>' />
<br>
<a href='<?= htmlentities($job["job_url"]) ?>'>Link Pendaftaran</a>

Hasilnya:

hasil proteksi xss

Mantap!

Tapi jangan senang dulu..

Karena ini masih belum aman.

Lah kenapa?

Kita patut waspadai output pada atribut src pada image dan href pada link.

Bisa saja mereka memberikan injeksi seperti ini:

x' onerror='alert("Hacked")'

Injeksi ini akan membuat atribut baru bernama onerror yang berfungsi untuk menjalankan kode javascript.

Atau kita bisa juga menggunakan onclick seperti ini:

#' click='alert("Hacked")'

Mari kita coba:

injeksi atribut

Hasilnya:

hasil injeksi xss atribut

Lihat kan.. belum 100% aman hehe.

Lalu giaman cara mengatasi ini?

βœ… Solusi:

Sebenarnya kita bisa gunakan double quote pada atribut HTML seperti ini:

<img src="<?= htmlentities($job["job_image_url"]) ?>" />
<a href="<?= htmlentities($job["job_url"]) ?>">Link Pendaftaran</a>

Hasilnya atribut akan seperti ini:

solusi double quote

Apakah sudah aman?

Belum! πŸ˜„

Percobaan ini saya lakukan di browser versi terbaru. Kemungkin di browser versi lama, akan tetap kena.

Terus solusinya bagaimana?

Kita bisa gunakan konstanta ENT_QUOTES pada parameter htmlentities() untuk melakukan enkode pada tanda petik. Baik yang ganda maupun yang tunggal.

Sehingga kode viewnya akan menjadi seperti ini:

<h1><?= htmlentities($job["name"]) ?></h1>
<img src="<?= htmlentities($job["job_image_url"], ENT_QUOTES) ?>" />
<br>
<a href="<?= htmlentities($job["job_url"], ENT_QUOTES) ?>">Link Pendaftaran</a>

Hasilnya bisa kita lihat di View Source.

hasil entquotes

Masalah injeksi tanda petik sudah beres.

Apakah sudah aman?

Belum! 🀣

Serangan injeksi berikutnya, saat user menginputkan:

javascript:alert('hacked')

Biasanya jika nilai ini diberikan pada link, maka browser akan menjalankan kode javascript yang diberikan.

Coba gunakan input tersebut.

injeksi url

Hasilnya:

hasil injeksi url

Kita sudah menggunakan htmlentities(), tapi yang ini bisa lolos.

Solusinya giaman donk?

βœ… Solusi:

Solusi yang bisa kita lakukan untuk mengatasi serangan ini adalah dengan melakukan validasi data. Baik di sisi client maupun server.

Pelajari juga:

Mari kita coba lakukan validasi di sisi server terlebih dahulu.

Ubah Controllernya menjadi seperti ini:

<?php

class Jobvacancy extends CI_Controller
{
	public function addjob()
	{
		$this->load->library('form_validation');
		if ($this->input->method() === "post") {
			$data["job"] = $this->input->post();

			$rules = [
				[
					'field' => 'name',
					'label' => 'Name',
					'rules' => ''
				],
				[
					'field' => 'job_image_url',
					'label' => 'Image URL',
					'rules' => 'valid_url'
				],
				[
					'field' => 'job_url',
					'label' => 'Job URL',
					'rules' => 'valid_url'
				],
			];

			$this->form_validation->set_rules($rules);
			if ($this->form_validation->run() == FALSE) {
				return $this->load->view('add_job_form');
			}

			// di sini biasanya dilakukan penyimpanan ke database

			return $this->load->view("list_job", $data);
		}

		$this->load->view('add_job_form');
	}
}

Kemudian ubah view untuk form-nya menjadi seperti ini:

πŸ“œ views/add_job_form.php

<form action="" method="post">
	<label for="name">Vacancy Job name</label>
	<input type="text" name="name" value="<?= htmlentities(set_value('name'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('name') ?></div>
	<br>
	<label for="job_image_url">Image URL</label>
	<input type="text" name="job_image_url" value="<?= htmlentities(set_value('job_image_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_image_url') ?></div>
	<br>
	<label for="job_url">Job URL</label>
	<input type="text" name="job_url" value="<?= htmlentities(set_value('job_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_url') ?></div>
	<br>
	<input type="submit" value="Simpan">
</form>

Hasilnya:

validasi input url

Mantap!

Dengan begini aman sudah.

Tinggal lakukan validasi di sisi client.

Validasi di sisi client bisa kita lakukan dengan atribut HTML. Kita bisa ubah nilai pada atribut type menjadi url dan juga menambahkan pattern untuk validasi dengan regex.

Silahkan pelajari juga:

Maka sekarang kita ubah kode view untuk form-nya menjadi seperti ini:

<form action="" method="post">
	<label for="name">Vacancy Job name</label>
	<input type="text" name="name" value="<?= htmlentities(set_value('name'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('name') ?></div>
	<br>
	<label for="job_image_url">Image URL</label>
	<input type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_image_url" value="<?= htmlentities(set_value('job_image_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_image_url') ?></div>
	<br>
	<label for="job_url">Job URL</label>
	<input type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_url" value="<?= htmlentities(set_value('job_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_url') ?></div>
	<br>
	<input type="submit" value="Simpan">
</form>

Hasilnya:

validasi sisi client

Kini saya rasa sudah aman..

Tapi jangan berpikir 100% akan aman, hehe.

Karena masih ada bentuk serangan XSS yang lainnya:

3. DOM Based XSS

DOM Based XSS adalah jenis serangan XSS yang injeksinya dijalankan di dalam DOM (Document Object Model).

Apa itu DOM?

Silahkan pelajari di:

Untuk mendemokan serangan ini, mari kita ubah view pada form add_job.php menjadi seperti ini:

<form action="" method="post">
	<label for="name">Vacancy Job name</label>
	<input type="text" name="name" value="<?= htmlentities(set_value('name'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('name') ?></div>
	<br>
	<label for="job_image_url">Image URL</label>
	<input onchange="previewImage(this)" type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_image_url" value="<?= htmlentities(set_value('job_image_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_image_url') ?></div>
	<div id="image-preview"></div>
	<br>
	<label for="job_url">Job URL</label>
	<input type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_url" value="<?= htmlentities(set_value('job_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_url') ?></div>
	<br>
	<input type="submit" value="Simpan">
</form>


<script>
function previewImage(event) {
	const previewContainer = document.querySelector('#image-preview');
	const imageURL = event.value
	previewContainer.innerHTML = `
		<img src="${imageURL}" height="200" width="200" />
	`;
}
</script>

Kita menambahkan fitur untuk preview image berdasarkan URL image yang diinputkan.

Jika URL yang diinputkan bernar, maka akan tampil seperti ini:

fitur preview image

Berdasarkan kode javascript yang kita buat. Kita menampilkan image dengan manipulasi DOM.

Ini tentunya akan membuat kita bisa melakukan DOM based XSS.

Mau bukti?

Silahkan inputkan injeksi XSS berikut di dalam URL image.

x" onerror="alert('hacked')"

Maka hasilnya:

hasil dom xss

Benar kan! πŸ˜†

Padahal kita sudah melakukan validasi di sisi client dan server.

Lalu gimana solusinya?

βœ… Solusi:

Solusi untuk menghindari DOM based XSS sebenarnya hampir sama seperti Reflected XSS.

Bedanya, DOM based XSS dilakukan di client atau di dalam Javascript.. bukan di dalam PHP atau kode server.

Namun, fungsi htmlentities() di javascript belum ada. Fungsi ini bisa kita buat secara manual, atau bisa juga memanfaatkan library seperti underscore.js dengan fungsi _.escape().

Ada juga library DOMPurify yang khusus untuk mengatasi XSS.

OWASP sendiri juga menawarkan library ESAPI untuk escape HTML di javascript.

Kamu bisa baca:

Solusi lain bisa dengan men-trigger validator HTML saat input field diinputkan.

Contohnya seperti ini:

<form id="form-add" action="" method="post">
	<label for="name">Vacancy Job name</label>
	<input type="text" name="name" value="<?= htmlentities(set_value('name'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('name') ?></div>
	<br>
	<label for="job_image_url">Image URL</label>
	<input onchange="previewImage(this)" type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_image_url" value="<?= htmlentities(set_value('job_image_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_image_url') ?></div>
	<div id="image-preview"></div>
	<br>
	<label for="job_url">Job URL</label>
	<input type="url" pattern="[(http(s)?):\/\/(www\.)[email protected]:%._\+~#=]{2,256}\.[a-z]{2,6}\b([[email protected]:%_\+.~#?&//=]*)" name="job_url" value="<?= htmlentities(set_value('job_url'), ENT_QUOTES) ?>">
	<div style="color: tomato;"><?= form_error('job_url') ?></div>
	<br>
	<input type="submit" value="Simpan">
</form>

<script type="text/javascript">
function previewImage(event) {
	const previewContainer = document.querySelector('#image-preview');
	const form = document.querySelector('#form-add');
	if(form.checkValidity()){
		const imageURL = event.value;
		previewContainer.innerHTML = `
			<img src="${imageURL}" height="200" width="200" />
		`;
	}
}
</script>

Maka preview image tidak akan ditampilkan selama nilai URL yang diberikan tidak valid.

mitigasi dom xss

4. Self-XSS

Teknik XSS ini sulit diatasi dari kode aplikasi. Karena dilakukan sendiri oleh user.

Misalnya gini:

Seorang Attacker melakukan sosial enginering ke pada pengguna market place.

Attacker: β€œHi, saya punya kode rahasia buat dapetin diskon 99%”

Korban: β€œBenarkah? gimana caranya?”

Attacker: β€œBuka aplikasinya dari Google Chrome di Laptop, masuk ke halaman checkout, Klik kanan, lalu pilih inspect element, Lalu masuk ke Console. Setelah itu, paste Kode ini **XSS SCRIPT**, setelah itu tekan Enter. ”

Korban: β€œWah boleh nih dicoba.”

Beberapa hari kemudian..

Korban: β€œKok saldo saya hilang 😭!!!”

Nah kira-kira begitulah skenario terjadinya Self XSS.

Injeksi XSS-nya dijalankan sendiri oleh user yang merupakan korban Social Engineering.

Lalu bagaimana cara mengatasinya?

βœ… Solusi:

Seperti yang saya bilang, Self XSS sulit diatasi dari kode aplikasi. Salah satu cara yang bisa dilakukan adalah memberikan edikasi kepada user agar tidak menjalankan kode apapun di dalam Console Javascript browser.

Kita bisa tiru cara facebook memberikan edukasi dengan menampilkan pesan ini di console Javascript.

facebook self xss

5. Mutated XSS (mXSS)

Teknik serangan XSS yang ini sangat jarang digunakan dan sulit untuk dideteksi. Karena baru ditemukan di tahun 2019.

Mutated XSS bisanya terjadi karena disebabkan adanya perbedaan cara browser menginterpretasi HTML.

Celah XSS ini ditemukan oleh Masato Kinugawa pada bulan Februri 2019.

Penjelasan cara kerjanya, bisa kamu tonton di video ini:

Sangat membingungkan bukan.

Celah ini ditemukan pada closure-library milik google dan tentunya sudah diperbaiki.

Akhir Kata..

Nah begitulah beberapa cara mitigasi atau menghindari serangan XSS attack di Codeigniter.

Intinya:

Saat menampilkan output, jangan lupa di encode atau escape teks yang mau ditampilkan.

Lalu jangan lupa lakukan validasi input juga.

Demo dari contoh di atas bisa kamu download di Github.

[🎁 Download Source]

Jika kamu punya tips keamanan lainnya, mari diskusi di kolom komentar.


  1. Wikipedia. 2021. Cross-site scripting. Diakses 13 Agustus 2021. ↩︎