TDCTF Academy Logo TDCTF ACADEMY

A04 - Insecure Design

Definisi dan Penjelasan

Insecure Design adalah kategori risiko keamanan yang berfokus pada kerentanan yang muncul dari kesalahan desain arsitektur dan cacat logika bisnis, bukan dari bug pada implementasi teknis atau coding. Ini adalah tambahan baru di OWASP Top 10 2021 yang sebelumnya tidak ada sebagai kategori terpisah di versi 2017.

Berbeda dengan kerentanan teknis (seperti SQL Injection atau XSS) yang bisa diperbaiki dengan patch kode, Insecure Design memerlukan perubahan fundamental pada arsitektur dan desain aplikasi. Kerentanan ini sudah ada sejak tahap perencanaan dan desain - bahkan sebelum satu baris kode pun ditulis.

Konsep kunci dalam Insecure Design:

  1. Security by Design - Keamanan harus diintegrasikan sejak fase desain, bukan ditambahkan setelah aplikasi selesai
  2. Threat Modeling - Identifikasi potensi ancaman sebelum implementasi
  3. Business Logic Flaws - Cacat dalam logika bisnis yang bisa dieksploitasi
  4. Missing Security Controls - Kontrol keamanan yang seharusnya ada sejak awal

Mengapa Insecure Design Berbahaya?

Aspek Dampak
Akar Masalah Kesalahan ada di level arsitektur, bukan kode
Biaya Perbaikan Sangat mahal - perlu redesign ulang
Deteksi Sulit dideteksi oleh scanner/SAST/DAST otomatis
Cakupan Biasanya mempengaruhi banyak komponen
Eksploitasi Sering membutuhkan pemahaman mendalam tentang bisnis

Perbedaan Insecure Design vs Implementation Bug

Aspek Insecure Design Implementation Bug
Tahap muncul Fase desain/perencanaan Fase coding
Deteksi alat Sulit (butuh threat modeling) Mudah (SAST, DAST)
Biaya perbaikan Tinggi (redesign) Rendah (patch)
Contoh Tidak ada rate limiting, forgot password flaw SQL Injection, XSS

Contoh-contoh Insecure Design

1. Business Logic Bypass

Attacker memanipulasi urutan atau alur proses bisnis untuk mendapatkan keuntungan.

Contoh:

  • Checkout tanpa pembayaran
  • Mendapatkan diskon berulang kali
  • Transfer uang ke akun sendiri lalu klaim refund

2. Rate Limiting Tidak Ada

Endpoint kritis (login, registrasi, reset password) tidak memiliki pembatasan kecepatan.

Dampak:

  • Brute force password
  • Account enumeration
  • SMS/email bombing

3. Flawed Forgot Password Logic

Mekanisme lupa password yang bisa dieksploitasi.

Contoh:

  • Security question yang mudah ditebak
  • Token reset password bisa ditebak
  • Link reset password tidak pernah expired
  • Tidak ada verifikasi identitas sebelum reset

4. 2FA Bypass

Two-factor authentication yang bisa di-bypass karena kesalahan desain.

Contoh:

  • 2FA hanya di halaman login, tapi endpoint API tidak dilindungi
  • OTP bisa ditebak (terlalu pendek, tidak ada rate limiting)
  • Backup code tidak dirotasi

5. Race Condition

Terjadi ketika aplikasi tidak menangani akses konkuren dengan benar.

Contoh:

# RENTAN - Race condition pada transfer uang
def transfer(sender, receiver, amount):
balance = get_balance(sender)
if balance >= amount:
deduct(sender, amount)
# 🔴 Thread lain membaca balance di sini
add(receiver, amount)

6. Missing Access Control pada Workflow

Setiap step dalam workflow seharusnya memvalidasi otorisasi.

Contoh: Aplikasi yang memeriksa role hanya di halaman pertama wizard, tetapi step-step selanjutnya tidak.

7. Default Credentials / Hardcoded Config

