Cegah XSS dengan Content-Security-Policy

Cegah XSS dengan Content-Security-Policy

Kemarin lusa (1/22), saya menerima laporan dari admin website kementerian yang saya buat kalau webnya mengalami kendala hanya menampilkan preloader saja. Setelah saya cek melalui devtools browser, pada bagian console, saya mendapati kalau browser tidak diizinkan untuk me-load javascript, css, maupun font yang berasal dari cdnjs dan unpkg. Alhasil sebagian script yang dibutuhkan tidak di-load, yaudah, rusak deh. Kurang lebih penampakan errornya (di Firefox) kayak gini:

CSP Error

Ternyata sebelumnya dari pihak IT pusat sana, web servernya baru disetting untuk mengirimkan header Content-Security-Policy yang berisikan whitelist host mana saja yang diperbolehkan untuk di-load oleh website-website yang berada pada server naungan mereka, dan cdnjs.cloudflare.com serta unpkg.com tidak terdaftar dalam whitelist tersebut. Karena saya cuma dikasih akses ke sebagian fitur cpanel-nya aja, alhasil yang saya lakukan adalah mengunduh semua resource pada CDN, dan mengunggahnya ke server mereka, lalu mengubah kode HTML dari yang tadinya load ke URL CDN menjadi load ke URL website itu sendiri.

Apa yang dilakukan IT pihak pusat memang sudah benar, yaitu mencegah potensi XSS dari berbagai website yang dikembangkan berbagai developer (dan vendor) yang hosting websitenya ke server mereka, dimana jika ada salah satu website saja vuln, maka website lain bisa terpengaruh karenanya.

Setelah kejadian ini saya baca-baca lagi tentang Content-Security-Policy (CSP), dan supaya menjadi manfaat karena blog saya udah lama ga update, saya tulis sekalian disini.

Setelah ini saya akan singkat Content-Security-Policy menjadi CSP saja.

Apa itu CSP?

CSP adalah HTTP Response Header atau Meta Tag untuk mengatur resource dari sumber mana saja yang diperbolehkan untuk dieksekusi oleh web browser (yang mendukung CSP tentunya).

Yang dimaksud resource disini itu ya web resource, seperti javascript, css, font, image, video, audio, dsb.

Dengan CSP kita dapat meminimalisir potensi-potensi serangan XSS pada website kita dengan memerintahkan browser untuk jangan me-load dan mengeksekusi resource dari sumber yang tidak kita inginkan.

Apa yang Bisa Kita Lakukan dengan CSP?

Dengan memberikan CSP ke halaman web kamu, kamu dapat:

  • Mencegah browser untuk mengeksekusi inline script (javascript di HTML).
  • Mencegah browser untuk mengeksekusi eval.
  • Mencegah browser untuk mengeksekusi inline style.
  • Mencegah browser untuk mengeksekusi script, css, font, gambar, video, suara, dsb diluar host yang kita tentukan.
  • Mencegah browser untuk submisi form yang action-nya menuju ke host yang tidak kita daftarkan.
  • Mencegah browser untuk menjalankan Ajax, maupun WebSocket diluar host yang kita tentukan.
  • dll.

Bagaimana CSP dapat Mencegah XSS?

Serangan XSS umumnya berasal dari form yang disubmit oleh user, yang kemudian isian dari form tersebut di-render ke user lain sebagai kode HTML (tidak di-escaping).

Contoh sederhananya adalah form komentar. Misalkan ada user memasukkan kode <script>alert('oi')</script> pada kolom komentar, dan server-side tidak melakukan escaping terhadap data komentar tersebut, yang terjadi saat komentar tersebut di-render ke user lain adalah browser akan mengeksekusi inline script-nya dan menampilkan alert ke user.

Dengan CSP, kita dapat menghindari hal ini dengan cara memerintahkan browser untuk jangan eksekusi inline script, melalui response header ataupun meta tag si CSP. Jadi sekalipun developer khilaf tidak melakukan escaping, serangan XSS dapat dihindari.

