Skip to main content

Command Palette

Search for a command to run...

[Bug Bounty] Race Condition: Redeeming Single-Use Coupon Multiple Times

Updated
4 min read
[Bug Bounty] Race Condition: Redeeming Single-Use Coupon Multiple Times

Pada kesempatan kali ini saya akan membahas kerentanan yang seringkali terlewat oleh mata telanjang tapi punya impact yang sangat fatal, yaitu Race Condition. Kerentanan ini saya temukan pada fitur Redeem Voucher di sebuah platform E-Commerce (mari kita sebut saja target.com).

Konsep dasarnya sederhana: Satu kode voucher seharusnya cuma bisa dipakai satu kali. Tapi, apa jadinya kalau kita kirim request pemakaian voucher tersebut secara bersamaan dalam hitungan milidetik? Apakah server sempat mencatat bahwa vouchernya "sudah terpakai" sebelum request kedua masuk?

Seketika saya langsung berpikir, "Kayaknya bisa deh kalau kita paksa server memproses banyak request sekaligus sebelum database sempat melakukan locking."

The Naive Attempt

Awalnya saya coba cara manual menggunakan Burp Suite Repeater. Saya kirim satu request, lalu dengan cepat saya tekan CTRL+R dan GO lagi.

Hasilnya? Gagal. Server target.com cukup cepat. Request pertama sukses (200 OK), dan request kedua langsung ditolak (400 Bad Request) dengan pesan "Voucher already redeemed". Ternyata latensi jaringan dan kecepatan klik jari manusia tidak cukup cepat untuk mengalahkan logic check di backend.

Di sini banyak bug hunter menyerah. Tapi sebenarnya, kita hanya butuh alat yang lebih presisi.

The Bypass: Turbo Intruder

Untuk melakukan bypass pada batasan kecepatan ini, kita tidak bisa mengandalkan Repeater biasa. Kita butuh Turbo Intruder, sebuah ekstensi di Burp Suite yang bisa menahan request di level jaringan, lalu melepaskannya secara serentak (paralel).

Strateginya adalah menggunakan teknik Last-Byte Sync. Kita akan menahan byte terakhir dari request, membuat koneksi terbuka dan siap ("gated"), lalu melepaskan semuanya dalam waktu bersamaan.

Berikut adalah script Python yang saya gunakan di Turbo Intruder:

def queueRequests(target, wordlists):
    # Membuka 30 koneksi sekaligus
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=30,
                           requestsPerConnection=1,
                           pipeline=False
                           )

    # Antrikan 30 request ke dalam 'gate' bernama 'race1'
    for i in range(30):
        engine.queue(target.req, gate='race1')

    # Lepaskan semua request secara bersamaan (presisi milidetik)
    engine.openGate('race1')
    engine.complete(timeout=60)

def handleResponse(req, interesting):
    # Catat semua respon ke dalam tabel hasil
    table.add(req)

The Result

Setelah script dijalankan, Turbo Intruder mengirimkan 30 request dalam jendela waktu kurang dari 5 milidetik. Dan hasilnya mengejutkan!

Alih-alih mendapatkan 1x status 200 OK dan 29x 400 Bad Request, saya mendapatkan 5x status 200 OK. Ini berarti dari 30 request yang "balapan" masuk ke database:

  1. Thread 1 cek voucher -> Valid -> Masuk proses potong saldo.

  2. Thread 2 cek voucher -> Valid (karena Thread 1 belum selesai update DB) -> Masuk proses potong saldo.

  3. Thread 3 cek voucher -> Valid -> Masuk proses potong saldo.

  4. dst...

Voucher senilai $10 yang seharusnya cuma bisa dipakai sekali, berhasil saya redeem sebanyak 5 kali, memberikan saya total kredit $50. Restriction pada backend berhasil terbypass total karena kegagalan atomisitas.

Alternative Method: The "No-Code" Way (Burp Repeater)

Buat kalian yang malas buka Python atau tuning script, Burp Suite versi terbaru sebenarnya sudah punya fitur bawaan yang ga kalah sadis: Parallel Request Grouping.

Fitur ini memanfaatkan teknik Last-byte Sync secara otomatis via UI. Caranya gampang banget:

  1. Kirim request voucher tadi ke Repeater.

  2. Duplikasi request tersebut sebanyak 20 kali (CTRL + R spamming).

  3. Klik icon + di baris tab, lalu pilih "Create tab group".

  4. Pilih semua tab yang sudah kita duplikasi tadi, beri nama grup (misal: Race), lalu Create.

Nah, ini kuncinya: Di bagian Send options (atas kiri), ubah dari "Send group in sequence" menjadi "Send group in parallel (single connection)".

Kenapa harus single connection? Karena opsi ini yang paling efektif memicu packet collision. Burp akan membuka satu koneksi TCP dan menumpuk semua request di sana, lalu melepas byte terakhir secara serentak.

Tinggal klik Send Group, dan boom! Kalian akan melihat 20 kolom respon muncul bersamaan. Di kasus saya, teknik UI ini juga sukses memicu 3x redeem ganda tanpa perlu menyentuh satu baris kode pun.

Remediation

Untuk remediasi, developer harus menerapkan Pessimistic Locking (misalnya SELECT FOR UPDATE pada database SQL) atau menggunakan operasi atomik. Ini akan memastikan bahwa ketika satu thread sedang membaca/mengubah status voucher, thread lain harus menunggu antrian dan tidak bisa membaca data yang sedang diproses.

Sistem yang sudah di-patch biasanya akan menangani antrian ini dengan benar sehingga hanya 1 request yang sukses, sisanya akan fail.

Real-World Study Cases

Teori tanpa studi kasus nyata seringkali sulit dibayangkan. Berikut adalah daftar laporan asli (Public Disclosure) di HackerOne yang bisa kamu bedah untuk memahami pola serangan ini di berbagai tingkat keparahan:

Low Severity (Logic & UI State):

Medium Severity:

High Severity (Financial & Integrity Impact):

Recommended Reading:

Bagi yang ingin belajar lebih dalam, teknik ini sangat efektif untuk target-target yang melibatkan saldo, kupon, atau limitasi jumlah penggunaan. Happy hacking!