Tagged: browser

Experimental Protocol for Protecting Local Storage from Cross-Site Scripting (XSS)

Update: DO NOT USE THIS. Thanks to @pfrazee for pointing out that I didn’t really protect against Cross-Site Scripting (XSS). The protocol described here fails at its goal. The failure is that an attacker can use XSS to execute the protocol itself and gain access to decrypted data. In other words, this protocol does nothing.

Solution: Use Content-Security-Policy to protect against XSS.

Local storage in the browser is vulnerable to Cross-Site Scripting (XSS) (see: Local Storage). A single mistake in sanitizing HTML or a compromise in the JavaScript supply chain (CDNs, libraries, etc.) can lead to arbitrary JavaScript being able to read local storage and expose any data within.

Assuming that XSS will occur, we can protect data in local storage by encrypting the data. This way, when an attacker gains access to local storage, the data will be protected by encryption.

There are two problems with encrypting local storage. The first problem is where to store the key. The second problem is trusting JavaScript encryption and all the disagreements that leads to.

To address the first problem, we can store the encryption key for local storage in a cookie. We can completely bypass the second problem by encrypting and decrypting server-side.

By storing the encryption key in an HttpOnly, Secure, path-specific cookie, we protect it from XSS attacks. However, the cookie remains vulnerable to Cross-Site Request Forgery (CSRF) (see: Cross-Site Request Forgery (CSRF)). The use of CSRF tokens addresses this vulnerability (see: Synchronizer CSRF Tokens).

What follows is an experimental protocol for securing local storage with encryption, where the encryption key is stored in an HttpOnly, Secure, path-specific cookie protected by CSRF tokens.

Encryption protocol:

1. Client requests “/” from the server.
2. 
Server includes CSRF token in the response.
3. 
Client, using CSRF token, sends POST to “/encrypt”.
4. 
Server encrypts the request payload using crypto key from Cookie header, and responds with ciphertext.
4a. If client request does not contain crypto key in Cookie header, the server generates new crypto key and includes it in “Set-Cookie” header in the response (HttpOnly, Secure, path=/decrypt and path=/encrypt).
5. Client stores ciphertext response in local storage.

Decryption protocol:

1. Client requests “/” from the server.
2. Server includes CSRF token in the response.
3. Client retrieves ciphertext from local storage.
4. Client, using CSRF token, sends POST to “/decrypt”.
5. Server decrypts the request payload using crypto key from Cookie header, and responds with plaintext.
5a. If client request does not contain crypto key in Cookie header, server responds with a failure status code of some kind.
6. Client uses plaintext response and discards it when no longer needed.

A proof of concept demo here: Node.js Github Gist.

* the demo does not set the Cookie to be Secure, but that’s just to keep the demo simple.

 

Advertisement