Akun default dengan password yang diketahui publik.

8. Infinite Money / Coupon Abuse

Sistem kupon/diskon yang bisa digunakan berkali-kali.

Dampak Keamanan

Dampak Deskripsi Contoh
Financial Loss Kerugian finansial langsung Abuse diskon, pencurian saldo
Account Takeover Pengambilalihan akun Brute force tanpa rate limiting
Data Breach Kebocoran data Workflow bypass akses data
Denial of Service Layanan tidak tersedia No rate limiting + abuse
Reputational Damage Hilangnya kepercayaan Keamanan buruk tersebar luas

Contoh Kerentanan (Code Snippets)

Contoh 1: Rate Limiting Tidak Ada (Rentan)

# RENTAN - Endpoint login tanpa rate limiting
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = authenticate(username, password)
if user:
return jsonify({"token": create_token(user)})
return jsonify({"error": "Invalid credentials"}), 401

Dampak: Attacker bisa melakukan brute force jutaan password.

Contoh 2: Rate Limiting yang Benar (Aman)

# AMAN - Rate limiting dengan Flask-Limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(app, key_func=get_remote_address)

@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # Max 5 percobaan per menit
def login():
username = request.form['username']
password = request.form['password']
user = authenticate(username, password)
if user:
return jsonify({"token": create_token(user)})
return jsonify({"error": "Invalid credentials"}), 401

Contoh 3: Forgot Password - Token Dapat Ditebak (Rentan)

# RENTAN - Token dapat ditebak
import random

def generate_reset_token():
# Hanya 10000 kemungkinan (0000-9999)
return f"{random.randint(0, 9999):04d}"
# RENTAN - Token tidak pernah expired
reset_token = generate_reset_token()
save_to_db(user_id, reset_token)
# Token berlaku selamanya sampai digunakan!

Contoh 4: Forgot Password yang Aman

# AMAN - Token dengan secrets library dan expiration
import secrets
from datetime import datetime, timedelta

def generate_reset_token(user_id):
# 32 byte true random - tidak bisa ditebak
token = secrets.token_hex(32)
expired_at = datetime.utcnow() + timedelta(hours=1)

save_to_db(user_id, {
'token': hash_token(token), # Hash sebelum simpan
'expired_at': expired_at,
'used': False
})

return token # Return plaintext untuk dikirim via email

def verify_reset_token(user_id, token):
record = get_reset_record(user_id)
if not record:
return False
if record['used']:
return False
if datetime.utcnow() > record['expired_at']:
return False
if not compare_token(record['token'], token):
return False
return True

Contoh 5: 2FA Bypass - Hanya di Frontend (Rentan)

// RENTAN - 2FA hanya di frontend
async function login() {
const token = await getJWT();
if (is2FAEnabled()) {
// Tampilkan form 2FA
show2FAForm();
const otp = await getOTP();
if (!verifyOTP(otp)) {
return;
}
}
// Setelah 2FA, panggil API tanpa verifikasi ulang
await callAPI(token); // 🔴 API endpoint tidak memverifikasi 2FA!
}

Contoh 6: Race Condition pada Stok Barang (Rentan)

# RENTAN - Race condition
@app.route('/api/order', methods=['POST'])
def create_order():
product_id = request.json['product_id']
quantity = request.json['quantity']

# 🔴 Gap antara SELECT dan UPDATE
stock = get_stock(product_id)
if stock >= quantity:
update_stock(product_id, stock - quantity)
create_order_record(product_id, quantity)
return jsonify({"success": True})
return jsonify({"error": "Insufficient stock"}), 400

Eksploitasi: Kirim 100 request simultan untuk produk dengan stok 5 - semuanya berhasil!

Contoh 7: Race Condition - Diperbaiki dengan Atomic Operation

# AMAN - Gunakan atomic operation / locking
@app.route('/api/order', methods=['POST'])
def create_order():
product_id = request.json['product_id']
quantity = request.json['quantity']

