/ax1500-crypto-client

Python client for the encrypted-over-http web UI on the TP-Link AX1500.

Primary LanguagePythonMIT LicenseMIT

Let's Not Encrypt

A brief tale of custom encryption on the TP-Link AX-1500 WiFi 6 Router.

Background

I own a TP-Link AX-1500, and thought it would be fun to poke around the web UI a bit. To my surprise, despite running over HTTP, the request and response bodies appeared to be encrypted.

Encrypted and Signed Request Body

sign=5d1e21103ba1df32ca541e5d359289d46b0e060c47669af6c129e933f6614086f61ed87cc627159d165a9153ab4c0963e34ae95bb31dbbaade202c65cb9ef818&data=wsouR8JC%2FEdXO8kYEJFiZsfO8tfxyY%2BS3rg9h7MbUh9Il9NgjmlDhXfwSApzaxkFzHbStjxQFkG27eosqqYyx4WOwLGTFfTvn%2FRPpspuvil7eN4%2Bp5v9cwY4Oq92RoPr

Encrypted Response Body

{"data":"t6NDs1j3rICWw7rdpoe1rVGk+Eq5dVUrsibKtd5WgN3trF1LTj6S5QI+O75ZR5L/oy0C7gRrNmUP/9cp6tdK898KlQAwuaYruTBo5TH1MrN6pTVoCn764xveGPVRPov8JXk6ZPT2TnEY8J0JdKTKlWQg7NSKGA+Uf86IdA7+nlnWbxvdsdNx97oVt6DvxgpO"}

This put a damper on understanding the web UI, so I decided to figure out how this crypto was implemented.

If you're lazy and just want something to hack on, I authored a Python client which you can use to login to your router, send encrypted commands, and decrypt the responses.

NOTE: This repository does not include any crypto keys.

For those, you'll have to download the publicly-available firmware for the TP-Link AX-1500, or bust out your l33t hacker skills and solder onto some through-holes. (More on that in a later.)

Custom Crypto is Best Crypto

I suspect they were trying to do something like this:

  • client generates a random session key
  • client encrypts the session key and login password with the router's public key
  • client signs the request with the router's second public key
  • router validates the request, and starts using the session key

In this scenario, one might imagine random session keys would be desired, along with unique keypairs per device.

But, this is how it actually works:

  • client generates a random session key, but uses a hard-coded key anyway
  • client encrypts the login password with the hard-coded public key
  • the corresponding private key ships with the public, unencrypted firmware image
  • client "signs" the request with the router's second public key, but signature payloads can be sniffed and replayed
  • router validates the request, and starts using the hard-coded session key

Where are the Keys?

1024-bit RSA Keypair

The hardcoded public and private exponents are defined in a configuration file on disk:

/etc/config/accountmgnt 

This can be accessed either by downloading the firmware image from the TP-Link website and extracting the squashfs image, or via the root shell so kindly provided over UART.

On the board, there are 4x unpopulated througholes, providing an auth-free root shell over 115200-baud UART.

512-bit RSA Keypair

The public and private exponents are generated by the router, and are not contained in the firmware image.

Once you have your shell, you can read the public and private exponents from this file on disk:

/tmp/rsakey

AES Key and IV

The hardcoded AES key and IV are defined in this file on disk:

/www/webpages/js/libs/tpEncrypt.js

Please see the Python client PoC for an example of how to parse out the key and IV.

PoC Script

The ax1500-poc.py script implements a client for the custom crypto, and demonstrates logging into the router, issuing an encrypted command, and parsing the encrypted response.

This includes parsing the AES key and IV out of the router-provided JS, and requesting the RSA public keys from the router.

Happy Hacking :)