← Back to Feed CACHED · 2026-10-02 10:18:43 · CACHE_KEY CVE-2026-3227
CVE-2026-3227 · CWE-78 · Disclosed 2026-03-16

TP-Link TL-WR802N/WR841N/WR840N Authenticated Command Injection via Config Import

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

It's like finding a lockpick vulnerability on a padlocked garden shed — you already need the key and you have to be standing in the yard

CVE-2026-3227 is an OS command injection (CWE-78) in the configuration backup/restore mechanism of three TP-Link consumer routers: TL-WR802N v4 (firmware < V4_260304), TL-WR841N v14 (< V14_260303), and TL-WR840N v6 (< V6_260304). An attacker who already holds admin credentials and is on the adjacent LAN/WiFi can download the encrypted config blob, decrypt it, inject a shell command (capped at 15 characters) into the X_TP_IfName XML field, re-encrypt it using QEMU-hooked native binaries, upload it, and trigger execution via the port-trigger subsystem. The injected command runs as root. A payload like ";reboot;" survives factory reset and creates a permanent bootloop, effectively bricking the device.

The CNA-assigned CVSS 3.1 of 6.8 MEDIUM is accurate to the *technical* attack surface — AV:A/AC:L/PR:H already prices in adjacent-only reach and admin-level authentication. TP-Link's own CVSS 4.0 re-score of 8.5 HIGH inflates the picture by not adequately weighting how many compound prerequisites an attacker must clear. For an enterprise fleet, the score overstates risk further because these are $15–25 consumer 802.11n routers with near-zero presence in managed corporate environments. No active exploitation exists, no KEV listing, and the EPSS of 1.8% reflects fringe probability. The vendor severity is fair for home users who leave default credentials in place; for an enterprise security team it is generous.

"Admin-auth + adjacent-only command injection on a $20 consumer router is backlog material for enterprise teams."
02 · The Attack Path

6 steps from start to impact.

STEP 01

Gain adjacent network position

The attacker must be on the same LAN or WiFi segment as the target router's management interface. The CVSS vector specifies AV:A — there is no internet-reachable attack surface. For home networks this means physical proximity or prior compromise of a LAN host; for enterprise branches it means the attacker is already post-initial-access.
Conditions required:
  • Physical proximity to the target WLAN or wired LAN
  • OR prior compromise of a host on the same segment
Where this breaks in practice:
  • Enterprise networks segment management planes; SOHO routers are rarely on managed VLANs
  • Attacker must already be inside the perimeter — this is a post-compromise escalation step
Detection/coverage: Wireless IDS (e.g., Cisco CleanAir, Aruba WIPS) detects rogue associations. NAC solutions block unauthorized LAN access.
STEP 02

Authenticate as router admin

The attacker needs valid admin credentials for the router's web interface (default admin/admin on these models). CVSS marks this PR:H. While many SOHO units ship with default creds, an enterprise-managed device should have changed them. The web UI is HTTP-only on these models, so LAN-sniffing could capture credentials if they are entered during the session.
Conditions required:
  • Knowledge of admin password (default or harvested)
  • HTTP access to the router management port (typically :80)
Where this breaks in practice:
  • If the admin password has been changed from default, brute-forcing is slow against embedded httpd rate limits
  • Enterprise deployments that exist at all typically provision non-default credentials via template
Detection/coverage: Failed login attempts visible in router syslog (if forwarded). HTTP credential sniffing detectable by NIDS watching for cleartext auth on management subnets.
STEP 03

Download and decrypt configuration blob

The authenticated attacker navigates to the router's backup/restore page and downloads config.bin. This file is DES-ECB encrypted with the known static key 478DA50BF9E3D2CF, followed by zlib compression. Decryption is trivial using the PoC's Python tooling or any DES library.
Conditions required:
  • Authenticated admin session from Step 2
  • Knowledge of the static DES key (publicly documented)
Where this breaks in practice:
  • Requires Python + pycryptodome on the attacker's machine — not a significant barrier
Detection/coverage: Configuration download generates an HTTP GET to /cgi/conf.bin — log monitoring or IDS signatures can flag this.
STEP 04

Inject payload into X_TP_IfName field