# Atomic decrement dengan kondisi
# Hanya berhasil jika stok >= quantity
result = db.execute("""
UPDATE products
SET stock = stock - ?
WHERE id = ? AND stock >= ?
""", (quantity, product_id, quantity))

if result.rowcount > 0:
create_order_record(product_id, quantity)
return jsonify({"success": True})
return jsonify({"error": "Insufficient stock"}), 400

Contoh 8: Workflow Bypass (Rentan)

# RENTAN - Tidak memvalidasi role di setiap step
@app.route('/checkout/step1', methods=['POST'])
def checkout_step1():
# Cek role hanya di step 1
if not current_user.is_authenticated:
return redirect('/login')
session['cart'] = request.json
return jsonify({"next": "/checkout/step2"})

@app.route('/checkout/step2', methods=['POST'])
def checkout_step2():
# 🔴 Tidak ada pengecekan role!
# User yang sudah logout masih bisa akses step ini
process_payment(session['cart'])
return jsonify({"success": True})

Contoh 9: Workflow Bypass (Diperbaiki)

# AMAN - Validasi di setiap step
def require_checkout_access():
if not current_user.is_authenticated:
abort(401)
if 'checkout_session' not in session:
abort(400, "Invalid checkout session")

@app.route('/checkout/step1', methods=['POST'])
@login_required
def checkout_step1():
session['checkout_session'] = str(uuid.uuid4())
session['cart'] = request.json
return jsonify({"next": "/checkout/step2"})

@app.route('/checkout/step2', methods=['POST'])
@login_required
@require_checkout_access
def checkout_step2():
process_payment(session['cart'])
session.pop('checkout_session', None)
return jsonify({"success": True})

Cara Eksploitasi (Relevan ke Lab)

1. Business Logic Bypass - Ticket Lab

Skenario: Sistem pembelian tiket dengan workflow:

Pilih tiket → Masukkan ke keranjang → Checkout → Bayar → Dapat tiket

Eksploitasi:

  1. Tambahkan tiket ke keranjang
  2. Langsung akses endpoint konfirmasi tanpa melewati pembayaran
  3. Jika berhasil, Anda mendapatkan tiket gratis

2. Rate Limiting Abuse

Skenario: Endpoint login tanpa rate limiting

# Brute force dengan hydra
hydra -l admin -P /usr/share/wordlists/rockyou.txt target.com http-post-form "/login:username=^USER^&password=^PASS^:Invalid credentials"

# Atau dengan script Python sederhana
for password in passwords:
r = requests.post(url, data={"username": "admin", "password": password})
if "Invalid credentials" not in r.text:
print(f"Found: {password}")
break

3. 2FA Bypass

  1. Login dengan kredensial yang valid
  2. Capture semua request menggunakan Burp Suite
  3. Perhatikan endpoint API yang dipanggil setelah 2FA
  4. Coba akses endpoint tersebut langsung tanpa melalui 2FA
  5. Jika berhasil, 2FA dapat di-bypass

4. Race Condition

# Script untuk race condition
import threading
import requests

url = "http://target.com/api/order"
data = {"product_id": 1, "quantity": 1}

def send_request():
r = requests.post(url, json=data, cookies=cookies)
print(r.status_code, r.text)

# Kirim 20 request simultan
threads = []
for i in range(20):
t = threading.Thread(target=send_request)
threads.append(t)
t.start()

for t in threads:
t.join()

5. Coupon/Diskon Abuse

  1. Dapatkan kupon diskon
  2. Gunakan kupon sekali
  3. Coba gunakan kupon yang sama lagi
  4. Jika sistem tidak menandai kupon sebagai "used", Anda bisa menggunakannya berulang kali

Cara Pencegahan

1. Threat Modeling Sejak Awal

Lakukan threat modeling di setiap tahap desain menggunakan metodologi seperti STRIDE (Microsoft):

