OWASP #
Sebagian besar pelanggaran keamanan yang terjadi di aplikasi web bukan karena serangan yang canggih dan belum pernah terpikirkan sebelumnya. Mayoritas berasal dari kategori yang sama, berulang dari tahun ke tahun, dan sudah diketahui cara mencegahnya. OWASP — Open Web Application Security Project — mendokumentasikan kategori-kategori ini dalam daftar Top 10 yang diperbarui secara berkala berdasarkan data insiden nyata dari ratusan organisasi di seluruh dunia.
Memahami OWASP Top 10 bukan tentang menghafal nama-namanya. Ini tentang memahami cara berpikir attacker — bagaimana mereka mengidentifikasi dan mengeksploitasi kelemahan yang terlihat tidak berbahaya dari perspektif developer yang hanya memikirkan happy path. Setiap item dalam daftar ini merepresentasikan satu cara di mana asumsi developer tentang keamanan ternyata tidak berlaku di dunia nyata.
Apa Itu OWASP dan Mengapa Relevan #
OWASP adalah organisasi nonprofit yang menghasilkan dokumentasi, tools, dan standar keamanan yang semuanya tersedia gratis. Yang paling dikenal adalah OWASP Top 10 — daftar sepuluh kategori kerentanan paling kritis yang diperbarui setiap beberapa tahun berdasarkan data insiden nyata.
OWASP Top 10 saat ini (2021) mencerminkan pergeseran lanskap ancaman: dari serangan teknis murni menuju kombinasi teknis dan desain. Dua item baru — Insecure Design dan Software Integrity Failures — menunjukkan bahwa keamanan tidak bisa hanya ditambahkan di akhir sebagai lapisan, tapi harus menjadi bagian dari cara sistem dirancang dari awal.
graph TD
A[Attacker] --> B{Entry Points}
B --> C[User Input]
B --> D[API Endpoints]
B --> E[Authentication]
B --> F[Third-party Components]
C --> G[Injection / XSS]
D --> H[Broken Access Control / SSRF]
E --> I[Auth Failures]
F --> J[Vulnerable Components / Supply Chain]
G --> K[Data Breach / RCE]
H --> K
I --> L[Account Takeover]
J --> K
K --> M[Reputasi, Finansial, Legal]
L --> MA01 — Broken Access Control #
Broken Access Control adalah risiko nomor satu sejak 2021, naik dari posisi lima di daftar sebelumnya. Ini terjadi ketika sistem tidak memverifikasi dengan benar apakah pengguna yang melakukan request berhak melakukan operasi tersebut.
Kunci untuk memahami risiko ini: authorization dan authentication adalah dua hal yang berbeda. Authentication memverifikasi siapa kamu. Authorization memverifikasi apa yang boleh kamu lakukan. Banyak sistem yang autentikasinya kuat tapi authorizationnya lemah.
# ANTI-PATTERN: tidak ada otorisasi di endpoint — hanya mengecek login
@app.route('/orders/<int:order_id>')
@login_required # hanya cek apakah user sudah login
def get_order(order_id):
order = Order.query.get(order_id)
return jsonify(order.to_dict())
# Attacker yang sudah login bisa mengakses order milik user lain
# hanya dengan mengubah order_id di URL:
# GET /orders/1001 → GET /orders/1002 → GET /orders/1003
# Ini disebut Insecure Direct Object Reference (IDOR)
# BENAR: verifikasi ownership sebelum memberikan akses
@app.route('/orders/<int:order_id>')
@login_required
def get_order(order_id):
order = Order.query.get_or_404(order_id)
# Verifikasi bahwa order ini milik user yang sedang login
if order.user_id != current_user.id:
abort(403) # Forbidden — bukan milikmu
return jsonify(order.to_dict())
Prinsip-prinsip mitigasi Broken Access Control:
✓ Verifikasi ownership di setiap request yang mengakses resource spesifik
✓ Implement RBAC (Role-Based Access Control) yang ketat
✓ Deny by default — jika tidak ada aturan eksplisit yang mengizinkan, tolak
✓ Jangan andalkan client-side untuk kontrol akses apapun
✓ Log semua access control failure — pola gagal berulang = serangan IDOR
✗ Jangan expose primary key database langsung di URL (gunakan UUID atau token)
A02 — Cryptographic Failures #
Sebelumnya bernama “Sensitive Data Exposure”, nama yang lebih baru mengalihkan fokus ke akar masalah: kegagalan dalam menggunakan kriptografi yang benar adalah yang menyebabkan data sensitif terekspos.
Ini bukan hanya tentang tidak mengenkripsi data — ini tentang menggunakan enkripsi yang salah, algoritma yang sudah usang, atau implementasi yang terlihat benar tapi memiliki kelemahan fundamental.
# ANTI-PATTERN: menyimpan password dalam plaintext
def register_user(email, password):
user = User(email=email, password=password) # ← SANGAT BERBAHAYA
db.session.add(user)
# ANTI-PATTERN: menggunakan MD5 atau SHA1 untuk password
import hashlib
def register_user(email, password):
hashed = hashlib.md5(password.encode()).hexdigest() # ← MD5 tidak aman untuk password
user = User(email=email, password=hashed)
# BENAR: gunakan bcrypt atau Argon2 dengan salt yang di-generate otomatis
from passlib.hash import argon2
def register_user(email, password):
hashed = argon2.hash(password) # Argon2 menangani salt secara otomatis
user = User(email=email, password_hash=hashed)
db.session.add(user)
def verify_password(plain_password, stored_hash):
return argon2.verify(plain_password, stored_hash)
# Argon2 dirancang untuk slow hashing — membuat brute-force sangat mahal
Apa yang membuat hashing password menjadi benar:
✓ Gunakan algoritma yang dirancang untuk password: Argon2, bcrypt, scrypt
Bukan: MD5, SHA1, SHA256 tanpa parameter tambahan
Alasan: algoritma hash umum dirancang untuk kecepatan. Password hashing
perlu lambat — satu hash butuh 100ms adalah fitur, bukan bug.
✓ Salt di-generate secara random untuk setiap password (bcrypt/Argon2 otomatis)
Tanpa salt: dua user dengan password sama memiliki hash yang sama
→ rainbow table attack menjadi efektif
✓ Enkripsi data sensitif at rest
Database dikompromikan → data sensitif tetap tidak terbaca
✓ TLS untuk semua komunikasi (HTTP → HTTPS)
Network traffic tidak bisa di-sniff
✗ Jangan hardcode encryption key di source code
✗ Jangan gunakan ECB mode untuk enkripsi blok
✗ Jangan generate random number dengan Math.random() untuk keperluan keamanan
A03 — Injection #
Injection terjadi ketika data yang dikirim oleh user dieksekusi sebagai perintah atau query. SQL Injection adalah yang paling umum, tapi kategori ini mencakup Command Injection, LDAP Injection, dan berbagai bentuk lainnya.
Yang membuat injection berbahaya: attacker tidak butuh akun, tidak butuh session yang valid, dan tidak perlu menemukan bug yang kompleks. Satu endpoint yang rentan sudah cukup untuk mengekspos seluruh database.
# ANTI-PATTERN: string concatenation langsung ke query SQL
def get_user_by_email(email):
query = f"SELECT * FROM users WHERE email = '{email}'"
return db.execute(query)
# Apa yang terjadi jika attacker mengirim email:
# email = "anything' OR '1'='1"
# Query menjadi: SELECT * FROM users WHERE email = 'anything' OR '1'='1'
# → mengembalikan SEMUA user di database
# Lebih berbahaya — DROP TABLE:
# email = "'; DROP TABLE users; --"
# Query menjadi: SELECT * FROM users WHERE email = ''; DROP TABLE users; --'
# → menghapus seluruh tabel users
# BENAR: parameterized query — input selalu diperlakukan sebagai data, bukan kode
def get_user_by_email(email):
query = "SELECT * FROM users WHERE email = ?"
return db.execute(query, (email,))
# Database engine memisahkan query structure dari data
# Input ''; DROP TABLE users; --' akan dicari secara literal,
# bukan dieksekusi sebagai SQL
# BENAR dengan ORM (lebih idiomatis):
def get_user_by_email(email):
return User.query.filter_by(email=email).first()
# ORM yang baik selalu menggunakan parameterized query di bawahnya
# Command Injection — sama berbahayanya
import subprocess
# ANTI-PATTERN: user input langsung ke shell command
def convert_image(filename):
subprocess.run(f"convert {filename} output.png", shell=True)
# Jika filename = "test.jpg; rm -rf /", server akan menjalankan rm -rf /
# BENAR: gunakan list argument, jangan shell=True
def convert_image(filename):
# Validasi filename terlebih dahulu
if not filename.replace('.', '').replace('_', '').isalnum():
raise ValueError("Invalid filename")
subprocess.run(["convert", filename, "output.png"])
# Setiap argumen diperlakukan sebagai satu unit, bukan diparse oleh shell
A04 — Insecure Design #
Ini adalah kategori baru di 2021 yang menjawab pertanyaan penting: bagaimana jika implementasinya sudah benar, tapi desainnya yang salah? Insecure Design terjadi ketika sistem dirancang tanpa mempertimbangkan bagaimana attacker akan mencoba mengeksploitasinya.
Contoh Insecure Design dalam alur reset password:
Desain yang tidak aman:
1. User minta reset password
2. Server kirim link: /reset?token=12345
(token adalah sequential number, mudah ditebak)
3. Attacker mencoba: /reset?token=12344, 12346, 12347...
4. Attacker bisa reset password orang lain
Desain yang aman:
1. User minta reset password
2. Server generate cryptographically secure random token (256-bit)
3. Token di-hash sebelum disimpan di database
4. Link dikirim: /reset?token=<random-256-bit>
5. Token hanya berlaku 15 menit dan single-use
6. Setelah digunakan, token langsung dihapus dari database
# Implementasi token reset password yang aman
import secrets
import hashlib
from datetime import datetime, timedelta
def create_password_reset_token(user_id):
# Generate token yang kriptografis aman (32 byte = 256 bit entropy)
raw_token = secrets.token_urlsafe(32)
# Hash token sebelum disimpan — jika database bocor, token tidak bisa digunakan
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
# Simpan hash dan waktu expiry
PasswordResetToken.create(
user_id=user_id,
token_hash=token_hash,
expires_at=datetime.utcnow() + timedelta(minutes=15),
used=False
)
# Return raw token untuk dikirim ke user (bukan hash)
return raw_token
def verify_reset_token(raw_token, new_password):
token_hash = hashlib.sha256(raw_token.encode()).hexdigest()
record = PasswordResetToken.find_by_hash(token_hash)
if not record:
raise InvalidTokenError("Token tidak valid")
if record.used:
raise InvalidTokenError("Token sudah digunakan")
if record.expires_at < datetime.utcnow():
raise InvalidTokenError("Token sudah expired")
# Update password dan invalidasi token
update_user_password(record.user_id, new_password)
record.mark_as_used() # Token hanya bisa digunakan sekali
Threat modeling adalah cara sistematis untuk mengidentifikasi Insecure Design sebelum implementasi dimulai. Pertanyaan utamanya: “Apa yang bisa dilakukan attacker dengan fitur ini?” Lakukan threat modeling untuk setiap fitur yang menyentuh autentikasi, otorisasi, data sensitif, atau alur keuangan.
A05 — Security Misconfiguration #
Konfigurasi yang salah adalah salah satu penyebab insiden keamanan yang paling sering dan paling mudah dihindari. Server yang menampilkan stack trace, endpoint debug yang terbuka di production, default credential yang tidak diganti, cloud storage bucket yang public — semua ini bukan bug dalam kode, tapi dalam konfigurasi.
Contoh Security Misconfiguration yang umum:
✗ Error handling yang mengekspos informasi sensitif:
HTTP 500: java.sql.SQLException: Table 'users' doesn't exist
→ Attacker tahu nama tabel database dan bahwa mereka bisa mencoba
SQL injection untuk menemukan tabel yang ada
✓ Error handling yang aman:
HTTP 500: Internal Server Error
→ Tidak ada informasi internal yang terekspos
→ Error detail di-log server-side saja
✗ Header HTTP yang mengekspos teknologi:
X-Powered-By: Express 4.18.2
Server: Apache/2.4.51 (Ubuntu)
→ Attacker tahu versi yang digunakan dan bisa mencari CVE yang relevan
✓ Hapus header yang mengekspos teknologi:
# Express.js
app.disable('x-powered-by')
# Nginx
server_tokens off;
✗ CORS yang terlalu permisif:
Access-Control-Allow-Origin: *
→ Setiap website bisa membuat request ke API kamu atas nama user
✓ CORS yang ketat:
Access-Control-Allow-Origin: https://app.yourdomain.com
# Security headers yang wajib ada di setiap response HTTP:
# Content Security Policy — mencegah XSS
Content-Security-Policy: default-src 'self'; script-src 'self'
# Mencegah MIME type sniffing
X-Content-Type-Options: nosniff
# Mencegah clickjacking
X-Frame-Options: DENY
# Memaksa HTTPS selama 1 tahun
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Tidak kirim Referer ke situs lain
Referrer-Policy: strict-origin-when-cross-origin
A06 — Vulnerable and Outdated Components #
Aplikasi modern tidak ditulis dari nol — mereka bergantung pada ratusan library dan framework pihak ketiga. Setiap dependency adalah potensi attack vector. Ketika sebuah kerentanan ditemukan di library yang kamu gunakan, attacker yang mengeksploitasi aplikasimu tidak perlu menemukan bug baru — mereka tinggal menggunakan exploit yang sudah tersedia.
Siklus hidup kerentanan di dependency:
1. Peneliti menemukan kerentanan di library X versi 1.2.3
2. CVE dipublikasikan: "Library X sebelum 1.2.4 rentan terhadap RCE"
3. Patch dirilis: library X versi 1.2.4
4. Exploit tool dipublikasikan di GitHub/ExploitDB
5. Automated scanner (Shodan, dll) menemukan aplikasi yang masih pakai 1.2.3
6. Mass exploitation dimulai — aplikasi yang tidak update menjadi target
Selama kamu masih menggunakan versi lama, kamu terekspos.
Berapa lama antara step 2 dan step 6? Bisa hitungan jam.
# Integrasikan dependency scanning ke CI pipeline
# GitHub Actions contoh:
name: Security Scan
on: [push, pull_request]
jobs:
dependency-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Untuk Node.js
- name: NPM Audit
run: npm audit --audit-level=high
# Gagalkan build jika ada vulnerability severity tinggi
# Untuk Python
- name: Safety Check
run: |
pip install safety
safety check --full-report
# Untuk Go
- name: GoVulnCheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# OWASP Dependency-Check (universal)
- name: OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'my-app'
path: '.'
format: 'HTML'
args: >
--failOnCVSS 7
--enableRetired
A07 — Identification and Authentication Failures #
Autentikasi yang lemah adalah pintu masuk paling langsung ke akun pengguna. Ini bukan hanya tentang password yang mudah ditebak — ini tentang semua mekanisme yang memastikan bahwa orang yang mengklaim sebagai pengguna X adalah benar-benar pengguna X.
# ANTI-PATTERN: tidak ada rate limiting pada login endpoint
@app.route('/login', methods=['POST'])
def login():
email = request.json['email']
password = request.json['password']
user = User.query.filter_by(email=email).first()
if user and user.check_password(password):
return jsonify({'token': generate_token(user)})
return jsonify({'error': 'Invalid credentials'}), 401
# Masalah: attacker bisa mencoba ribuan kombinasi password per menit
# Brute-force dan credential stuffing tidak terdeteksi
# BENAR: rate limiting, account lockout, dan logging
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # Max 5 percobaan per menit per IP
def login():
email = request.json.get('email')
password = request.json.get('password')
user = User.query.filter_by(email=email).first()
# Constant-time comparison untuk mencegah timing attack
# Jangan early return jika user tidak ditemukan
if user is None:
# Tetap jalankan check_password dengan dummy untuk consistent timing
dummy_hash = "$argon2id$..."
argon2.verify(password, dummy_hash) # akan gagal tapi waktunya sama
return jsonify({'error': 'Invalid credentials'}), 401
if not argon2.verify(password, user.password_hash):
# Log percobaan gagal untuk monitoring
log_failed_login(email, request.remote_addr)
return jsonify({'error': 'Invalid credentials'}), 401
return jsonify({'token': generate_token(user)})
Mekanisme autentikasi yang harus ada:
✓ Multi-Factor Authentication (MFA) untuk akun sensitif
→ Bahkan jika password dikompromikan, attacker butuh faktor kedua
✓ Rate limiting dan account lockout
→ Setelah N percobaan gagal, akun terkunci sementara
✓ Deteksi credential stuffing
→ Login dari lokasi yang tidak biasa → trigger verification tambahan
✓ Session management yang benar
→ Session token di-invalidate saat logout
→ Session expired setelah inaktivitas
✗ Jangan izinkan password "password", "123456", atau password yang ada di
list common passwords (HIBP API bisa digunakan untuk cek ini)
✗ Jangan expose informasi "email tidak terdaftar" vs "password salah"
→ Gunakan pesan yang sama: "Invalid email or password"
A08 — Software and Data Integrity Failures #
Kategori ini mencakup dua masalah yang terkait: kepercayaan buta pada software dari luar (supply chain attack) dan tidak adanya verifikasi integritas pada update atau data kritis.
Contoh nyata supply chain attack:
2020 — SolarWinds:
Attacker menyusup ke pipeline build SolarWinds
→ Software update yang terinfeksi didistribusikan ke 18.000+ pelanggan
→ Termasuk lembaga pemerintah AS
2021 — Codecov:
Script upload Codecov dikompromikan
→ Semua CI pipeline yang menggunakan Codecov mem-leak environment variables
→ Termasuk credential AWS, GitHub token, dll
Pelajaran: kamu tidak hanya perlu memercayai kode yang kamu tulis,
tapi juga semua kode yang kamu jalankan.
# Mitigasi supply chain attack:
# 1. Pin dependency ke versi eksak (bukan ^x.x.x atau ~x.x.x)
# package.json:
{
"dependencies": {
"express": "4.18.2", # ✓ versi eksak
"lodash": "^4.17.21" # ✗ bisa update otomatis ke 4.x.x terbaru
}
}
# 2. Verifikasi integrity dengan lock file
npm ci # gunakan package-lock.json untuk install yang reproducible
# (bukan npm install yang bisa mengupdate lock file)
# 3. Subresource Integrity untuk CDN resources
<script src="https://cdn.example.com/jquery.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
# Browser akan memverifikasi hash sebelum menjalankan script
A09 — Security Logging and Monitoring Failures #
Sistem yang tidak di-monitor adalah sistem yang tidak bisa dipertahankan. Serangan yang tidak terdeteksi bisa berlangsung berbulan-bulan sebelum ditemukan — dan semakin lama berlangsung, semakin besar kerusakannya.
Apa yang wajib di-log untuk security purposes:
Authentication:
✓ Semua login attempt (sukses dan gagal)
✓ Password reset request
✓ MFA event (setup, verified, failed)
✓ Session creation dan termination
Authorization:
✓ Access denied event (403, 401)
✓ Privilege escalation attempt
✓ Akses ke resource sensitif
Data:
✓ Export data dalam jumlah besar
✓ Perubahan pada data kritis (password, email, role)
✓ Bulk delete atau update
Infrastructure:
✓ Dependency vulnerability yang terdeteksi
✓ Konfigurasi yang berubah
✓ Deployment baru
Format log yang berguna untuk security analysis:
{
"timestamp": "2025-06-01T14:23:45Z",
"level": "WARN",
"event": "login_failed",
"user_email": "[email protected]", ← identifikasi siapa
"ip_address": "203.x.x.x", ← dari mana
"user_agent": "...",
"attempt_count": 5, ← konteks
"request_id": "req_abc123" ← correlation ID
}
Apa yang TIDAK boleh di-log:
✗ Password (plaintext maupun hash)
✗ Session token atau API key
✗ Nomor kartu kredit (bahkan yang sudah di-mask sebagian butuh hati-hati)
✗ Data medis atau data sensitif lainnya yang diregulasi
✗ Token reset password
A10 — Server-Side Request Forgery (SSRF) #
SSRF terjadi ketika server melakukan HTTP request ke URL yang ditentukan oleh user, tanpa memvalidasi bahwa URL tersebut aman. Attacker menggunakan ini untuk membuat server melakukan request ke resource internal yang tidak seharusnya bisa diakses dari luar.
# ANTI-PATTERN: server melakukan fetch ke URL dari user tanpa validasi
@app.route('/fetch-preview')
def fetch_url_preview():
url = request.args.get('url')
# Server sekarang menjadi proxy yang attacker bisa kendalikan
response = requests.get(url)
return response.text
# Attacker bisa mengirim:
# /fetch-preview?url=http://169.254.169.254/latest/meta-data/
# → Mengakses AWS EC2 instance metadata (credential AWS!)
# /fetch-preview?url=http://localhost:6379
# → Mencoba connect ke Redis yang mungkin tidak terproteksi
# /fetch-preview?url=http://internal-api.company.internal/admin
# → Mengakses service internal yang tidak terexpose ke internet
# BENAR: validasi dan whitelist URL yang diizinkan
from urllib.parse import urlparse
import ipaddress
ALLOWED_DOMAINS = {'api.partner.com', 'cdn.external-service.com'}
def is_safe_url(url):
try:
parsed = urlparse(url)
# Hanya izinkan HTTPS
if parsed.scheme != 'https':
return False
# Cek domain di whitelist
if parsed.hostname not in ALLOWED_DOMAINS:
return False
# Pastikan tidak ada IP address internal (RFC 1918)
try:
ip = ipaddress.ip_address(parsed.hostname)
if ip.is_private or ip.is_loopback or ip.is_link_local:
return False
except ValueError:
pass # hostname bukan IP, lanjutkan
return True
except Exception:
return False
@app.route('/fetch-preview')
def fetch_url_preview():
url = request.args.get('url')
if not url or not is_safe_url(url):
abort(400, "URL tidak diizinkan")
response = requests.get(url, timeout=5)
return response.text
OWASP ASVS: Standar Verifikasi Keamanan Aplikasi #
OWASP ASVS (Application Security Verification Standard) adalah kerangka kerja untuk menilai seberapa aman sebuah aplikasi. Berbeda dari Top 10 yang berfokus pada risiko yang harus dihindari, ASVS memberikan checklist positif tentang apa yang harus ada.
Tiga level ASVS dan kapan digunakan:
Level 1 — Minimum Security (untuk semua aplikasi):
→ Perlindungan dasar terhadap OWASP Top 10
→ Cocok untuk: aplikasi publik dengan risiko rendah, landing page
→ Verifikasi: automated security scanning
Level 2 — Standard Security (untuk aplikasi bisnis):
→ Defense in depth, proper auth, session management
→ Cocok untuk: aplikasi SaaS, e-commerce, aplikasi enterprise
→ Verifikasi: manual review + automated testing
Level 3 — Advanced Security (untuk aplikasi kritikal):
→ Formal threat modeling, cryptographic implementations diaudit
→ Cocok untuk: banking, healthcare, infrastruktur kritikal
→ Verifikasi: penetration testing + code audit + formal review
Anti-Pattern yang Harus Dihindari #
✗ "Security nanti saja setelah fitur selesai"
Keamanan yang ditambahkan belakangan hampir selalu tidak lengkap.
✓ Security by design: pertimbangkan ancaman dari tahap desain.
✗ Mengandalkan validasi di client-side saja
JavaScript bisa dimodifikasi atau dilewati sepenuhnya.
✓ Selalu validasi di server — client-side hanya untuk UX.
✗ "Kita tidak punya data sensitif, tidak perlu khawatir keamanan"
Setiap aplikasi memiliki data yang berharga bagi attacker:
session token, email user, atau kapasitas server untuk melakukan
hal-hal atas nama user.
✓ Semua aplikasi perlu keamanan dasar.
✗ Security scanning hanya dilakukan sekali saat launch
Kerentanan baru ditemukan setiap hari di library yang digunakan.
✓ Integrasikan security scanning ke CI pipeline.
✗ Log yang tidak pernah dibaca
Log yang tidak di-monitor sama dengan tidak punya log.
✓ Setup alerting untuk event keamanan yang kritis.
Checklist OWASP untuk Developer #
AUTHENTICATION & AUTHORIZATION:
□ Semua endpoint yang mengakses resource verifikasi ownership
□ RBAC diimplementasikan dan diuji
□ Login endpoint memiliki rate limiting
□ MFA tersedia untuk akun sensitif
□ Error message autentikasi tidak membedakan "email tidak ada" vs "password salah"
CRYPTOGRAPHY:
□ Password di-hash dengan bcrypt/Argon2/scrypt
□ Tidak ada data sensitif dalam plaintext di database
□ TLS digunakan untuk semua komunikasi
□ Tidak ada hardcoded secret di source code
INPUT HANDLING:
□ Semua database query menggunakan parameterized query
□ File upload divalidasi tipe dan ukurannya
□ Output di-encode sebelum dirender di HTML (XSS prevention)
CONFIGURATION:
□ Stack trace tidak ditampilkan di production
□ Security headers dipasang (CSP, HSTS, X-Frame-Options, dsb)
□ Default credential sudah diganti
□ Debug endpoint tidak aktif di production
DEPENDENCY:
□ Dependency scanning berjalan di CI
□ Tidak ada dependency dengan known critical vulnerability
□ Lock file di-commit untuk reproducible builds
LOGGING:
□ Authentication event di-log
□ Authorization failure di-log
□ Tidak ada credential atau token yang ter-log
□ Ada alerting untuk anomali keamanan
Ringkasan #
- OWASP Top 10 merepresentasikan cara berpikir attacker — memahaminya bukan tentang hafalan nama, tapi tentang mengenali pola kelemahan yang terus berulang di aplikasi yang berbeda.
- Broken Access Control adalah risiko #1 — selalu verifikasi ownership di server, tidak hanya apakah user sudah login. IDOR adalah serangan yang sangat sederhana dan sangat umum.
- Parameterized query adalah solusi mutlak untuk Injection — tidak ada alasan valid untuk membangun SQL query dengan string concatenation dari input user.
- Password harus di-hash dengan algoritma yang dirancang untuk itu — Argon2, bcrypt, atau scrypt. MD5, SHA1, dan SHA256 biasa bukan untuk password hashing.
- Security Misconfiguration adalah yang paling mudah dihindari — hapus debug endpoint, tutup stack trace, pasang security header, rotate default credential.
- Dependency kamu adalah attack surface kamu — integrasikan dependency scanning ke CI dan act cepat saat ada CVE di library yang digunakan.
- MFA dan rate limiting adalah perlindungan autentikasi yang paling efektif — bahkan password yang kuat bisa dikompromikan; MFA menambah lapisan yang jauh lebih sulit ditembus.
- SSRF muncul ketika server mempercayai URL dari user — whitelist domain yang diizinkan, validasi tidak ada IP internal, gunakan timeout yang ketat.
- Log yang tidak di-monitor adalah log yang tidak berguna — setup alerting untuk event keamanan, bukan hanya simpan log ke file yang tidak dibaca.
- Security adalah proses, bukan destination — kerentanan baru ditemukan setiap hari. Integrasi scanning ke CI, patch dependency rutin, dan lakukan security review berkala.
← Sebelumnya: Asynchronous Content Loading Berikutnya: OWASP Cheatsheet →