The attacker modifies the decrypted XML config, inserting a command (max 15 characters) into the X_TP_IfName field used by oal_pt_addPortTrigger. The function passes this value unsanitized into an iptables shell invocation. The 15-character limit severely constrains payloads — ";reboot;" (10 chars) causes a persistent bootloop; more complex payloads like reverse shells are nearly impossible without staged delivery.
Conditions required:
  • Decrypted config from Step 3
  • Payload fitting within the 15-character buffer
Where this breaks in practice:
  • 15-character limit makes meaningful post-exploitation extremely difficult
  • Downloading a second-stage payload or opening a reverse shell in ≤15 chars requires creative chaining that may not be reliable across firmware builds
STEP 05

Re-encrypt and upload malicious config

The PoC uses QEMU user-mode emulation (qemu-mipsel) to hook the router's native httpd binary and call the original encryption routines, producing a valid config.bin. The attacker then uploads it to /cgi/confup. A simpler Python-only re-encryption may also work since the DES key is static, but the PoC author found the original binary's wrapper necessary for header/checksum validity.
Conditions required:
  • QEMU MIPS cross-compilation toolchain on attacker machine
  • Extracted firmware filesystem (via binwalk)
  • Continued authenticated session
Where this breaks in practice:
  • QEMU + cross-compiler setup is a non-trivial environment requirement — this is not a one-click Metasploit module
  • The encryption hook approach is fragile across firmware versions
Detection/coverage: Configuration upload generates an HTTP POST to /cgi/confup — an unusual admin action that can be baselined and alerted on.
STEP 06

Trigger command execution

The attacker adds or modifies a port-trigger rule via the web interface. When the router's oal_pt_addPortTrigger function processes the tainted X_TP_IfName value, the injected shell metacharacters break out of the iptables command and execute the payload as root. The payload also re-fires on reboot if the config persists, enabling persistent DoS (bootloop).
Conditions required:
  • Malicious config successfully uploaded and accepted by the router
  • Port-trigger rule activation (manual or on next reboot)
Where this breaks in practice:
  • If the payload is a reboot command, the result is a bricked router — useful for sabotage but not for persistence or data theft
  • Achieving persistent backdoor access within the 15-char limit is impractical without a staged approach
Detection/coverage: Unexpected router reboots or loss of connectivity from a specific branch site should trigger NOC investigation.
03 · Compensating Control

1
LOW 3.5→IGNORE 1.5
SEVERITY REDUCED
Change default admin credentials on all TP-Link SOHO routers — The entire chain depends on PR:H — admin authentication. Changing the default admin/admin password to a strong, unique credential eliminates the most common exploitation path. This is a configuration change, not a firmware update, and can be done immediately. As a LOW-severity finding, there is no noisgate mitigation SLA — treat as backlog hygiene.
2
LOW 3.5→IGNORE 1.0
SEVERITY REDUCED
Disable remote/WAN management and restrict LAN management access — Ensure the router's HTTP management interface is not exposed to the WAN (it should not be by default on these models). If possible, bind the management interface to a specific LAN IP or MAC filter. This reduces the adjacent-access surface to only physically connected or associated clients.
3
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Update firmware to patched versions — Apply V4_260304 (WR802N), V14_260303 (WR841N), or V6_260304 (WR840N) from the TP-Link support portal. This is the definitive fix. As a LOW-severity finding, the noisgate remediation SLA treats this as backlog — no hard deadline, but patch during next scheduled maintenance window or device refresh cycle.
4
LOW 3.5→IGNORE 0.0
SEVERITY REDUCED
Replace consumer routers with enterprise-managed access points — If these SOHO routers are present in any corporate environment, the strategic fix is replacement with enterprise-grade APs (Aruba, Meraki, Cisco) managed by your central wireless controller. This eliminates the entire class of consumer-firmware vulnerabilities permanently and improves visibility, segmentation, and policy enforcement.
What doesn't work
  • WAF / IPS signatures — the attack occurs over the LAN management interface using legitimate HTTP endpoints (/cgi/confup). Enterprise WAFs and IPS sit at the perimeter and do not inspect traffic to SOHO router admin panels on internal segments.
  • EDR on endpoints — the vulnerability is in the router's embedded firmware, not in a host OS. Endpoint detection agents have no visibility into the router's config upload or command execution.
  • Network segmentation alone — while segmentation limits who can reach the router, it does not prevent an authorized admin (or attacker with admin creds) on the management VLAN from exploiting the flaw. Segmentation reduces exposure but does not remediate.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone confirmed. Not listed on CISA KEV. No GreyNoise tags or Shadowserver reports reference CVE-2026-3227. No known campaigns or APT usage as of October 2026.