Kategori Ancaman Contoh
Spoofing Pemalsuan identitas Akun palsu, session hijacking
Tampering Modifikasi data Manipulasi harga, saldo
Repudiation Penyangkalan aksi Tidak ada log audit
Information Disclosure Kebocoran informasi Error yang menampilkan detail
Denial of Service Gangguan layanan Flood request
Elevation of Privilege Eskalasi privilege User jadi admin

2. Security by Design Principles

Prinsip Keamanan Desain:
- Least Privilege: Setiap komponen hanya punya akses minimal
- Defense in Depth: Berlapis-lapis pertahanan
- Fail Secure: Gagal ke mode aman (deny by default)
- Complete Mediation: Validasi di setiap akses
- Economy of Mechanism: Desain sesederhana mungkin
- Open Design: Keamanan tidak bergantung pada kerahasiaan
- Psychological Acceptability: Mudah digunakan, tetap aman

3. Implementasi Rate Limiting

# Rate limiting berlapis
# 1. IP-based (per alamat IP)
# 2. User-based (per akun)
# 3. Endpoint-based (per endpoint)

limiter = Limiter(
app,
key_func=lambda: f"{get_remote_address()}:{current_user.id}"
)

# Endpoint kritis
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute, 20 per hour")
def login(): ...

@app.route('/register', methods=['POST'])
@limiter.limit("3 per hour")
def register(): ...

@app.route('/reset-password', methods=['POST'])
@limiter.limit("2 per hour")
def reset_password(): ...

# Endpoint API
@app.route('/api/transfer', methods=['POST'])
@limiter.limit("10 per minute, 100 per hour")
def transfer(): ...

4. Secure Workflow Design

# Setiap state dalam workflow harus tervalidasi
WORKFLOW_STATES = {
'init': ['select_items', 'checkout'],
'select_items': ['checkout'],
'checkout': ['payment'],
'payment': ['confirmation'],
'confirmation': ['completed'],
'completed': []
}

def validate_transition(current_state, next_state):
if next_state not in WORKFLOW_STATES.get(current_state, []):
abort(400, "Invalid workflow transition")

5. Race Condition Prevention

# 1. Gunakan database transactions
@app.route('/api/transfer', methods=['POST'])
def transfer():
db.begin()
try:
sender_balance = db.execute(
"SELECT balance FROM accounts WHERE id = ? FOR UPDATE",
(sender_id,)
).fetchone()[0]

if sender_balance < amount:
db.rollback()
return jsonify({"error": "Insufficient funds"}), 400

db.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?",
(amount, sender_id))
db.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?",
(amount, receiver_id))
db.commit()
return jsonify({"success": True})
except:
db.rollback()
return jsonify({"error": "Transaction failed"}), 500

# 2. Atau gunakan pessimistic locking
# 3. Atau gunakan atomic operations
# 4. Atau gunakan queue/async processing

6. Secure Forgot Password

Desain Reset Password yang Aman:
- Token: 32+ byte, true random (secrets.token_hex)
- Expiration: 1 jam maksimal
- One-time use: Token tidak bisa dipakai dua kali
- Hash token di database (jangan simpan plaintext)
- Rate limiting: 2-3 request per jam per akun
- Notifikasi: Email/SMS bahwa password direset
- No user enumeration: Jangan bedakan "email tidak ada" vs "email terdaftar"

7. 2FA yang Benar

# 2FA harus diterapkan di layer SERVER, bukan hanya frontend
# Setiap endpoint sensitif harus memvalidasi 2FA

def require_2fa(f):
@wraps(f)
def decorated(*args, **kwargs):
if current_user.is_2fa_enabled and not session.get('2fa_verified'):
return jsonify({"error": "2FA verification required"}), 401
return f(*args, **kwargs)
return decorated

@app.route('/api/profile/email', methods=['PUT'])
@login_required
@require_2fa
def update_email():
# Hanya bisa diakses setelah 2FA
pass

8. Testing dan Review