Benefitnya akan sangat terasa kalau kamu punya website atau sedang mengelola server yang memiliki puluhan sampai ratusan halaman yang menampilkan data hasil inputan form didalamnya. Dengan CSP kamu cukup setting http server kamu (nginx/apache/dsb) untuk mengirimkan header CSP, dan boom! kamu tidak perlu lagi khawatir tentang XSS. Tapi sebelum melakukan ini, lebih baik kamu komunikasikan dulu hal ini ke tim developer, supaya ga ada kejadian seperti saya yang mengalami error saat production karena menggunakan resource dari sumber yang tidak "direstui".

Penulisan CSP

Seperti yang sudah saya bilang sebelumnya, CSP dapat kita tuliskan pada response header, ataupun meta tag. Hanya saja, artikel yang saya baca di halaman Google Developers lebih menyarankan menggunakan response header. Jadi disini saya akan mencontohkan menggunakan response header saja.

Untuk penulisan header CSP, formatnya adalah sebagai berikut:

Content-Security-Policy: <policy-directive-1>; <policy-directive-2>; <policy-directive-n>

Sedangkan untuk penulisan <policy-directive> formatnya adalah seperti ini:

<directive-a> <source-1> <source-2> <source-n>

Sebagai contoh, jika kita hanya memperbolehkan load script dan css dari web-a.com dan web-b.com, kita dapat menuliskan CSP seperti ini:

Content-Security-Policy: script-src https://web-a.com https://web-b.com; style-src https://web-a.com https://web-b.com

Pada contoh diatas kita menggunakan directive script-src dan style-src. Dibawah ini adalah daftar beberapa directive yang dapat kamu gunakan:

  • connect-src: membatasi akses ajax (XMLHttpRequest), WebSocket, dan EventSource.
  • font-src: membatasi akses font.
  • img-src: membatasi akses image.
  • script-src: membatasi akses script.
  • style-src: membatasi akses style (css).
  • media-src: membatasi akses video dan audio.
  • worker-src: membatasi akses service worker.
  • child-src: membatasi akses iframe dan worker.
  • form-action: membatasi action dari form.
  • default-src: kalau kamu malas jabarkan satu-persatu, kamu bisa gunakan ini untuk membatasi semua hal diatas.

Daftar diatas bisa saja bertambah atau berubah suatu saat, untuk itu, selebihnya bisa kamu pelajari sendiri pada artikel CSP di Google Developers atau panduan CSP di MDN.

Selanjutnya, selain mendaftarkan host apa saja yang diperbolehkan, CSP juga memiliki beberapa keyword yang dapat kamu gunakan sebagai <source> seperti dibawah ini:

  • 'self': untuk memperbolehkan browser load resource dari domain yang sama dengan website di-load.
  • 'unsafe-inline': untuk memperbolehkan browser mengeksekusi inline script ataupun inline style.
  • 'unsafe-eval': untuk memperbolehkan browser mengeksekusi fungsi javascript eval.
  • 'none': jika kamu tidak memperbolehkan browser load resource dari sumber manapun.

Keyword diatas harus kamu tulis dengan menyertakan tanda petik tunggal.

Contoh Penerapan CSP

Pada bagian ini saya menuliskan contoh-contoh CSP umum yang mungkin banyak orang butuhkan. Disini dimisalkan saya host halaman-halaman contoh ini pada https://www.emsifa.com.

Disini saya akan menggunakan PHP supaya lebih ringkas aja.

1. Memperbolehkan Semua Jenis Resource dari Domain yang Sama

Dari kebutuhan diatas, kita dapat terjemahkan menjadi:

  • Semua Jenis Resource: default-src
  • Dari Domain yang Sama: 'self'

Jadi penerapan CSP-nya adalah sebagai berikut:

<?php

header("Content-Security-Policy: default-src 'self'");

?>
<html>
<body>
  <script src="https://www.emsifa.com/js/app.js"></script>
  <script src="https://cdnjs.cloudflare.com/jquery/jquery.min.js"></script>
  <script>alert('hello')</script>
</body>
</html>