Proof-of-ConceptPublic PoC available. GitHub repo do4choo/CVE-2026-3227 published ~June 25, 2026. Includes Python decrypt/inject tooling, QEMU hook for re-encryption, and config payload generator. Flagged by @DarkWebInformer. Not yet indexed in Metasploit or Nuclei templates.
EPSS0.01789 (~1.8%) — 30-day exploitation probability. Low but nonzero; reflects the authenticated + adjacent-only constraints.
CISA KEV StatusNot listed. No federal mandate to patch under BOD 22-01.
CVSS VectorCVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H = 6.8 MEDIUM. Key constraints: AV:A (adjacent only — no internet reach) and PR:H (admin auth required). TP-Link's CVSS 4.0 self-assessment is 8.5 HIGH — the delta reflects v4.0's different weighting, not a different attack surface.
Affected VersionsTL-WR802N v4 firmware < V4_260304, TL-WR841N v14 < V14_260303, TL-WR840N v6 < V6_260304. All are 802.11n (Wi-Fi 4) consumer SOHO routers.
Fixed VersionsV4_260304 (WR802N), V14_260303 (WR841N), V6_260304 (WR840N) — released March 3–4, 2026 via TP-Link support.
Internet ExposureShodan indexes ~2,287 TL-WR841N management interfaces exposed to the internet, but the vuln requires AV:A (adjacent) — remote exploitation over the WAN is not part of this CVE's attack surface. Exposed management UIs are a separate hygiene problem.
Disclosure Date2026-03-16 (NVD published). TP-Link advisory dated 2026-03-13. PoC published ~2026-06-25 (~3 months post-patch).
Researcherdo4choo (GitHub). Independent researcher; not affiliated with a named security firm. Original discovery via firmware reverse engineering with binwalk and QEMU emulation.

Sources.

  1. TP-Link Security Advisory (CVE-2026-3227)
  2. NVD — CVE-2026-3227 Detail
  3. do4choo/CVE-2026-3227 — GitHub PoC Repository
  4. Foresiet — TP-Link CVE-2026-3227 Command Injection Analysis
  5. SentinelOne Vulnerability Database — CVE-2026-3227
  6. DarkWebInformer — PoC Disclosure Tweet
  7. LargeCardinal — Decrypting TP-Link config.bin Files
  8. GitHub Advisory Database — GHSA-7vvh-gmhq-282v
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.5/10)

Why this verdict

  • Adjacent-only access (AV:A) already priced in, but enterprise context deepens the discount. The CVSS 3.1 score of 6.8 accounts for AV:A technically, but does not capture that enterprise networks segment management planes. An attacker who can reach a consumer router's LAN interface in a corporate environment is already post-compromise on that segment — the router is not the initial access vector, it is a lateral target of marginal value.
  • Admin authentication (PR:H) plus default-cred dependency. The chain requires the router admin password. Default credentials (admin/admin) are common on these units in home environments, but in any managed deployment the creds should be changed. This prerequisite is reflected in CVSS but the practical exploitation rate against non-default creds is near zero.
  • Complex, fragile toolchain with a 15-character payload ceiling. The exploit requires QEMU MIPS emulation, cross-compilation, firmware extraction, and DES re-encryption — far beyond commodity exploit kits. The 15-char injection limit makes reverse shells or meaningful C2 implantation impractical. The realistic worst case is device bricking (DoS), not data exfiltration or persistent access.
  • No exploitation in the wild, no KEV listing, EPSS 1.8%. Seven months post-disclosure and four months post-PoC, there are zero confirmed campaigns. The EPSS score is consistent with a vuln that rarely gets weaponized.
  • Role multiplier: These are $15–25 consumer 802.11n SOHO routers (TL-WR841N retails ~$20). They are NOT enterprise network edge appliances (F5, Palo, FortiGate), identity infrastructure, hypervisors, or any high-value-role component. In a 10,000-host enterprise, the installed base of these specific models is effectively zero in production roles. Even in a branch-office scenario where one might appear, compromising it yields a pivot point on a single LAN segment — not domain takeover, fleet compromise, or supply-chain impact. The blast radius is host-level (the router itself) or at most site-level (one small branch). No high-value-role floor applies because <1% of the installed base of these consumer routers occupies any enterprise high-value role.

