Bu makale, bir uygulamanın diğer bir servise gönderdiği veya aldığı webhooks’ların güvenliğini sağlamak için HMAC imza sürecinin doğru bir şekilde nasıl uygulanacağını ele alıyor. Webhooks, uygulamanızın belirli bir etkinliği tetiklediğinde başka bir servise bilgi göndermesine olanak tanır. Güvenilirlik açısından, alıcının yükün gerçekten sizden geldiğini ve yolculuk sırasında değiştirilmediğini doğrulaması önemlidir. Standard cevap, yükü paylaşılan bir gizli anahtarla hash’lemektir: yükü hash’leyin, hash’i isteğin yanında gönderin ve alıcıya tekrar hesaplatın ve karşılaştırın.
Sorun
Sorun
Bu işlem kulağa basit geliyor. Gerçekten de basit, ta ki yanlış byte’ları imzalayana kadar.
Doğru İmzalama
Doğru İmzalama
Temel yöntem:
$body = json_encode($payload);
$signature = hash_hmac('sha256', $body, $secret);$signature‘ı bir başlıkta gönderin (örneğin, X-Signature), ve alıcı, aldığı yük için HMAC’i tekrar hesaplar ve karşılaştırır. Bu kısım neredeyse herkes tarafından doğru yapılır.
Sorun: Ağ Üzerinden Geçen Tam Byte’ları İmzalayın
Sorun: Ağ Üzerinden Geçen Tam Byte’ları İmzalayın
Görünüşte doğru olan ama yanlış bir versiyon:
// Görünüşte doğru ama iki farklı serileştirme gönderiyor
$payload = ['event' => 'order.created', 'total' => 42.00];
$signature = hash_hmac('sha256', json_encode($payload), $secret);
Http::withHeaders(['X-Signature' => $signature])
->post($url, $payload); // bu, $payload'ı dahili olarak tekrar serileştirirHata: json_encode($payload)‘yi bir kere, manuel olarak çağırdınız ve ardından diziyi Laravel’in HTTP istemcisine verdiniz. Bu işlem, istek gövdesini oluştururken dahili olarak tekrar JSON’lar. Bu, iki ayrı serileştirme geçirir. Çoğu zaman bunlar aynı byte’ları üretir, bu yüzden testlerde ve hatta üretimde uzun bir süre çalışır. Fakat bir gün çalışmaz hale gelir – bir float farklı biçimlendirilir, ya da bazı kodlama seçenekleri farklı olur ve imza sessiz bir şekilde uyuşmamaya başlar. Bu takibi zor bir hata çünkü hiçbir hata mesajı vermez; alıcı sadece webhooks’unuzu belirsiz bir sebepten reddetmeye başlar.
Çözüm, bir kerede serileştirmek ve o tam string’i göndermektir:
// Bir kere serileştir, o string'i imzala, o tam string'i gönder
$body = json_encode($payload);
$signature = hash_hmac('sha256', $body, $secret);
Http::withHeaders(['X-Signature' => $signature])
->withBody($body, 'application/json')
->post($url);withBody(), ham string’i olduğu gibi gönderir, istemcinin dizi-JSON aşamasını tamamen atlayarak. Artık imzaladığınız byte’lar ve gönderdiğiniz byte’lar kesinlikle aynı, tesadüfen değil, yapısal olarak.
Alıcı Tarafında Doğrulama
Alıcı Tarafında Doğrulama
Tam byte’ları prensibi, yazma tarafında olduğu gibi, alıcı tarafında da geçerlidir ve burada da yanlış yapmak oldukça kolaydır:
public function verify(Request $request, string $secret): bool
{
$expected = hash_hmac('sha256', $request->getContent(), $secret);
return hash_equals($expected, $request->header('X-Signature', ''));
}İki detayı belirtmek gerekir:
-
$request->getContent(), değil$request->all()ya dajson_encode($request->json()->all()). JSON’u çözümleyip tekrar kodlayarak “okunabilirlik” için yeniden kodlarsanız, alıcı tarafında tam olarak aynı hatayı tekrar yaratmış olursunuz – artık aslında imzalanan ham byte’larla karşılaştırmak yerine tekrar serileştirilmiş bir versiyonla karşılaştırıyorsunuz. -
hash_equals(), değil===. Basit bir string karşılaştırması, ilk farklı byte’ta kısa kesilir ve bu durum, bir saldırganın teorik olarak doğru imzayı bir byte bir byte tahmin etmesi için kullanabileceği zamanlama bilgisi sızdırır.hash_equals(), hangi kısıtlardan bağımsız olarak sabit zamanlı çalışır.
Nereden Geldi Bu
Nereden Geldi Bu
Bu yazıyı, Filament Outbox adlı projem için webhooks kanalı oluştururken yazdım. Bu, Laravel’in yerel Notification sistemini kullanarak Discord, Slack, Microsoft Teams ve imzalı webhook bildirim kanalları ile oluşturulmuş küçük bir Laravel paketidir. Normal bir Notification sınıfı yazın, öğrenilecek yeni API yok. Yönetim uç noktalarını yönetmek, gönderim geçmişini gözden geçirmek ve hataları yeniden denemek için Filament v5 yönetim paneli de mevcuttur, ancak ücretsiz paket herhangi bir Laravel uygulamasında bağımsız olarak çalışabilir.
Sonuç
Sonuç
Eğer yığınınızda webhooks’ları imzalıyorsanız, bu kesin desen için hızlı bir denetim yapmanız faydalı olabilir – elle hazırlanan bir json_encode() sonrasında diziyi bir HTTP istemcisine vermek, çift serileştirme yapmadan fark edilmeden yazılabilecek kolay bir şeydir. Bununla ilgili başkalarının deneyimi olup olmadığını ya da daha temiz bir desen olup olmadığını merak ediyorum – yorumlarda bildirin.
Kaynak: Orijinal Makale


