Prevent Forwarded header spoofing with HTTP message signature
The Forwarded
HTTP header has been introduced in RFC7239 from June 2014. It “defines an HTTP extension header field that allows proxy components to disclose information lost in the proxying process, for example, the originating IP address(…)”.
However there is not any mechanism to protect subsequent component against spoofing. Indeed, if your subsequent component, let’s say an API, is exposed on Internet, anyone can forge a HTTP Forwarded
header. If your API is always behind your proxy, it has to remove/replace the original Forwarded
header. It becomes even more complex if you have multiple proxies.
What could you do to mitigate this security issue ? You could1:
- restrict access to subsequent components with network filtering (firewall, private network, …)
- allow the
Forwarded
header only for a given set of whitelisted IP - identify and trust your proxy using mTLS, API key,…
These solutions have a few drawbacks, especially if you have multiples proxies in cascade. However we could think about another solution based on an upcoming RFC (still in draft): the “HTTP Message Signatures” (draft-ietf-httpbis-message-signatures). As explained in the abstract, it “supports use cases where the full HTTP message may not be known to the signer, and where the message may be transformed (e.g., by intermediaries) before reaching the verifier”, which is the case of the Forwarded
header.
How does it work ? The idea is that the “verifier”, basically our subsequent component (for instance an API) will check the integrity of the Forwarded
header with a digital signature of this header stored in two different headers named Signature
and Signature-Input
. The Signature-Input
contains the metadata of the message signature, in a nutshell: a signature identifier, the signed components (headers, method, uri,…) and some parameters (algorithm used to sign, key identifier, creation date and expiration date). In our case, it will only contain the component derived from the Forwarded
header:
Signature-Input: proxy_signature=("forwarded")\
;created=1618884480;expires=1618884540;keyid="my-proxy-key-rsa-example"\
;alg="rsa-v1_5-sha256"
The Signature
header contains the signature of the (well formated) components using the private key.
Signature: proxy_signature=:G1WLTL4/9PGSKEQbSAMypZNk+I2dpLJ6qvl2JISahlP31OO/QEUd8/\
HdO2O7vYLi5k3JIiAK3UPK4U+kvJZyIUidsiXlzRI+Y2se3SGo0D8dLfhG95bKr\
(...)
M9P7WaS7fMGOt8h1kSqgkZQB9YqiIo+WhHvJa7iPy8QrYFKzx9BBEY6AwfStZAs\
XXz3LobZseyxsYcLJLs8rY0wVA9NPsxKrHGA==:
Therefore, the signature generated by our proxy to prove the Forwarded
header validity will be used by the subsequent component (aka our API) to check for instance the origin IP. Obviously, the proxy private key must remain a secret and our API MUST know the proxy’s public key. This mechanism could be use by multiple proxies in cascade to sign their message transformations.
But… I will close this post with an important disclaimer: this (probably-not-new) idea came to me while I was reading the draft written by Annabelle Backman, Justin Richer and Manu Sporny. Indeed, the example above is coming from the section “4.3. Multiple Signatures” of the draft. Furthermore, I didn’t test any implementation and there are a lot of security concerns to consider before going further with this fun but experimental feature.
-
Some of the solutions described here are coming from the following Security StackExchange question: “How to prevent spoofing of X-Forwarded-For header?” ↩