Belajar Yii - Security Best Practices
Series/Belajar Yii/Episode 17
Episode 17 of 27

Belajar Yii - Security Best Practices

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.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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.

XSS: Menangkal Skrip Jahat di Halaman

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.

Rentan XSS (JANGAN ditiru)
// input user dirender tanpa escape
<p><?= $comment->body ?></p>
Aman: escape dengan Html::encode
use yii\helpers\Html;
 
<p><?= Html::encode($comment->body) ?></p>

Html::encode mengubah <script> menjadi &lt;script&gt; — browser menampilkan sebagai teks, bukan mengeksekusinya. Yii menerapkan aturan ini dengan tegas:

Konteks OutputMethod yang Benar
Teks biasa dalam tag HTMLHtml::encode($text)
Atribut HTMLHtml::encode (atau Html::attributeEncode)
JavaScriptJANGAN pernah interpolasi; gunakan JSON + escape
URLHtml::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.

CSRF: Melindungi State-Changing Request

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 defaultenableCsrfValidation aktif, dan ActiveForm otomatis menyertakan token:

HTMLToken CSRF di form
<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:

JSCSRF token untuk AJAX
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: Query yang Aman

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.

Rentan SQL injection (JANGAN ditiru)
$posts = Post::find()->where("title = '$title'")->all(); // buruk
Aman: bentuk array dan binding
$posts = Post::find()->where(['title' => $title])->all();      // di-bind otomatis
$posts = Post::find()->where('title = :t', ['t' => $title])->all(); // binding eksplisit

Tiga zona bahaya dan cara amannya:

ZonaBahayaCara Aman
Query builder / AR whereString mentahBentuk array / binding named parameter
Raw SQL (createCommand)InterpolasicreateCommand($sql, [':t' => $value])
Order by / kolom dinamisInterpolasi identifikatorWhitelist kolom, jangan pernah terima langsung dari user
Raw SQL yang aman
$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.

Password Hashing

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:

Hashing yang benar
use Yii;
 
$hash = Yii::$app->security->generatePasswordHash($password);   // bcrypt
$ok = Yii::$app->security->validatePassword($password, $hash);  // verifikasi

generatePasswordHash 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.

Security Headers

Lapisan terakhir: memanfaatkan HTTP response headers untuk memberi tahu browser cara memperlakukan respons. Yii memasangnya lewat filter yii\filters\ContentSecurityPolicy atau via response config:

config/web.php - security headers
'components' => [
    'response' => [
        'headers' => [
            'X-Content-Type-Options' => 'nosniff',
            'X-Frame-Options' => 'SAMEORIGIN',
            'Referrer-Policy' => 'strict-origin-when-cross-origin',
            'Permissions-Policy' => 'camera=(), microphone=(), geolocation=()',
        ],
    ],
],
HeaderMelindungi dari
X-Content-Type-Options: nosniffMIME sniffing yang bisa mengubah file upload jadi skrip
X-Frame-Options: SAMEORIGINClickjacking (halaman dipasang di iframe penyerang)
Referrer-PolicyKebocoran URL lewat referrer ke pihak ketiga
Permissions-PolicyFitur browser (kamera, mic) yang dipakai halaman
Content-Security-PolicyAturan ketat sumber skrip/aset — pertahanan XSS paling kuat
Strict-Transport-SecurityPaksa 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.

Praktik: Hardening Production

Mari satukan semuanya dalam checklist hardening production:

config/web.php - pengaturan keamanan 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:

Cek security headers
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.

Penutup

Inti yang harus dibawa pulang:

  • XSS: escape output dengan Html::encode; berhati-hati dengan format: html di GridView.
  • CSRF: token otomatis di ActiveForm; sertakan X-CSRF-Token untuk AJAX; jangan matikan tanpa alasan kuat.
  • SQL injection: selalu parameter binding (array/named); whitelist untuk kolom dinamis.
  • Password: Security::generatePasswordHash/validatePassword (bcrypt).
  • Security headers: nosniff, frame-options, CSP, HSTS, dan cookie flags 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!

Belajar Yii - Security Best Practices | Belajar Yii