Membangun Website PHP Aman Tanpa Framework

Framework menyediakan default aman, tetapi PHP native tetap dapat dibuat defensible jika aturan keamanan menjadi bagian arsitektur. Contoh berikut mengambil pola dari situs ini tanpa menganggapnya sempurna.
1. Session sebelum autentikasi#
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', $isHttps ? '1' : '0');
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', '1');
session_start();
// Setelah login berhasil
session_regenerate_id(true);
Tambahkan idle timeout dan authorization check pada setiap endpoint admin, bukan hanya menyembunyikan link.
2. CSRF untuk request yang mengubah state#
function csrf_token(): string {
return $_SESSION['csrf'] ??= bin2hex(random_bytes(32));
}
function verify_csrf(mixed $value): bool {
return is_string($value)
&& isset($_SESSION['csrf'])
&& hash_equals($_SESSION['csrf'], $value);
}
Validasi tipe penting: request _csrf[]=x tidak boleh membuat hash_equals() crash. Panduan OWASP CSRF merekomendasikan token pada semua state-changing request berbasis cookie.
3. Prepared statement#
$stmt = $pdo->prepare(
'SELECT id, password_hash FROM admin WHERE username = ?'
);
$stmt->execute([$username]);
Whitelist diperlukan untuk identifier dinamis seperti nama kolom; placeholder hanya melindungi value.
4. Output encoding sesuai konteks#
<h1><?= htmlspecialchars($title, ENT_QUOTES, 'UTF-8') ?></h1>
<a href="<?= htmlspecialchars($validatedUrl, ENT_QUOTES, 'UTF-8') ?>">
HTML, attribute, URL, JavaScript, dan CSS adalah konteks berbeda. Jangan memakai satu fungsi “sanitize input” lalu menganggap data aman untuk semua output.
5. Upload validation#
- Periksa error dan ukuran.
- Pastikan sumber benar-benar uploaded file.
- Deteksi MIME dari content, bukan ekstensi.
- Untuk gambar, parse dengan library gambar.
- Buat nama acak; jangan pakai nama user.
- Simpan di lokasi non-executable.
6. CSP sebagai lapisan tambahan#
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-RANDOM';
object-src 'none';
base-uri 'none';
frame-ancestors 'self';
OWASP CSP menempatkan CSP sebagai defense in depth, bukan pengganti output encoding dan sanitization.
7. Rate limiting#
Limit berdasarkan hash IP, account identifier, dan waktu. Jangan hanya menyimpan counter di session karena attacker dapat membuat session baru. Berikan respons generik agar username valid tidak dapat dienumerasi.
Definition of done#
- Input array/malformed tidak menghasilkan 500.
- Semua POST punya CSRF dan authorization.
- Session ID berganti saat privilege berubah.
- Upload tidak dapat dieksekusi.
- Secret dan backup di luar document root.
- Error detail tidak tampil di production.
- Security header diuji, bukan hanya ditulis.
Validasi bentuk input#
PHP mengubah name[]=x menjadi array. Fungsi yang mengharapkan string dapat melempar TypeError. Buat satu boundary helper dan jangan memanggil trim($_GET['q']) langsung.
function request_string(array $source, string $key, string $default=''): string {
if (!array_key_exists($key, $source) || !is_scalar($source[$key])) {
return $default;
}
return (string) $source[$key];
}
Error handling production#
Matikan display_errors, aktifkan structured logging, dan berikan correlation ID. Status harus benar: validation 400/422, unauthorized 401/403, not found 404, dependency unavailable 503. Jangan mengembalikan 200 berisi fatal error.
Threat-model halaman#
| Fitur | Risiko utama | Kontrol |
|---|---|---|
| Login | Brute force/session fixation | Rate limit, regenerate ID |
| Rich text | Stored XSS | Allowlist sanitizer, CSP |
| Upload | Web shell/polyglot | MIME parse, random name, non-executable |
| Admin action | CSRF/IDOR | Token + authorization per object |
Security review menjadi lebih jelas ketika setiap route memiliki asset, actor, trust boundary, dan failure mode.
💬 Comments (0)
No comments yet. Be the first to share your thoughts!