CVE-2018-6596
Description
webhooks/base.py in Anymail (aka django-anymail) before 1.2.1 is prone to a timing attack vulnerability on the WEBHOOK_AUTHORIZATION secret, which allows remote attackers to post arbitrary e-mail tracking events.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A timing attack vulnerability in django-anymail before 1.2.1 allows remote attackers to bypass WEBHOOK_AUTHORIZATION secret validation and post arbitrary email tracking events.
Vulnerability
A timing attack vulnerability exists in the webhooks/base.py module of django-anymail prior to version 1.2.1 [1]. The flaw affects the verification of the WEBHOOK_AUTHORIZATION secret, which is used to authenticate incoming webhook requests from email service providers. Due to a non-constant-time comparison, an attacker can determine the secret byte-by-byte by measuring response times, bypassing authentication for webhook endpoints [2].
Exploitation
An attacker requires network access to the webhook endpoint and does not need prior authentication. By sending a series of crafted HTTP requests with guessed Authorization header values and measuring the server's response time, the attacker can incrementally leak the secret. Once the secret is recovered, the attacker can craft valid requests to the webhook URL [2][4].
Impact
Successful exploitation allows the remote attacker to post arbitrary email tracking events to the webhook endpoint. This can lead to injection of false delivery, open, click, or other engagement events, potentially corrupting email analytics, triggering unintended application logic, and enabling further attacks if the webhook data is processed without additional validation [1][2].
Mitigation
Upgrade to django-anymail version 1.2.1 or later, which was released on 2018-02-03 and fixes the timing attack by implementing a constant-time comparison for the secret [3][4]. No workaround is available for earlier versions; users on unpatched versions should update immediately. This CVE is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog.
- NVD - CVE-2018-6596
- CVE-2018-6596 - GitHub Advisory Database
- GitHub - anymail/django-anymail: Django email backends and webhooks for Amazon SES, Brevo, MailerSend, Mailgun, Mailjet, Mailtrap, Postmark, Postal, Resend, Scaleway TEM, SendGrid, SparkPost, Unisender Go and more
- advisory-database/vulns/django-anymail/PYSEC-2018-7.yaml at main · pypa/advisory-database
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
django-anymailPyPI | < 1.2.1 | 1.2.1 |
Affected products
2- Range: <1.2.1
Patches
2db586ede1fbbSecurity: prevent timing attack on WEBHOOK_AUTHORIZATION secret
1 file changed · +12 −3
anymail/webhooks/base.py+12 −3 modified@@ -2,6 +2,7 @@ import six from django.http import HttpResponse +from django.utils.crypto import constant_time_compare from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.generic import View @@ -40,8 +41,13 @@ def __init__(self, **kwargs): def validate_request(self, request): """If configured for webhook basic auth, validate request has correct auth.""" if self.basic_auth: - basic_auth = get_request_basic_auth(request) - if basic_auth is None or basic_auth not in self.basic_auth: + request_auth = get_request_basic_auth(request) + # Use constant_time_compare to avoid timing attack on basic auth. (It's OK that any() + # can terminate early: we're not trying to protect how many auth strings are allowed, + # just the contents of each individual auth string.) + auth_ok = any(constant_time_compare(request_auth, allowed_auth) + for allowed_auth in self.basic_auth) + if not auth_ok: # noinspection PyUnresolvedReferences raise AnymailWebhookValidationFailure( "Missing or invalid basic auth in Anymail %s webhook" % self.esp_name) @@ -77,8 +83,11 @@ def validate_request(self, request): *All* definitions of this method in the class chain (including mixins) will be called. There is no need to chain to the superclass. (See self.run_validators and collect_all_methods.) + + Security note: use django.utils.crypto.constant_time_compare for string + comparisons, to avoid exposing your validation to a timing attack. """ - # if request.POST['signature'] != expected_signature: + # if not constant_time_compare(request.POST['signature'], expected_signature): # raise AnymailWebhookValidationFailure("...message...") # (else just do nothing) pass
c07998304b4aSecurity: prevent timing attack on WEBHOOK_AUTHORIZATION secret
1 file changed · +12 −3
anymail/webhooks/base.py+12 −3 modified@@ -3,6 +3,7 @@ import six from django.http import HttpResponse +from django.utils.crypto import constant_time_compare from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.generic import View @@ -41,8 +42,13 @@ def __init__(self, **kwargs): def validate_request(self, request): """If configured for webhook basic auth, validate request has correct auth.""" if self.basic_auth: - basic_auth = get_request_basic_auth(request) - if basic_auth is None or basic_auth not in self.basic_auth: + request_auth = get_request_basic_auth(request) + # Use constant_time_compare to avoid timing attack on basic auth. (It's OK that any() + # can terminate early: we're not trying to protect how many auth strings are allowed, + # just the contents of each individual auth string.) + auth_ok = any(constant_time_compare(request_auth, allowed_auth) + for allowed_auth in self.basic_auth) + if not auth_ok: # noinspection PyUnresolvedReferences raise AnymailWebhookValidationFailure( "Missing or invalid basic auth in Anymail %s webhook" % self.esp_name) @@ -78,8 +84,11 @@ def validate_request(self, request): *All* definitions of this method in the class chain (including mixins) will be called. There is no need to chain to the superclass. (See self.run_validators and collect_all_methods.) + + Security note: use django.utils.crypto.constant_time_compare for string + comparisons, to avoid exposing your validation to a timing attack. """ - # if request.POST['signature'] != expected_signature: + # if not constant_time_compare(request.POST['signature'], expected_signature): # raise AnymailWebhookValidationFailure("...message...") # (else just do nothing) pass
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-hxf9-7h4c-f5jvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-6596ghsaADVISORY
- www.debian.org/security/2018/dsa-4107ghsavendor-advisoryx_refsource_DEBIANWEB
- bugs.debian.org/889450ghsax_refsource_CONFIRMWEB
- github.com/anymail/django-anymail/commit/c07998304b4a31df4c61deddcb03d3607a04691bghsax_refsource_CONFIRMWEB
- github.com/anymail/django-anymail/commit/db586ede1fbb41dce21310ea28ae15a1cf1286c5ghsax_refsource_CONFIRMWEB
- github.com/anymail/django-anymail/releases/tag/v1.2.1ghsax_refsource_CONFIRMWEB
- github.com/anymail/django-anymail/releases/tag/v1.3ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/django-anymail/PYSEC-2018-7.yamlghsaWEB
News mentions
0No linked articles in our index yet.