Checklist autentikasi pada section sebelumnya menutup satu pintu masuk; sayangnya aplikasi memiliki banyak pintu lain: form kontak, parameter URL, upload berkas, hingga data dari API pihak ketiga. Section ini membedah ancaman web satu per satu — XSS, CSRF, SQL injection — tetapi sebelum itu kita perlengkapi dulu cara berpikirnya. Prinsip secure coding bukan fitur yang bisa diinstall: ia kebiasaan berpikir yang diterapkan di setiap baris kode, sehingga ketika serangan yang belum kita kenal datang, kebiasaan itu tetap melindungi aplikasi. Delapan prinsip berikut masing-masing kita buktikan dengan contoh kode kecil yang dapat diuji — bukan sekadar slogan.

Validasi Input di Pintu Masuk #

Semua data dari luar — $_GET, $_POST, header, cookie, isi berkas, respons API — diperlakukan sebagai asing sampai terbukti sesuai harapan. Validasi menjawab satu pertanyaan: “apakah data ini persis seperti yang saya perkirakan?”. Cara paling kokoh adalah allowlist: daftar nilai atau bentuk yang diterima, segalanya di luar daerah itu ditolak. Lawannya, blocklist (menolak yang diketahui buruk), selalu kalah karena penyerang tak pernah kehabisan variasi baru:

php
<?php
function pilihKategori(string $input): string
{
    // Allowlist: hanya nilai eksplisit yang dikenal dan diterima.
    $kategoriSah = ["makanan", "minuman", "snack"];

    if (!in_array($input, $kategoriSah, true)) {
        return "Input ditolak: nilai tidak dikenal";
    }

    return "Input diterima: $input";
}

foreach (["makanan", "makanan' OR 1=1 --", "<script>alert(1)</script>"] as $input) {
    echo pilihKategori($input) . "\n";
}

Output yang Dihasilkan:

text
Input diterima: makanan
Input ditolak: nilai tidak dikenal
Input ditolak: nilai tidak dikenal

Perhatikan ironi pada output: dua input terakhir adalah payload SQL injection dan XSS, dan keduanya gugur tanpa satu pun logika khusus anti-injection atau anti-XSS. Allowlist tidak pernah tahu serangan apa yang sedang trend — ia hanya tahu bentuk data yang sah, dan cukup itu untuk menolak semua yang lain. Untuk data yang bentuknya lebih kaya (email, angka, tanggal), PHP menyediakan filter_var() dan validasi di sisi server — dibahas tuntas pada chapter Validasi Server-Side , termasuk perbedaan validasi dan sanitasi pada chapter Sanitasi vs Validasi .

Escape Output Sesuai Konteks #

Validasi menjaga pintu masuk; escaping menjaga pintu keluar — saat data akhirnya digabungkan ke HTML, JavaScript, atau URL untuk dikirim ke browser. Data yang sah pun bisa berbahaya di konteks yang salah: nama Budi" onmouseover="alert(1) lolos validasi “nama boleh berisi tanda kutip”, tetapi merusak halaman bila ditanam mentah ke atribut HTML:

php
<?php
$namaLayar = 'Budi" onmouseover="alert(1)';

// Satu-satunya tempat data ini menyentuh HTML: di batas output.
echo "Selamat datang, " . htmlspecialchars($namaLayar, ENT_QUOTES, "UTF-8") . "!\n";

Output yang Dihasilkan:

text
Selamat datang, Budi&quot; onmouseover=&quot;alert(1)!

Kata “konteks” adalah kata kuncinya: aturan escape untuk badan HTML berbeda dengan atribut HTML, JavaScript, dan URL — tidak ada satu fungsi ajaib untuk semuanya. Kita akan bedah tuntas aturan tiap konteks pada chapter Output Escaping , lalu melihat konsekuensi melanggarnya pada chapter Cross-Site Scripting .

Least Privilege: Hak Minimal yang Menyelesaikan Pekerjaan #

Setiap komponen — user database, proses server, token API, peran pengguna — sebaiknya hanya membawa hak yang benar-benar diperlukannya. Alasannya sederhana: kesalahan atau pembobolan pada satu komponen seharusnya tidak langsung memberi penyerang seluruh kerajaan. Aplikasi yang membaca-tulis tabel pesanan tidak butuh hak menghapus tabel; skrip backup tidak butuh hak menulis:

