Signature HMAC Webhook Memerlukan Kontrol Replay
Receiver webhook sering perlu menentukan apakah sebuah HTTP request berasal dari sender yang dikonfigurasi dan apakah payload berubah dalam perjalanan. Keyed message authentication code dapat mendukung keputusan tersebut ketika kedua pihak berbagi secret dan menghitung tag atas byte yang sama.
Properti itu tidak membuat request yang tertangkap menjadi sekali pakai. Jika attacker merekam request valid lalu mengirim kembali material terautentikasi yang sama, tag dapat tetap valid. Ketahanan terhadap replay karena itu harus menjadi bagian dari protokol webhook di sekitar MAC, bukan asumsi yang dilekatkan pada MAC.
Input yang ditandatangani harus didefinisikan secara presisi
HMAC, yang ditetapkan dalam RFC 2104, menggabungkan cryptographic hash dengan secret key untuk menghasilkan message authentication code. Receiver menghitung ulang tag lalu membandingkannya dengan nilai yang dikirim.
Untuk webhook, protokol harus mendefinisikan input terautentikasi tanpa ambiguitas. Konstruksi sederhana dapat mengikat timestamp ke raw request body:
signed_input = timestamp || "." || raw_body
tag = HMAC-SHA-256(secret, signed_input)Delimiter dan encoding merupakan detail protokol. Kedua endpoint harus memakai representasi yang sama.
Menandatangani JSON yang sudah di-parse adalah protokol yang berbeda. Proses parsing dan serialization JSON dapat mengubah whitespace, urutan member, escaping, atau representasi angka. Receiver yang memverifikasi raw body sebaiknya mempertahankan byte tersebut sampai autentikasi selesai.
Aturan yang sama berlaku untuk komponen request tambahan. Jika method, path, content type, atau delivery identifier relevan terhadap keamanan, representasi kanonisnya perlu dimasukkan secara sengaja dan tidak dianggap otomatis tercakup.
MAC yang valid tidak membuktikan freshness
Pertimbangkan satu delivery yang sah:
POST /hooks/payment
timestamp: 1789912800
body: {"event":"invoice.paid","id":"evt_42"}
tag: <valid HMAC>Observer yang dapat menangkap request lengkap mungkin tidak mengetahui secret dan tidak dapat memalsukan body berbeda. Observer tersebut tetap dapat mengirim ulang timestamp, body, dan tag yang sudah tertangkap.
Verifikasi kriptografis dapat berhasil karena byte terautentikasi tidak berubah. Properti yang belum ada adalah freshness.
Timestamp memberi receiver dasar untuk menolak request lama:
abs(receiver_time - signed_time) <= acceptance_windowWindow yang tepat merupakan kebijakan operasional. Nilainya perlu menampung delay delivery dan error clock yang diperkirakan tanpa menjadi terlalu lebar.
Timestamp itu sendiri harus diautentikasi. Memeriksa timestamp yang tidak ditandatangani sambil memverifikasi MAC hanya atas body memungkinkan attacker mengubah nilai freshness tanpa membuat tag tidak valid.
Time window membatasi replay tetapi tidak menghapusnya
Acceptance window lima menit, misalnya, dapat menolak request yang tertangkap setelah lima menit. Kontrol itu tidak menghentikan request yang sama agar tidak diputar ulang beberapa kali selama lima menit tersebut.
Protokol yang membutuhkan duplicate suppression lebih kuat memerlukan nilai yang mengidentifikasi delivery, seperti nonce atau event identifier, serta state di sisi receiver yang mencatat nilai yang sudah diterima selama retention period yang sesuai.
Secara konseptual:
verifikasi MAC
|
v
cek signed timestamp
|
v
cek delivery_id belum pernah terlihat
|
v
catat delivery_id
|
v
proses eventIdentifier juga harus tercakup dalam input terautentikasi. Jika tidak, attacker dapat mengubahnya sambil mempertahankan body yang sudah diautentikasi.
Retention perlu sesuai dengan replay policy. Menghapus identifier sebelum acceptance period terkait berakhir dapat membuka kembali jalur duplikat yang semestinya ditutup oleh store.
Duplicate delivery dan replay berbahaya dapat terlihat sama
Sistem webhook lazim melakukan retry setelah network failure atau response non-success. Receiver karena itu dapat melihat event sah yang sama lebih dari sekali walaupun tidak ada attacker.
Kontrol keamanan dan delivery semantics perlu selaras. Menolak delivery identifier berulang pada authentication boundary cocok untuk protokol yang menetapkan setiap signed delivery sebagai sekali pakai. Pada desain lain, receiver dapat menerima delivery terautentikasi yang berulang tetapi membuat pemrosesan event bersifat idempotent.
Sebagai contoh, operasi database dapat memakai event identifier stabil dari provider sebagai uniqueness key:
event_id: evt_42
insert pertama -> diterima
insert kedua -> sudah diprosesIdempotency melindungi application effect dari pemrosesan duplikat. Mekanisme ini bukan pengganti autentikasi request dan tidak dengan sendirinya memberi batas waktu pada traffic yang tertangkap.
Bandingkan authentication tag tanpa early exit yang bergantung pada data
Setelah menghitung expected tag, receiver sebaiknya memakai constant-time comparison primitive dari platform untuk authentication tag dengan panjang sama, bukan operasi string equality biasa.
Format input perlu divalidasi sebelum perbandingan. Encoding Hex dan Base64 membutuhkan aturan decoding yang ketat, dan nilai malformed sebaiknya menggagalkan autentikasi alih-alih dinormalisasi melalui beberapa representasi yang permisif.
Pemilihan secret juga penting saat key rotation. Jika protokol mendukung key identifier, identifier tersebut sebaiknya memilih candidate secret dalam jumlah terbatas. Mencoba seluruh historical secret tanpa batas memperluas kompleksitas operasional sekaligus kumpulan credential yang masih berguna.
Verifikasi ditempatkan sebelum side effect
Receiver sebaiknya mengautentikasi request dan menerapkan freshness check sebelum melakukan parsing data tidak tepercaya lebih dalam dari yang diperlukan atau menjalankan business action.
Urutan praktisnya:
terapkan request-size limit
|
v
baca raw body
|
v
parse signature metadata secara ketat
|
v
pilih active secret
|
v
verifikasi HMAC
|
v
cek signed freshness data
|
v
terapkan duplicate policy
|
v
parse event dan authorize actionRequest-size limit tetap berguna karena sender yang belum terautentikasi masih dapat menghabiskan bandwidth dan memory sebelum verifikasi kriptografis selesai.
Autentikasi juga tidak menggantikan application authorization. Sender yang valid dapat diizinkan mengirim beberapa event type, sementara endpoint tertentu hanya boleh bertindak pada subset tertentu. Event type, tenant binding, object identity, dan requested state transition tetap memerlukan pemeriksaan pada level aplikasi.
Penanganan kegagalan tidak perlu membuka detail verifikasi
Error yang terlihat dari luar tidak perlu membedakan unknown key identifier, malformed tag, MAC yang salah, stale timestamp, atau nonce berulang. Detail tersebut dapat tetap berada di telemetry yang terlindungi sementara HTTP response sengaja dibuat lebih umum.
Log sebaiknya tidak merekam shared secret atau signature material lengkap. Event identifier, bounded reason code, sender configuration identifier, dan timing data biasanya cukup untuk operasi tanpa mengubah log menjadi arsip credential.
Rate limit dapat mengurangi abuse lebih lanjut, tetapi merupakan kontrol terpisah. Rate limit tidak mengautentikasi request, dan MAC valid tidak menjamin sender akan tetap berada dalam volume traffic yang diharapkan.
Security boundary mencakup protokol verifikasi lengkap
HMAC menyediakan message authentication untuk byte yang dicakup MAC. Ketahanan terhadap replay berasal dari state protokol tambahan: freshness data yang diautentikasi, aturan penerimaan berbatas waktu, serta duplicate tracking atau pemrosesan idempotent ketika diperlukan.
Pemisahan tersebut membuat desain tetap presisi. Receiver dapat menyatakan byte mana yang diautentikasi, berapa lama signed request dapat diterima, kondisi yang dianggap duplikat, dan application effect mana yang terlindungi dari pengulangan. Properti tersebut berbeda satu sama lain dan masing-masing memerlukan mekanisme eksplisit.
Referensi
- IETF, RFC 2104: HMAC: Keyed-Hashing for Message Authentication: https://www.rfc-editor.org/rfc/rfc2104
- IETF, RFC 9421: HTTP Message Signatures: https://www.rfc-editor.org/rfc/rfc9421