Metodologi Pengujian:
1. Threat Modeling Review: Sebelum implementasi
2. Architecture Risk Analysis: Analisis risiko arsitektur
3. Security User Stories: Tulis requirement keamanan sebagai user stories
4. Abuse Cases: Test skenario penyalahgunaan (bukan hanya use case normal)
5. Penetration Testing: Fokus pada business logic flaws
6. Code Review: Review dengan perspektif keamanan desain

CWE Mapping

CWE Deskripsi
CWE-840 Business Logic Errors (Kesalahan Logika Bisnis)
CWE-657 Violation of Secure Design Principles (Pelanggaran Prinsip Desain Aman)
CWE-799 Improper Control of Interaction Frequency (Rate Limiting)
CWE-362 Concurrent Execution using Shared Resource (Race Condition)
CWE-306 Missing Authentication for Critical Function
CWE-352 Cross-Site Request Forgery (CSRF)
CWE-602 Client-Side Enforcement of Server-Side Security
CWE-640 Weak Password Recovery Mechanism
CWE-308 Use of Single-factor Authentication
CWE-441 Unintended Proxy or Intermediary (Confusion)

Lab Terkait

Lab Konsep Tingkat Kesulitan
Ticket (Workflow Bypass) Business logic bypass pada sistem tiket Medium
WebGoat Business logic flaws, race condition, 2FA bypass Easy-Hard

Contoh Konkret dari Lab

Ticket Lab - Workflow Bypass

Skenario Lab: Sistem e-ticket dengan workflow:

  1. Pilih tiket
  2. Masukkan ke keranjang
  3. Checkout
  4. Pembayaran
  5. Konfirmasi
  6. Tiket diterbitkan

Langkah eksploitasi:

  1. Buka aplikasi dan pilih tiket
  2. Buka Burp Suite dan intercept request
  3. Tambahkan tiket ke keranjang (request normal)
  4. Coba akses langsung endpoint konfirmasi: POST /api/tickets/confirm
  5. Jika server tidak memvalidasi status pembayaran, tiket akan diterbitkan gratis
  6. Jika berhasil, cek apakah tiket benar-benar ada di profil Anda

Variasi eksploitasi:

  • Coba akses endpoint dengan metode HTTP yang berbeda (GET vs POST)
  • Manipulasi parameter quantity menjadi negatif atau desimal
  • Coba pesan dengan harga 0
  • Coba gunakan kupon yang sudah expired

WebGoat - Business Logic Flaws

WebGoat Lessons terkait:

  1. Business Logic Flaws - Pelajaran tentang bagaimana logika bisnis bisa dimanipulasi
  2. Race Condition - Eksploitasi konkurensi pada transfer uang
  3. 2FA Bypass - Bypass mekanisme two-factor authentication

Langkah-langkah di WebGoat (Race Condition):

  1. Buka lesson "Race Condition"
  2. Perhatikan endpoint transfer uang
  3. Kirim beberapa request transfer secara simultan
  4. Jika saldo tidak terkunci dengan benar, Anda bisa transfer lebih dari saldo yang dimiliki

Kesimpulan

Insecure Design adalah kategori kerentanan yang unik karena berfokus pada akar masalah - kesalahan desain yang sudah ada sebelum aplikasi ditulis. Berbeda dengan kerentanan implementasi yang bisa diperbaiki dengan patch, insecure design sering membutuhkan redesain ulang yang mahal dan memakan waktu.

Pencegahan terbaik adalah pendekatan Security by Design: lakukan threat modeling sejak awal, implementasikan rate limiting di endpoint kritis, desain workflow dengan validasi di setiap tahap, gunakan transaksi database untuk mencegah race condition, dan selalu pikirkan skenario penyalahgunaan (abuse cases) di samping skenario normal (use cases).

Ingatlah bahwa keamanan bukanlah fitur yang bisa "ditambahkan" setelah aplikasi selesai - keamanan harus menjadi bagian integral dari desain arsitektur sejak hari pertama.

PADA HALAMAN INI