sql
-- User database khusus aplikasi: hanya hak yang benar-benar dipakai.
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'YOUR_DATABASE_PASSWORD';
GRANT SELECT, INSERT, UPDATE ON toko.pesanan TO 'webapp'@'localhost';
GRANT SELECT ON toko.produk TO 'webapp'@'localhost';
-- Tidak ada DROP, tidak ada ALTER, tidak ada akses ke tabel users.

Prinsip yang sama berlapis ke atasnya: token API dibatasi scope (seperti posts:read pada chapter API Token), pengguna dibatasi peran, dan proses PHP sebaiknya tidak berjalan sebagai root. Pemodelan peran dan permission dibahas pada chapter Role-Based Access Control .

Defense in Depth: Pertahanan Berlapis #

Asumsikan satu lapisan pertahanan pasti bisa ditembus suatu hari — karena software punya bug, konfigurasi punya kesalahan, dan manusia punya lelah. Defense in depth menumpuk pertahanan yang saling independen sehingga satu keberhasilan penyerang tidak otomatis menjadi keberhasilan berikutnya:

LapisanAncaman yang diredamBahasan
Validasi inputData janggal masuk sistemValidasi Server-Side
Prepared statementSQL injectionSQL Injection
Escaping outputXSSOutput Escaping
Cookie httponly + CSPPerluasan dampak XSSCookie Options, Content Security Policy
Rate limitingPercobaan masuk masalAuthentication Security

Contoh nyatanya: XSS terjadi di satu halaman, tetapi cookie bertanda httponly membuat JavaScript penyerang tidak bisa mencuri session, dan CSP memblokir pemuatan script dari domain asing — kerusakan terhenti di lapisan kedua dan ketiga.

Fail Securely: Gagal ke Arah Menolak #

Jalur kode yang gagal — data hilang, konfigurasi tak terbaca, layanan eksternal mati — jarang diuji, padahal penyerang senang mencari tepatannya. Aturannya: ketika ragu, tolak. Bandingkan dua gaya pemeriksaan hak akses berikut:

php
<?php
function bolehKelolaArtikel(array $user): bool
{
    // Tolak secara bawaan: hanya peran eksplisit "editor" yang lolos.
    return ($user["peran"] ?? null) === "editor";
}

var_dump(bolehKelolaArtikel(["peran" => "editor"]));
var_dump(bolehKelolaArtikel(["peran" => "pelanggan"]));
var_dump(bolehKelolaArtikel([]));
var_dump(bolehKelolaArtikel(["peran" => true]));

Output yang Dihasilkan:

text
bool(true)
bool(false)
bool(false)
bool(false)

Fungsi di atas gagal ke arah menolak lewat tiga mekanisme sekaligus: operator ?? mengembalikan null (bukan error) saat kunci peran hilang, perbandingan === menolak nilai dengan tipe janggal seperti true, dan tak ada cabang “default boleh”. Bayangkan versi kebalikannya — return $user["peran"] !== "pelanggan"; — yang meloloskan data hilang dan tipe janggal sebagai admin. Prinsip yang sama berlaku untuk kegagalan sistem: bila berkas konfigurasi gagal dimuat, tampilkan halaman galat dan berhenti; jangan melanjutkan dengan asumsi “mungkin aman”.

Jangan Implementasikan Kriptografi Sendiri #

PHP sudah menyediakan blok bangunan kriptografi yang sudah diuji puluhan tahun: password_hash(), random_bytes(), hash_hmac(), OpenSSL. Menulis algoritma sendiri hampir selalu menghasilkan sesuatu yang terlihat rumit tetapi ambrum di kriteria yang tak terpikirkan penulisnya — integritas data, pola yang bocor, kunci yang lemah. Lihat “cipher” buatan sendiri berikut:

php
<?php
function xorCipher(string $data, string $kunci): string
{
    $hasil = "";
    for ($i = 0; $i < strlen($data); $i++) {
        $hasil .= $data[$i] ^ $kunci[$i % strlen($kunci)];
    }

    return $hasil;
}

$sandiTersimpan = xorCipher("Halo Budi", "k");

