|
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