A brief tale of custom encryption on the TP-Link AX-1500 WiFi 6 Router.
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.
sign=5d1e21103ba1df32ca541e5d359289d46b0e060c47669af6c129e933f6614086f61ed87cc627159d165a9153ab4c0963e34ae95bb31dbbaade202c65cb9ef818&data=wsouR8JC%2FEdXO8kYEJFiZsfO8tfxyY%2BS3rg9h7MbUh9Il9NgjmlDhXfwSApzaxkFzHbStjxQFkG27eosqqYyx4WOwLGTFfTvn%2FRPpspuvil7eN4%2Bp5v9cwY4Oq92RoPr
{"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.)
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
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.
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
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.
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 :)