Files
django-anymail/docs/tips/securing_webhooks.rst
medmunds 1a6086f2b5 Security: rename WEBHOOK_AUTHORIZATION --> WEBHOOK_SECRET
This fixes a low severity security issue affecting Anymail v0.2--v1.3.

Django error reporting includes the value of your Anymail
WEBHOOK_AUTHORIZATION setting. In a properly-configured deployment,
this should not be cause for concern. But if you have somehow exposed
your Django error reports (e.g., by mis-deploying with DEBUG=True or by
sending error reports through insecure channels), anyone who gains
access to those reports could discover your webhook shared secret. An
attacker could use this to post fabricated or malicious Anymail
tracking/inbound events to your app, if you are using those Anymail
features.

The fix renames Anymail's webhook shared secret setting so that
Django's error reporting mechanism will [sanitize][0] it.

If you are using Anymail's event tracking and/or inbound webhooks, you
should upgrade to this release and change "WEBHOOK_AUTHORIZATION" to
"WEBHOOK_SECRET" in the ANYMAIL section of your settings.py. You may
also want to [rotate the shared secret][1] value, particularly if you
have ever exposed your Django error reports to untrusted individuals.

If you are only using Anymail's EmailBackends for sending email and
have not set up Anymail's webhooks, this issue does not affect you.

The old WEBHOOK_AUTHORIZATION setting is still allowed in this release,
but will issue a system-check warning when running most Django
management commands. It will be removed completely in a near-future
release, as a breaking change.

Thanks to Charlie DeTar (@yourcelf) for responsibly reporting this
security issue through private channels.

[0]: https://docs.djangoproject.com/en/stable/ref/settings/#debug
[1]: https://anymail.readthedocs.io/en/1.4/tips/securing_webhooks/#use-a-shared-authorization-secret
2018-02-08 11:38:15 -08:00

110 lines
3.8 KiB
ReStructuredText

.. _securing-webhooks:
Securing webhooks
=================
If not used carefully, webhooks can create security vulnerabilities
in your Django application.
At minimum, you should **use SSL** and a **shared authorization secret**
for your Anymail webhooks. (Really, for *any* webhooks.)
Use SSL
-------
Your Django site must use SSL, and the webhook URLs you
give your ESP should start with "https" (not http).
Without https, the data your ESP sends your webhooks is exposed in transit.
This can include your customers' email addresses, the contents of messages
you receive through your ESP, the shared secret used to authorize calls
to your webhooks (described in the next section), and other data you'd
probably like to keep private.
Configuring SSL is beyond the scope of Anymail, but there are many good
tutorials on the web.
If you aren't able to use https on your Django site, then you should
not set up your ESP's webhooks.
.. setting:: ANYMAIL_WEBHOOK_SECRET
Use a shared authorization secret
---------------------------------
A webhook is an ordinary URL---anyone can post anything to it.
To avoid receiving random (or malicious) data in your webhook,
you should use a shared random secret that your ESP can present
with webhook data, to prove the post is coming from your ESP.
Most ESPs recommend using HTTP basic authorization as this shared
secret. Anymail includes support for this, via the
:setting:`!ANYMAIL_WEBHOOK_SECRET` setting.
Basic usage is covered in the
:ref:`webhooks configuration <webhooks-configuration>` docs.
If something posts to your webhooks without the required shared
secret as basic auth in the HTTP_AUTHORIZATION header, Anymail will
raise an :exc:`AnymailWebhookValidationFailure` error, which is
a subclass of Django's :exc:`~django.core.exceptions.SuspiciousOperation`.
This will result in an HTTP 400 response, without further processing
the data or calling your signal receiver function.
In addition to a single "random:random" string, you can give a list
of authorization strings. Anymail will permit webhook calls that match
any of the authorization strings:
.. code-block:: python
ANYMAIL = {
...
'WEBHOOK_SECRET': [
'abcdefghijklmnop:qrstuvwxyz0123456789',
'ZYXWVUTSRQPONMLK:JIHGFEDCBA9876543210',
],
}
This facilitates credential rotation: first, append a new authorization
string to the list, and deploy your Django site. Then, update the webhook
URLs at your ESP to use the new authorization. Finally, remove the old
(now unused) authorization string from the list and re-deploy.
.. warning::
If your webhook URLs don't use https, this shared authorization
secret won't stay secret, defeating its purpose.
Signed webhooks
---------------
Some ESPs implement webhook signing, which is another method of verifying
the webhook data came from your ESP. Anymail will verify these signatures
for ESPs that support them. See the docs for your
:ref:`specific ESP <supported-esps>` for more details and configuration
that may be required.
Even with signed webhooks, it doesn't hurt to also use a shared secret.
Additional steps
----------------
Webhooks aren't unique to Anymail or to ESPs. They're used for many
different types of inter-site communication, and you can find additional
recommendations for improving webhook security on the web.
For example, you might consider:
* Tracking :attr:`~anymail.signals.AnymailTrackingEvent.event_id`,
to avoid accidental double-processing of the same events (or replay attacks)
* Checking the webhook's :attr:`~anymail.signals.AnymailTrackingEvent.timestamp`
is reasonably close the current time
* Configuring your firewall to reject webhook calls that come from
somewhere other than your ESP's documented IP addresses (if your ESP
provides this information)
But you should start with using SSL and a random shared secret via HTTP auth.