Do you know how vulnerable you can be when you leave your computer unlocked for five minutes? Apart from pranks you may get subjected to, stealing of user credentials and session hijacking is major issue. What if the attacker has took a photo of your session cookies such as “JSESSIONID” of your company web server? Then, he can plant that on his machine, and act as yourself. Specially tokens such as OAuth 2.0 authorization tokens, refresh tokens are vulnerable since those may get passed around via HTTP query parameters. So, a HTTP access logs may expose of a reverse-proxy may expose these tokens in plain-text.
The Token Binding Protocol specification has provided a long-waited need for securing the security tokens such as the session cookies, OAuth2.0 access tokens from session hijacking and replay attacks by malicious attackers. How does it do that? It provides a mechanism to bind these security tokens to your TLS connection between your browser/client and the server. Now, the security tokens can only be used through that TLS connection. Now, the session hijackers will not be able to use those tokens since they do not possess the original TLS connection that was used to establish the connection between the client and the server.
Security Token: A token such as session cookies (JSESSIONID), OAuth2.0 access/refresh tokens, authorization codes.
Token Binding ID (TB_ID): A generated Token Binding ID that is unique to a given TLS connection
Client: A browser or a TLS client with support for Token Binding
Server: A web server that is exposed via TLS with support for Token Binding
Sec-Token-Binding: The HTTP header used for sending the Token Binding Message. This includes the TB_ID.
For Token Binding to work, both Client and Server need to have support for it. Token Binding is implemented as a TLS extension. During the TLS hand-shake, the client and server negotiate on what token binding versions and key parameters the client and server support. Then, both will agree on enabling token binding support for subsequent TLS requests.
After the TLS-handshake, the first TLS request to the server is sent — at this point, the client creates a Token Binding ID using a public-private key-pair that is generated at client-side specifically for this TLS connection. This ID will be sent along with the TLS request via a header named “Sec-Token-Binding”. Now, any security token that is issued by the server can be bound to this Token Binding ID. When the client send the next request that use this security token, the Token Binding Message (TB_MSG) gets added to it which contains the unique TB_ID. The server will verify whether the Token Binding ID in the request matches with the original TB_ID the security token was issued to. Since TB_ID is unique for each TLS connection, session hijackers won’t be able to use the hijacked token.
Token Binding binding specification is still in draft status (Aug 2017), and therefore, only a hand-full of clients and servers has support.
- Google Chrome is the only browser with support for Token Binding.
- Apache HTTPD and NGINX reverse proxies has modules that implement this. HTTPD has mod_token_binding. NGINX has ngx_token_binding which is developed by Google engineers.
In this article, let’s see how we can test out Token Binding. I’m going to use Google Chrome (60.0.3112.90) and Apache HTTPD’s mod_token_binding module. Following diagram shows how it works.
- The browser initiates a TLS connection to the reverse-proxy.
- During the TLS handshake’s ClientHello and ServerHello, the client and server exchange the token binding information. And, decide on the Token Binding version, key length, key parameter to use etc.
- TLS handshake finishes.
- Now, the Browser generates a private-public key-pair for this TLS connection. It sends this public key and a signed EKM (Exported Keying Material) to prove that the client indeed holds the private key of the sent public key. This is sent in the very-first TLS HTTP request in the Sec-Token-Binding HTTP header. We call this Token Binding Message. The TB_ID is included inside. More details on this on a later post.
- When the request hit the reverse-proxy, it will extract the Token Binding ID. There are two token binding types. For now, let’s discuss only on the single-party case — Provided Token Binding — in which only the browser and the authorization server talks with each other. (In federated usecases “Referred Token Binding” is used when the acquired security tokens by the client need to be presented to a different server — a typical OAuth2.0 access token provider and consumer scenario).
- The reverse proxy send the Provided Token Binding ID over the Sec-Provided-Token-Binding-Id HTTP header to the authorization server.
- If Authorization server is issuing a security token, it can bind that token to the Provided Token Binding ID.
- Now, when the client tries to use that security token, it needs to use the same TLS connection it has used before. Otherwise, the new TLS connection’s Provided TB_ID will not match with the bound tokens TB_ID.
Set-up Google Chrome with Apache HTTPD for Token Binding
- Enable Google Chrome’s Token Binding support. — Open a new tab in Google Chrome, and head over to URL: chrome://flags/#enable-token-binding.
- Setting up Apache HTTPD requires a bit of work — But the good news is the developer had thought through and provided us with a Dockerfile to try it out in a single shot. Here’s what you need to do.
- Pre-requisite: Install Docker and Git if you do not have it already. #TODO
- Clone the Git repo zmartzone/mod_token_binding, and go to test/docker directory.
git clone https://github.com/zmartzone/mod_token_binding.git && cd mod_token_binding/test/docker
- Now, you need to build and run the docker image.
make build run
If you do not have make installed in your machine, then you can directly use docker commands to build and run.
docker build -t $(TAG) . # Do not forget the . in the end.
docker run -p 8080:8080 -p 4443:4443 -it $(TAG) /bin/bash -c “$(TARGET_DIR)/bin/apachectl start && tail -f $(TARGET_DIR)/logs/error_log”
- If all goes smoothly, you should have a Apache HTTPD instance running in your local machine. Aha!
Before we go into invoke the Apache, let’s discuss what was there in that Dockerfile. The commands should be pretty explanatory for a Unix user. But overall, it does the following:
- It is built on top of Ubuntu docker image. At first, it installs a common set of utilities.
- For Token Binding to function, we need to do a one-liner patch to OpenSSL library. That is what happens in lines 13–22.
- HTTPD also needs a modification since the changes are not merged to master yet. You see that in lines 23–32.
- Then, it builds the token_binding and mod_token_binding git repos.
- Finally, it copies the httpd-ssl.conf and httpd.conf config files into the docker image.
This docker image directs any request it receive to http://httpbin.org/headers. This web location simply present you with a web page that prints all the HTTP request headers it received. So, it is a good candidate to test token binding.
Invoking Apache Server
Open a new tab in Google chrome and issue a request to https://localhost:4443/.
- You should get a HTML page with a list of HTTP headers. Check below:
"Accept-Encoding": "gzip, deflate, br",
Find the Sec-Provided-Token-Binding-Id HTTP request header. Skip Sec-Token-Binding-Context. It is not coming from a specification as I see. An older draft of Token Binding with TTRP mentioned the use of Token-Binding-Context, but it is no longer used.
Following logs can be seen in the apache debug logs (/usr/local/logs/error_log) (truncated for brevity) for a single TLS request.
tb_is_enabled: Token Binding is enabled on this connection: key_type=2!
tb_get_decoded_header: Token Binding header found: Sec-Token-Binding=AIkAAgBBQJhKnfsT-[truncated ...]
tb_clean_header: removing incoming request header (Sec-Token-Binding: AIkAAgBBQJhKnfsT-[truncated ...])
tb_post_read_request: verified Token Binding header!
tb_set_var: set Token Binding ID environment variable: Sec-Provided-Token-Binding-ID=AgBBQJhKnfsT-[truncated ...]
tb_set_var: set Token Binding ID header: Sec-Provided-Token-Binding-ID=AgBBQJhKnfsT-[truncated ...]
tb_draft_campbell_tokbind_ttrp: no referred token binding ID found
tb_set_var: set Token Binding ID environment variable: Sec-Token-Binding-Context=AA0Cr[truncated ...]
tb_set_var: set Token Binding ID header: Sec-Token-Binding-Context=AA0Cr[truncated ...]
- The logs show that Token Binding is enabled for this request.
- It found the Sec-Token-Binding header sent by the client.
- Then, it has added the Sec-Provided-Token-Binding-ID to the back-end request.
- If you are not seeing the Sec-Provided-Token-Binding-Id header, check what the apache logs say.
Does it say the following?
[token_binding:debug] tb_post_read_request: enter
[token_binding:debug] tb_is_enabled: Token Binding is not enabled by the peer
“Token Binding is not enabled by the peer” may mean that you have not enabled Token Binding support in Google Chrome. Go to chrome://flags/#enable-token-binding and do that, and retry the TLS request. No need to restart apache.
That’s about it. I hope this gives a brief introduction into how Token Binding functions and how to get it to work.
Do leave a comment below if you have questions! Thanks.