Mengeraskan aplikasi Yii terhadap ancaman web paling umum: menangkal XSS dengan Html::encode dan escape otomatis, melindungi form dari CSRF, mencegah SQL injection lewat parameter binding, hashing password yang benar, serta memasang security headers untuk hardening production.

Fitur tidak berarti apa-apa jika aplikasi bisa dibobol. Di episode 9 dan 10 kalian sudah mengamankan autentikasi dan API — sekarang kita bicara tentang ancaman yang menyerang data dan browser user: XSS, CSRF, dan SQL injection. Ketiganya masuk dalam OWASP Top 10 dan bertanggung jawab atas mayoritas insiden keamanan web.
Kabar baiknya, Yii dibangun dengan pertahanan untuk ketiganya: escape output, CSRF token otomatis, dan parameter binding di semua lapisan query. Tapi pertahanan hanya bekerja jika kalian memahami di mana dan bagaimana memakainya dengan benar — pertahanan yang dipasang salah sama bahayanya dengan tidak dipasang. Di episode ini kita bedah semuanya dan mengeraskan production.
Cross-Site Scripting (XSS) terjadi ketika input user dirender sebagai HTML tanpa di-escape, sehingga attacker menyuntikkan JavaScript yang berjalan di browser korban — mencuri cookie, membajak session, atau menyamar sebagai user.
Serangan klasik: user mengetik <script>alert(document.cookie)</script> di field komentar. Jika dirender mentah, skrip itu dieksekusi setiap kali komentar dilihat.
// input user dirender tanpa escape
<p><?= $comment->body ?></p>use yii\helpers\Html;
<p><?= Html::encode($comment->body) ?></p>Html::encode mengubah <script> menjadi <script> — browser menampilkan sebagai teks, bukan mengeksekusinya. Yii menerapkan aturan ini dengan tegas:
| Konteks Output | Method yang Benar |
|---|---|
| Teks biasa dalam tag HTML | Html::encode($text) |
| Atribut HTML | Html::encode (atau Html::attributeEncode) |
| JavaScript | JANGAN pernah interpolasi; gunakan JSON + escape |
| URL | Html::encode(Url::to(...)) + validasi skema http/https |
Pengecualian yang harus disadari: jika kalian memang perlu menampilkan HTML (misal konten editor WYSIWYG), gunakan library sanitizer seperti ezyang/htmlpurifier dan validasi whitelist tag — jangan pernah langsung echo data user. Ini keputusan paling berisiko dalam pengembangan web.
Warning
GridView dan DetailView memformat nilai lewat format. Nilai default format adalah 'html' untuk teks — ini berarti nilai di-render apa adanya. Untuk teks dari user, set eksplisit 'format' => 'text' (atau 'ntext' untuk multiline) pada kolom yang menampilkan input user. Kesalahan klasik yang membuat DataTables tampil normal tapi XSS aktif.
Cross-Site Request Forgery (CSRF) menipu browser korban untuk mengirim request berbahaya ke aplikasi kalian saat korban sedang login. Contoh: attacker membuat halaman berisi <img src="https://app-kalian.com/user/delete?id=1"> — jika korban (yang sedang login) membuka halaman itu, browser mengirim GET dan akun terhapus.
Pertahanan CSRF: setiap request yang mengubah state (POST/PUT/DELETE) harus membawa token CSRF yang hanya diketahui aplikasi. Yii menangani ini secara default — enableCsrfValidation aktif, dan ActiveForm otomatis menyertakan token:
<form action="/post/create" method="post">
<input type="hidden" name="_csrf" value="<token acak>">
</form>Untuk request non-form (AJAX/fetch), sertakan token di header:
fetch('/api/post/delete', {
method: 'POST',
headers: {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
});Yii juga menyediakan yii\web\View::registerCsrfMetaTags() yang menambahkan <meta name="csrf-token"> untuk kebutuhan ini. Aturan singkat: jangan pernah menonaktifkan CSRF tanpa alasan yang sangat kuat (dan jika terpaksa untuk API, API kalian harus diamankan dengan mekanisme lain seperti token bearer — episode 18).
SQL injection menyuntikkan SQL berbahaya lewat input user. Kita sudah menyentuh ini di episode 5, tapi mari kita pertegas: Yii aman selama kalian memakai lapisan query yang benar.
$posts = Post::find()->where("title = '$title'")->all(); // buruk$posts = Post::find()->where(['title' => $title])->all(); // di-bind otomatis
$posts = Post::find()->where('title = :t', ['t' => $title])->all(); // binding eksplisitTiga zona bahaya dan cara amannya:
| Zona | Bahaya | Cara Aman |
|---|---|---|
Query builder / AR where | String mentah | Bentuk array / binding named parameter |
Raw SQL (createCommand) | Interpolasi | createCommand($sql, [':t' => $value]) |
| Order by / kolom dinamis | Interpolasi identifikator | Whitelist kolom, jangan pernah terima langsung dari user |
$count = Yii::$app->db->createCommand(
'SELECT COUNT(*) FROM posts WHERE title = :t',
[':t' => $title]
)->queryScalar();Rule of thumb: jika kalian pernah menulis tanda kutip string dalam SQL secara manual di PHP, berhenti dan pikirkan ulang — kemungkinan besar itu lubang injection.
Ini sudah dibahas di episode 9, tapi karena termasuk topik paling sering salah: jangan pernah menyimpan password dalam bentuk teks, MD5, atau SHA1. Yii menyediakan API yang benar:
use Yii;
$hash = Yii::$app->security->generatePasswordHash($password); // bcrypt
$ok = Yii::$app->security->validatePassword($password, $hash); // verifikasigeneratePasswordHash memakai bcrypt dengan cost yang dapat dikonfigurasi dan menyertakan salt otomatis. Ke depan, bcrypt tetap menjadi pilihan aman untuk sebagian besar aplikasi — dan Security component juga menyediakan hashData/validateData untuk integritas data.
Lapisan terakhir: memanfaatkan HTTP response headers untuk memberi tahu browser cara memperlakukan respons. Yii memasangnya lewat filter yii\filters\ContentSecurityPolicy atau via response config:
'components' => [
'response' => [
'headers' => [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'SAMEORIGIN',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()',
],
],
],| Header | Melindungi dari |
|---|---|
X-Content-Type-Options: nosniff | MIME sniffing yang bisa mengubah file upload jadi skrip |
X-Frame-Options: SAMEORIGIN | Clickjacking (halaman dipasang di iframe penyerang) |
Referrer-Policy | Kebocoran URL lewat referrer ke pihak ketiga |
Permissions-Policy | Fitur browser (kamera, mic) yang dipakai halaman |
Content-Security-Policy | Aturan ketat sumber skrip/aset — pertahanan XSS paling kuat |
Strict-Transport-Security | Paksa HTTPS (bila di belakang HTTPS) |
Warning
Content-Security-Policy sangat kuat tapi mudah salah konfigurasi — aturan yang terlalu ketat membuat halaman rusak (skrip inline diblokir), dan terlalu longgar membuatnya tidak berguna. Mulailah dengan mode report-only (Content-Security-Policy-Report-Only), kumpulkan log pelanggaran selama beberapa minggu, lalu aktifkan penuh setelah pola kalian jelas.
Mari satukan semuanya dalam checklist hardening production:
// 1. matikan debug & gii di production
'modules' => [],
'components' => [
'request' => [
'cookieValidationKey' => getenv('COOKIE_VALIDATION_KEY'),
'enableCsrfValidation' => true, // default; jangan dimatikan
'enableCookieValidation' => true, // validasi integritas cookie
],
'user' => [
'identityCookie' => [
'httpOnly' => true, // JS tidak bisa baca cookie
'secure' => true, // hanya lewat HTTPS
'sameSite' => 'Lax', // batasi pengiriman lintas situs
],
],
'session' => [
'cookieParams' => [
'httpOnly' => true,
'secure' => true,
'sameSite' => 'Lax',
],
],
'log' => [
'traceLevel' => 0, // jangan bocorkan stack trace
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
],Verifikasi final dengan cek headers dan behavior:
curl -sI https://app-kalian.com/ | grep -iE "x-content-type|x-frame|content-security|strict-transport"Jalankan juga audit otomatis — misalnya OWASP ZAP atau npx lighthouse — untuk melihat apakah ada celah yang terlewat. Hardening bukan acara sekali jalan: ia daftar periksa yang harus dijalankan ulang setiap rilis.
Inti yang harus dibawa pulang:
Html::encode; berhati-hati dengan format: html di GridView.X-CSRF-Token untuk AJAX; jangan matikan tanpa alasan kuat.Security::generatePasswordHash/validatePassword (bcrypt).httpOnly/secure/sameSite.Di episode 18 selanjutnya, kita memperdalam keamanan untuk API dan sistem terdistribusi: auth lanjutan & API security — JWT, OAuth2, API token dengan masa berlaku, refresh token, serta perlindungan endpoint login dari brute-force. Sampai jumpa di episode 18!