echo "Teks sandi (hex)               : " . bin2hex($sandiTersimpan) . "\n";
echo "Dikembalikan dengan kunci sama : " . xorCipher($sandiTersimpan, "k") . "\n";
echo "Pola bocor, 'aaaa' jadi        : " . bin2hex(xorCipher("aaaa", "k")) . "\n";

Output yang Dihasilkan:

text
Teks sandi (hex)               : 230a07044b291e0f02
Dikembalikan dengan kunci sama : Halo Budi
Pola bocor, 'aaaa' jadi        : 0a0a0a0a

Tiga baris output itu adalah tiga pengaduan sekaligus. Pertama, “menyandi” dan “mengembalikan” hanya memakai operasi ^ yang sama — siapa pun yang tahu caranya bisa membuka datanya. Kedua, aaaa menjadi empat byte sandi yang persis identik (0a0a0a0a): huruf plaintext yang sama selalu menghasilkan byte sandi yang sama, sehingga pola teks asli terbaca dari teks sandi — inilah mengapa mode ECB dan XOR kunci pendek dilarang. Ketiga, tidak ada mekanisme integritas: penyerang bisa mengubah teks sandi tanpa terdeteksi. password_hash() untuk kata sandi, random_bytes() untuk token, OpenSSL untuk enkripsi — biarkan kriptografi pada para ahlinya.

Perbarui Dependensi Secara Berkala #

Sebagian besar aplikasi PHP modern berdiri di atas framework dan puluhan package — dan kerentanan justru sering ditemukan di lapisan itu, bukan di kode kita. Package yang sehat hari ini bisa ditemukan celahnya enam bulan lagi, sehingga “sudah aman saat instalasi” tidak pernah menjadi status permanen. Jadikan pemeriksaan bagian rutin dari alur kerja:

bash
composer outdated --direct
composer audit

Keluaran composer audit akan menyebutkan paket, nomor CVE, dan tingkat keparahannya bila ada yang terdampak — perbarui segera, utamakan yang berlabel critical. Proses mengelola dependensi secara aman (versi, lock file, audit di CI) dibahas tuntas pada chapter Dependency Security .

Catat untuk Mendeteksi, Bukan Sekadar Mengingat #

Prinsip terakhir bersifat pasca-insiden: ketika sesuatu menembus semua lapisan, log adalah satu-satunya saksi. Pencatatan yang berguna bersifat terstruktur — level, pesang, konteks berupa fakta yang bisa ditelusuri — dan disiplin tentang apa yang tidak boleh masuk:

php
<?php
function catatKejadian(string $level, string $pesan, array $konteks): string
{
    // Konteks hanya berisi fakta yang aman — tanpa password atau token.
    return sprintf("%s: %s %s", strtoupper($level), $pesan, json_encode($konteks));
}

echo catatKejadian("warning", "input ditolak validasi", ["field" => "email", "alasan" => "format"]), "\n";
echo catatKejadian("info", "login berhasil", ["user_id" => 7]), "\n";

Output yang Dihasilkan:

text
WARNING: input ditolak validasi {"field":"email","alasan":"format"}
INFO: login berhasil {"user_id":7}

Baris WARNING mencatat percobaan input janggal — bila pola ini berulang dari satu IP, itu sinyal probing yang layak diblokir; baris INFO mencatat kejadian normal sebagai jejak audit. Bungkus pola ini dengan logger sungguhan di produksi (error_log() untuk memulai, monolog untuk skala besar) dan pantau aliran lognya, jangan hanya menimbunnya.

Referensi #

Dokumentasi resmi untuk pendalaman lebih lanjut:

Ringkasan #

Delapan prinsip ini — validasi input, escape output, least privilege, defense in depth, fail securely, kriptografi dari pabrik, dependensi terbarui, dan pencatatan — adalah benteng yang bekerja bahkan sebelum kita mengenal nama satu pun serangan. Prinsip escape output yang baru sekilas kita lihat adalah yang paling sering dilanggar di aplikasi PHP dunia nyata, dan pelanggarannya bernama Cross-Site Scripting. Chapter berikutnya membedah tuntas aturan escaping per konteks — HTML, atribut, JavaScript, dan URL — lengkap dengan fungsi PHP yang tepat untuk masing-masing.