Why not higher?

The device is a consumer SOHO router with near-zero enterprise presence. Even granting the full attack chain succeeds, the outcome is root on a single cheap router — not a domain controller, not a hypervisor, not a CI/CD system. The 15-character payload limit prevents meaningful post-exploitation beyond DoS. There is no realistic scenario where this CVE enables fleet-scale or identity-scale compromise in an enterprise. Upgrading to MEDIUM would overstate the operational impact for the audience managing 10,000 corporate hosts.

Why not lower?

A public PoC exists and the default credentials on these routers are well-known, meaning a motivated insider or red-teamer on the LAN can reliably brick the device. The persistent-bootloop capability (survives factory reset) is a genuine availability concern for any organization that does have these units deployed — replacement rather than recovery is required. Dropping to IGNORE would understate the sabotage risk in environments where these routers exist, such as retail kiosks or temporary branch setups.

06 · Verification

Crowdsourced verification payload.

Run from any auditor workstation with Python 3 and the requests library. Invoke: python3 check_cve_2026_3227.py 192.168.0.1 admin admin. Requires valid admin credentials for the target router. No special OS privileges needed on the workstation.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
"""CVE-2026-3227 Checker — TP-Link TL-WR802N/WR841N/WR840N
Checks firmware version via authenticated web scrape.
Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN
"""
import sys
import re
import requests
from requests.auth import HTTPBasicAuth

PATCHED = {
    'TL-WR802N': 'V4_260304',
    'TL-WR841N': 'V14_260303',
    'TL-WR840N': 'V6_260304',
}

def normalize_ver(v):
    # Extract numeric portion for comparison, e.g. V14_260303 -> (14, 260303)
    m = re.search(r'V?(\d+)[_.]?(\d+)', v)
    if m:
        return (int(m.group(1)), int(m.group(2)))
    return None

def main():
    if len(sys.argv) < 4:
        print('Usage: check_cve_2026_3227.py <IP> <user> <password>')
        sys.exit(2)
    ip, user, pw = sys.argv[1], sys.argv[2], sys.argv[3]
    url = f'http://{ip}/'
    try:
        r = requests.get(url, auth=HTTPBasicAuth(user, pw), timeout=10)
    except Exception as e:
        print(f'UNKNOWN — connection failed: {e}')
        sys.exit(2)
    if r.status_code == 401:
        print('UNKNOWN — authentication failed (bad credentials)')
        sys.exit(2)
    body = r.text
    # Try to find model
    model_match = re.search(r'(TL-WR(?:802|841|840)N)', body, re.IGNORECASE)
    if not model_match:
        # Try status page
        for path in ['/userRpm/StatusRpm.htm', '/cgi/info']:
            try:
                r2 = requests.get(f'http://{ip}{path}', auth=HTTPBasicAuth(user, pw), timeout=10)
                body += r2.text
            except:
                pass
        model_match = re.search(r'(TL-WR(?:802|841|840)N)', body, re.IGNORECASE)
    if not model_match:
        print('UNKNOWN — could not identify model (not TL-WR802N/841N/840N)')
        sys.exit(2)
    model = model_match.group(1).upper()
    # Find firmware version
    fw_match = re.search(r'(V\d+[_.]\d{6})', body)
    if not fw_match:
        fw_match = re.search(r'Firmware\s*Version[^\d]*(\d+\.\d+\.\d+)', body)
    if not fw_match:
        print(f'UNKNOWN — found model {model} but could not extract firmware version')
        sys.exit(2)
    fw_raw = fw_match.group(1)
    fw_tuple = normalize_ver(fw_raw)
    patched_raw = PATCHED.get(model)
    if not patched_raw:
        print(f'UNKNOWN — model {model} not in affected list')
        sys.exit(2)
    patched_tuple = normalize_ver(patched_raw)
    if fw_tuple is None or patched_tuple is None:
        print(f'UNKNOWN — could not parse versions (fw={fw_raw}, patched={patched_raw})')
        sys.exit(2)
    if fw_tuple >= patched_tuple:
        print(f'PATCHED — {model} firmware {fw_raw} >= {patched_raw}')
        sys.exit(0)
    else:
        print(f'VULNERABLE — {model} firmware {fw_raw} < {patched_raw} (CVE-2026-3227)')
        sys.exit(1)

if __name__ == '__main__':
    main()