Pada contoh diatas, script app.js akan di-load dan dieksekusi oleh browser, sedangkan script jQuery tidak akan dieksekusi karena berada pada host cdnjs.cloudflare.com, dan script alert('hello') dibawahnya juga tidak akan dieksekusi karena kita tidak mendaftarkan 'unsafe-inline' pada header CSP.

2. Memperbolehkan Semua Jenis Resource dari Sumber yang Sama dan CDN.js

Dari kebutuhan diatas, kalau kita terjemahkan kedalam CSP, menjadi:

  • Semua Jenis Resource: default-src
  • Sumber yang sama dan CDN.js: 'self' https://cdnjs.cloudflare.com

Kalau kita terapkan ke CSP akan menjadi:

<?php

header("Content-Security-Policy: default-src 'self' https://cdnjs.cloudflare.com");

?>
<html>
<body>
  <script src="https://www.emsifa.com/js/app.js"></script>
  <script src="https://cdnjs.cloudflare.com/jquery/jquery.min.js"></script>
  <script>alert('hello')</script>
</body>
</html>

Pada contoh diatas, app.js dan jQuery akan di-load oleh browser, sedangkan script alert('hello') tetap diblokir oleh browser.

3. Hanya Memperbolehkan Script, CSS, dan Image dari Hosting Sendiri, tidak Memperbolehkan Video dan Audio Apapun, tapi memperbolehkan Embed Video Youtube

Oke, kita coba terjemahkan dulu kebutuhan diatas:

  • Hanya Memperbolehkan Script CSS dan Image dari Hosting sendiri: artinya kita memerlukan 3 buah directive yaitu script-src, style-src, dan img-src yang memperbolehkan 'self'.
  • Tidak memperbolehkan Video dan Audio Apapun: artinya kita perlu menuliskan directive media-src menjadi 'none'.
  • Tapi memperbolehkan Embed Video Youtube: artinya kita perlu menuliskan directive child-src ke https://youtube.com.

Kalau kita terapkan ke header CSP menjadi seperti ini:

<?php

header("Content-Security-Policy: script-src 'self'; style-src 'self'; img-src: 'self'; media-src 'none'; child-src https://youtube.com");

?>
<html>
<head>
  <link rel="stylesheet" href="https://www.emsifa.com/css/style.css"/>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/bootstrap/css/bootstrap.min.css"/>
  <style>
    img { width: 100px; }
  </style>
</head>
<body>
  <img src="https://www.emsifa.com/img/logo.png"/>
  <img src="https://www.w3schools.com/img/logo.png"/>

  <iframe width="560" height="315" src="https://www.youtube.com/embed/pTGRpH2dvRM" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

  <script src="https://www.emsifa.com/js/app.js"></script>
  <script src="https://cdnjs.cloudflare.com/jquery/jquery.min.js"></script>
  <script>alert('hello')</script>
</body>
</html>

Dari seluruh resource pada kode HTML diatas, hasilnya adalah seperti ini:

  1. style https://www.emsifa.com/css/style.css: ✔.
  2. style https://cdnjs.cloudflare.com/bootstrap/css/bootstrap.min.css: ❌.
  3. inline style img {width: 100px}: ❌.
  4. image https://www.emsifa.com/img/logo.png: ✔.
  5. image https://www.w3schools.com/img/logo.png: ❌.
  6. iframe https://www.youtube.com/embed/pTGRpH2dvRM: ✔.
  7. script https://www.emsifa.com/js/app.js: ✔.
  8. script https://cdnjs.cloudflare.com/jquery/jquery.min.js: ❌.
  9. inline script alert('hello'): ❌.

Penutup

Jadi seperti itulah CSP, bagaimana penulisannya, serta beberapa contohnya. Artikel ini saya adaptasi dari 2 link dibawah ini:

Yaudah, sekian dulu artikel kali ini. Semoga bermanfaat, dah ~

Suka artikel ini?

Saya biasanya share artikel-artikel terbaru via facebook atau fanpage foobarology saya. Kalau mau dapat updatenya, di add friend/like/follow aja link-link dibawah ini 😃

Facebook Foobarology