Blog

MITM and Why JS Encryption is Worthless

You build this great web app that is loaded with JavaScript-based features with a spectacular AJAX setup where content is being pulled instantly as the user wants it. The application is real work of art.

Your development version works flawlessly and your client loves the new website, but, as always happens, they want a few changes. This button gets moved from the bottom of the page to the top of the page. The order of that menu gets rearranged. Every web designer has experienced these general design and layout changes, which take a bit of time to complete, but are technologically easy and take client satisfaction to a whole new level.

The client then asks about security, and how their customer information is protected. They had heard about a friend’s website getting hacked and want to make sure it doesn’t happen to their new beauty. You tell them about how the server can only be accessed ftp connection via cPanel and how you used this great input filtering class so bad stuff can be uploaded. The client misunderstands your conversion of programming jargon into “Real English”, and infers that all data their customers send.

You know the correct answer is to serve the website over HTTPS and use TLS to encrypt data between the customer’s browser and the server. The problem with doing that with this particular client is they went cheap on hosting, and use one of those ultra-discount co-hosting sites, so to deploy TLS on their site would add $10 a month to their hosting bill, in addition to the cost of the certificate, setup and annually updating the certificate. You know the client is not going to like this increased expense so you run through every option you can think of to protect customer data between the browser and the server. Having done all this great work with JavaScript already, the obvious solution is to use JavaScript to encrypt the customer data in the browser and then just decrypt it on the server.

WRONG!

First, encryption via JavaScript in the browser is nearly worthless. There is a single, very limited situation where encryption can be performed in the browser, and I will discuss that at the end. However, in general, JavaScript based encryption provides no real level of security. It does not matter how good of an algorithm you use, or the key length or whether you use RSA/Asymmetric encryption or AES/symmetric encryption. JavaScript based encryption provides no real level of security.

The weakness in every situation revolves around the classical man-in-the-middle (MITM) attack. A hacker filters the connection between your client’s server and the customer’s browser, making changes as the hacker sees fit, and capturing all data they want.

This is how it would work:

  1. Visitor connects to the internet through a compromised router (think encrypted coffee house wi-fi)
  2. Hacker monitors the request data going through router and flags the Visitor’s connection as an attack point
  3. Visitor requests a page on the standard, http domain
  4. Hacker intercepts request and passes it along unadultered after recording all the data tied with the request (like the URL and cookies)
  5. Server hosting domain builds up web page normally and sends it back to Visitor
  6. Hacker intercepts the response html and makes a change to remove the JavaScript encryption mechanism and records any encryption keys
  7. The Visitor gets the web page which looks perfectly normal and proceeds to enter their address, credit card or sensitive data in a form and send it back
  8. Hacker captures this sensitive data then mimics the JavaScript encrypt mechanism and forwards on the correctly encrypted data to the server
  9. The server hosting the domain decrypts the data and continues on, never realizing someone intercepted the data

This general methodology will work for any situation where a response is served over http, without the protections offered by HTTPS via TLS. Unless the HTML response and all JavaScript associated with the browser-based encryption mechanism is served over TLS, there is no guarantee that the end user received correct algorithm, ran the algorithm and sent only encrypted data back to the server.

This guarantee cannot be short-cutted by serving the JavaScript files over HTTPS, but not the original HTML content, as a hacker would either remove the JavaScript or simply change URL of the JavaScript files to a version they controlled. Serving the HTML content via HTTPS but not the JavaScript allows the Hacker to modify the JavaScript during transit and will create mixed-content errors for the user when the browser sees content being served over HTTP and HTTPS.

The Crux

The crux of the problem with encryption via JavaScript is disseminating an encryption key that only the user can create and keeping the key local to the user. Assuming the encryption key can be securely disseminated to the user via a secondary channel, like email, SMS or even physically off-line, how do you keep the key only with the user when they need to use the key on various pages of the website.

The short answer is you cannot do it.

Without HTTPS, the developer does not control the authenticity of the content being delivered. If you cannot control the authenticity of the content being delivered, you have no way to make sure additional methods of data extraction are not coupled with your intended methods.

Hashing is not Encryption

Hashing is generally thought of as one-way encryption, while symmetric and asymmetric are viewed as two-way encryption. Hashing, however, has the same issues as two-way encryption when used in the browser. In order for it to provide any value, the entire connection has to occur over TLS; generally mitigating the value that hashing hoped to create.

A few months ago, after giving a talk at Atlanta PHP on API Security, I was asked about the concept of hashing a password in browser then transmitting the digest (hexadecimal version of the hash) to the server and querying the database for the digest. After having him break down exactly what was happening, he realized that the whole process still must occur over TLS and it provided no more security than transmitting the raw password to the server and having the server do the hashing. From an attack standpoint, the digest simply becomes the password, and due to performance issues of running JavaScript hashing on a variety of platforms, users will only accept the time delay of certain hashing algorithms and a certain number of iterations of the give algorithm.

The gentleman next suggested using a 2-fator key as the salt for the hash and sending this new digest to the server. This makes the situation actually less secure, because in order to continually validate the password, the server must store it the password in plain text (or a symmetrically encrypted, which is only marginally better). If/when the database is hacked; all the passwords are then immediately compromised, rather than the significant delay using current key-lengthening techniques with robust hashing algorithms.

I have actually seen another situation where hashing in the browser reduced the overall security of the application. In this circumstance, the username was concatenated to the password, hashed, and the digest was sent to the server for validation. The app did not even send the raw username, it simply sent the digest. The digest was then queried in the database and whichever user happened to have that digest as their password became the authenticated user. I should correct that, the active user. Authentication is a much too strong of a word to describe what was happening. This methodology created a significant reduction in entropy of the user credentials, allow for real chances of digests collisions where User B has the same credentials as User A, and therefore the system thinks User B is User A.

Minimal Value

At the very beginning I mentioned one particular situation where JavaScript encryption (or hashing for that matter) has some minimal value. When the server and the browser connects 100% over HTTPS, and all the content is encrypted during transmission and authenticated at the user-end, but the JavaScript in the browser must communicate with a third-party server which does not run support TLS. In this situation, JavaScript can be trusted to securely encrypt the data being sent over HTTP to the third-party server which can securely decrypt the data. This whole situation only makes sense if the third-party does not support TLS, but your server supports it completely. I have seen this setup once in a payment processing situation.

LivingSocial applies the third-party server principle to their internal subnet. The browser receives everything over TLS and uses asymmetric encryption to encrypt their customer’s credit card data. The browser then posts this encrypted data to the LivingSocial domain, which really is a Network Access Point (NAT) for their internal subnet. The data is then directed all the way to their gateway processor (BrainTree), without ever being decrypted within their subnet. This effectively provides their customers full end-to-end encryption of their credit card data, without having to deal with redirects and other tricks common in payment processing industry.

JavaScript based hashing has a different situation where value can be created; weakening brute-force attacks. As I mentioned earlier, hashing public form data prior to submitting the data, can increase the cost of form spam to spammers, though the hash can be validated on the server at minimal cost.

Summary

Do not expect JavaScript to impart any security in your web application. Encryption and hashing in the browser, for the purposes of security, is a pointless task, and simply results in another situation of theatrical security.

Security Theater Coined: https://www.schneier.com/crypto-gram/archives/2003/1115.html

Living Social Payment Processing: https://www.braintreepayments.com/blog/client-side-encryption/