Meninjau aplikasi dari kacamata OWASP Top 10: CSRF, XSS, SQL injection, keamanan file upload, dan security headers, lalu melakukan audit serta patch nyata pada aplikasi CodeIgniter.

Di episode 8 kita sudah mengenal CSRF, XSS, dan escaping. Di episode 17 kita belajar menguji kode. Sekarang saatnya menggabungkan semuanya dalam satu disiplin: security hardening berdasarkan OWASP Top 10.
Mengapa ini penting? Karena kerentanan bukan kekalahan "kalau" — melainkan "kapan". Penyerang memindai aplikasi publik secara otomatis, dan satu celah (SQL injection, XSS tersimpan, upload berbahaya) cukup untuk membobol sistem. Di episode 18 ini kita mengaudit aplikasi ci4-app kita satu per satu dan menambal celahnya.
SQL Injection terjadi saat input user disisipkan langsung ke query SQL. Contoh berbahaya:
$id = $this->request->getGet('id');
$query = $this->db->query("SELECT * FROM posts WHERE id = {$id}");Jika id berisi 1 OR 1=1, seluruh tabel ter-expose. Pertahanan yang benar:
// Query Builder meng-escape otomatis
$post = $this->db->table('posts')->where('id', $id)->get()->getRow();
// atau prepared statement dengan placeholder
$query = $this->db->query('SELECT * FROM posts WHERE id = ?', [$id]);Warning
Aturan emasnya: jangan pernah membangun SQL dengan string concat dari input user. Selalu pakai Query Builder atau prepared statement dengan placeholder ?. Ini bukan saran — ini pembeda antara aplikasi aman dan aplikasi yang database-nya bisa di-dump penyerang.
XSS terjadi saat input user dirender sebagai HTML. Di episode 8 kita sudah memakai esc(), tapi mari pastikan konsisten:
<!-- AMAN -->
<h1><?= esc($post['title']) ?></h1>
<input value="<?= esc($post['title'], 'attr') ?>">
<!-- RENTAN - input dirender mentah -->
<h1><?= $post['title'] ?></h1>Aturan audit: setiap output yang berasal dari user atau database harus melewati esc(). Untuk konten rich text yang perlu HTML, sanitasi dengan HTMLPurifier (episode 8) — jangan biarkan input mentah lewat.
Kita sudah mengaktifkan filter csrf global di episode 8. Audit:
<?= csrf_field() ?>.// app/Config/Filters.php
public $globals = [
'before' => [
'csrf',
'honeypot',
],
];Upload file adalah salah satu vektor paling berbahaya. Kode aman:
$rules = [
'avatar' => 'uploaded[avatar]|max_size[avatar,2048]|is_image[avatar]|mime_in[avatar,image/png,image/jpg,image/jpeg]',
];
if (! $this->validate($rules)) {
return redirect()->back()->with('errors', $this->validator->getErrors());
}
$file = $this->request->getFile('avatar');
if ($file->isValid() && ! $file->hasMoved()) {
$newName = $file->getRandomName(); // nama acak, bukan nama user
$file->move(WRITEPATH . 'uploads', $newName); // di luar public/, dibahas di bawah
}Poin keamanan:
getRandomName() — jangan pernah memakai nama file asli dari user (bisa berisi path traversal atau nama berbahaya).public/ — file yang tidak perlu diakses publik (misal dokumen privat) sebaiknya di writable/uploads/; untuk file publik yang harus disajikan browser, beri nama acak dan kontrol akses.Response headers yang benar memblokir banyak serangan client-side:
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class SecurityHeaders implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null): mixed
{
return null;
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): ResponseInterface
{
return $response
->setHeader('X-Frame-Options', 'DENY')
->setHeader('X-Content-Type-Options', 'nosniff')
->setHeader('Referrer-Policy', 'no-referrer')
->setHeader('X-XSS-Protection', '0')
->setHeader('Content-Security-Policy', "default-src 'self'");
}
}Daftarkan di Config/Filters.php sebagai after global:
public $globals = [
'before' => ['csrf', 'honeypot'],
'after' => [
'toolbar',
'securityheaders' => [
'except' => ['api/*'],
],
],
];CodeIgniter 4 bahkan menyediakan SecureHeaders filter bawaan (di episode 19 kita akan melihat daftar filter lengkapnya) — tapi memahami isi header manual seperti di atas tetap penting.
Audit cepat aplikasi blog ci4-app:
| Area | Pemeriksaan | Status |
|---|---|---|
| SQL | Semua query lewat Query Builder/prepared? | ✅ |
| XSS | Semua output lewat esc()? | ✅ |
| CSRF | Form ada csrf_field(), filter aktif? | ✅ |
| Password | password_hash()/password_verify()? | ✅ |
| Upload | getRandomName() + validasi mime? | ✅ |
| Headers | Security headers terpasang? | ✅ |
| Auth | Session regenerate saat login? | ✅ |
Tip
Jadikan audit ini checklist berkala, bukan kegiatan sekali jalan. Banyak tim menaruhnya di Definition of Done: fitur "selesai" hanya jika lolos checklist keamanan di atas. Di episode 26 kita akan menyusun checklist lengkap akhir series.
esc() di semua output; HTMLPurifier untuk rich text.csrf_field() di setiap form.getRandomName(), validasi mime/size, simpan di luar public/.Inti yang harus dibawa pulang:
Di episode 19 selanjutnya kita akan membahas filter, middleware & rate limiting — filter bawaan, custom filter before/after, dan membangun throttler untuk melindungi API dan halaman login dari brute force. Keamanan kalian akan naik level!