|
3 | 3 |
|
4 | 4 | import six |
5 | 5 | from django.http import HttpResponse |
| 6 | +from django.utils.crypto import constant_time_compare |
6 | 7 | from django.utils.decorators import method_decorator |
7 | 8 | from django.views.decorators.csrf import csrf_exempt |
8 | 9 | from django.views.generic import View |
@@ -41,8 +42,13 @@ def __init__(self, **kwargs): |
41 | 42 | def validate_request(self, request): |
42 | 43 | """If configured for webhook basic auth, validate request has correct auth.""" |
43 | 44 | if self.basic_auth: |
44 | | - basic_auth = get_request_basic_auth(request) |
45 | | - if basic_auth is None or basic_auth not in self.basic_auth: |
| 45 | + request_auth = get_request_basic_auth(request) |
| 46 | + # Use constant_time_compare to avoid timing attack on basic auth. (It's OK that any() |
| 47 | + # can terminate early: we're not trying to protect how many auth strings are allowed, |
| 48 | + # just the contents of each individual auth string.) |
| 49 | + auth_ok = any(constant_time_compare(request_auth, allowed_auth) |
| 50 | + for allowed_auth in self.basic_auth) |
| 51 | + if not auth_ok: |
46 | 52 | # noinspection PyUnresolvedReferences |
47 | 53 | raise AnymailWebhookValidationFailure( |
48 | 54 | "Missing or invalid basic auth in Anymail %s webhook" % self.esp_name) |
@@ -78,8 +84,11 @@ def validate_request(self, request): |
78 | 84 | *All* definitions of this method in the class chain (including mixins) |
79 | 85 | will be called. There is no need to chain to the superclass. |
80 | 86 | (See self.run_validators and collect_all_methods.) |
| 87 | +
|
| 88 | + Security note: use django.utils.crypto.constant_time_compare for string |
| 89 | + comparisons, to avoid exposing your validation to a timing attack. |
81 | 90 | """ |
82 | | - # if request.POST['signature'] != expected_signature: |
| 91 | + # if not constant_time_compare(request.POST['signature'], expected_signature): |
83 | 92 | # raise AnymailWebhookValidationFailure("...message...") |
84 | 93 | # (else just do nothing) |
85 | 94 | pass |
|
0 commit comments