209 lines
7.2 KiB
Python
209 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Deep probe of public ZTE / MaaS-related hosts for an endpoint that accepts
|
|
key 2abd02c3-4e11-4a3b-88cf-341497e35892. Pure stdlib so the broken zsh env
|
|
doesn't bite us."""
|
|
import json, ssl, socket, sys, time
|
|
from urllib import request, error
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
KEY = "2abd02c3-4e11-4a3b-88cf-341497e35892"
|
|
TIMEOUT = 12
|
|
|
|
# Hosts that previously resolved publicly.
|
|
HOSTS = [
|
|
"openlab.zte.com.cn",
|
|
"maas-openapi.wanjiedata.com",
|
|
"aip.zte.com.cn",
|
|
"api-www.zte.com.cn",
|
|
"apipricenter.zte.com.cn",
|
|
"uds.dt.zte.com.cn",
|
|
"maas-apigateway.dt.zte.com.cn",
|
|
# Extra guesses worth a shot
|
|
"maas.zte.com.cn",
|
|
"openai.zte.com.cn",
|
|
"ai.zte.com.cn",
|
|
"openapi.zte.com.cn",
|
|
"apigw.zte.com.cn",
|
|
"maas-api.zte.com.cn",
|
|
"maas-openapi.zte.com.cn",
|
|
"aiapi.zte.com.cn",
|
|
"llm.zte.com.cn",
|
|
]
|
|
|
|
# Path matrix. The original URL pattern is /model/<slug>/v1 but the public
|
|
# gateway may expose /openai/v1, /api/v1, /v1, etc.
|
|
MODEL_SLUGS = [
|
|
"qwen3-coder-480b",
|
|
"Qwen3-Coder-480B-A35B-Instruct",
|
|
"qwen3-coder",
|
|
]
|
|
PATH_PREFIXES = [
|
|
"",
|
|
"/v1",
|
|
"/openai/v1",
|
|
"/api/v1",
|
|
"/api/openai/v1",
|
|
"/maas/v1",
|
|
"/maas/openai/v1",
|
|
"/model/qwen3-coder-480b/v1",
|
|
]
|
|
|
|
def resolve(host):
|
|
try:
|
|
socket.gethostbyname(host)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
def req(method, url, headers=None, body=None):
|
|
h = {"User-Agent": "Mozilla/5.0", "Accept": "*/*"}
|
|
if headers: h.update(headers)
|
|
data = None
|
|
if body is not None:
|
|
data = json.dumps(body).encode()
|
|
h["Content-Type"] = "application/json"
|
|
r = request.Request(url, data=data, headers=h, method=method)
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
try:
|
|
with request.urlopen(r, timeout=TIMEOUT, context=ctx) as resp:
|
|
raw = resp.read(800)
|
|
return resp.status, dict(resp.headers), raw.decode("utf-8", "replace")
|
|
except error.HTTPError as e:
|
|
raw = b""
|
|
try: raw = e.read(800)
|
|
except Exception: pass
|
|
return e.code, dict(e.headers or {}), raw.decode("utf-8", "replace")
|
|
except Exception as e:
|
|
return None, {}, f"{type(e).__name__}: {e}"
|
|
|
|
def probe_models(host, scheme):
|
|
"""GET /models across path prefixes with every key-header variant."""
|
|
out = []
|
|
header_sets = [
|
|
{"Authorization": f"Bearer {KEY}"},
|
|
{"Authorization": KEY},
|
|
{"api-key": KEY},
|
|
{"apikey": KEY},
|
|
{"X-API-Key": KEY},
|
|
{"X-Api-Key": KEY},
|
|
]
|
|
for prefix in PATH_PREFIXES:
|
|
for hs in header_sets:
|
|
url = f"{scheme}://{host}{prefix}/models"
|
|
code, hdrs, body = req("GET", url, hs)
|
|
tag = f"GET {url} [{list(hs.keys())[0]}]"
|
|
out.append((tag, code, body[:200] if body else ""))
|
|
# If we get anything other than 401/403/404/timeout, note loudly.
|
|
if code and code < 500 and code not in (401, 403, 404, 400):
|
|
print(f" [!] {code} {tag} -> {body[:200]!r}", flush=True)
|
|
if code == 200:
|
|
print(f" [*** 200 ***] {tag}\n {body[:400]!r}", flush=True)
|
|
return out
|
|
|
|
def probe_chat(host, scheme, prefix, model):
|
|
"""POST chat/completions with the Bearer key."""
|
|
url = f"{scheme}://{host}{prefix}/chat/completions"
|
|
body = {
|
|
"model": model,
|
|
"messages": [{"role":"user","content":"ping"}],
|
|
"max_tokens": 5,
|
|
"stream": False,
|
|
"temperature": 0,
|
|
}
|
|
headers = {"Authorization": f"Bearer {KEY}"}
|
|
code, hdrs, b = req("POST", url, headers, body)
|
|
return url, code, b[:400] if b else ""
|
|
|
|
def main():
|
|
print("=== Resolving hosts ===", flush=True)
|
|
live = []
|
|
for h in HOSTS:
|
|
ok = resolve(h)
|
|
print(f" {h}: {'LIVE' if ok else 'nxdomain'}", flush=True)
|
|
if ok: live.append(h)
|
|
|
|
print("\n=== Phase 1: GET /models matrix ===", flush=True)
|
|
for host in live:
|
|
for scheme in ("https","http"):
|
|
print(f"\n--- {scheme}://{host} ---", flush=True)
|
|
probe_models(host, scheme)
|
|
|
|
print("\n=== Phase 2: POST /chat/completions ===", flush=True)
|
|
targets = []
|
|
for host in live:
|
|
for scheme in ("https","http"):
|
|
for prefix in PATH_PREFIXES:
|
|
for model in MODEL_SLUGS + [host]:
|
|
targets.append((host, scheme, prefix, model))
|
|
|
|
found = []
|
|
with ThreadPoolExecutor(max_workers=12) as ex:
|
|
futs = {ex.submit(probe_chat, *t): t for t in targets}
|
|
for f in as_completed(futs):
|
|
url, code, body = f.result()
|
|
t = futs[f]
|
|
if code is None:
|
|
continue
|
|
mark = " "
|
|
if code == 200:
|
|
mark = "*** 200 ***"
|
|
found.append((url, body))
|
|
elif code in (401,403):
|
|
mark = "auth"
|
|
elif code == 404:
|
|
mark = "404 "
|
|
elif code and code < 500:
|
|
mark = f"!{code}!"
|
|
print(f" {mark} POST {url} model={t[3]}\n -> {code} {body[:160]!r}", flush=True)
|
|
|
|
print("\n=== Phase 3: deep openlab/ wanjiedata enumeration ===", flush=True)
|
|
extra_paths = [
|
|
"/", "/docs", "/redoc", "/openapi.json", "/swagger.json",
|
|
"/api", "/api/docs", "/api/openapi.json",
|
|
"/health", "/version", "/v1/models", "/api/v1/models",
|
|
"/openai/v1/models",
|
|
# wanjiedata already said /api/v1/models -> 400 "apiKey not found"
|
|
"/api/v1/chat/completions",
|
|
"/api/v1/completions",
|
|
"/api/v1/embeddings",
|
|
# ZTE MaaS documented variants
|
|
"/maas/v1/models",
|
|
"/maas/v1/chat/completions",
|
|
"/maas/openapi/v1/chat/completions",
|
|
"/openapi/v1/chat/completions",
|
|
"/restapi/v1/chat/completions",
|
|
"/service/v1/chat/completions",
|
|
"/qwen3-coder-480b/v1/chat/completions",
|
|
"/model/qwen3-coder-480b/v1/chat/completions",
|
|
]
|
|
for host in ("openlab.zte.com.cn", "maas-openapi.wanjiedata.com"):
|
|
for scheme in ("https","http"):
|
|
for p in extra_paths:
|
|
url = f"{scheme}://{host}{p}"
|
|
# GET
|
|
code, _, b = req("GET", url, {"Authorization": f"Bearer {KEY}"})
|
|
if code is not None:
|
|
print(f" GET {code} {url} {b[:160]!r}", flush=True)
|
|
# POST for chat paths
|
|
if "chat" in p or "completion" in p:
|
|
code, _, b = req("POST", url,
|
|
{"Authorization": f"Bearer {KEY}"},
|
|
{"model":"qwen3-coder-480b",
|
|
"messages":[{"role":"user","content":"ping"}],
|
|
"max_tokens":5})
|
|
if code is not None:
|
|
print(f" POST {code} {url} {b[:200]!r}", flush=True)
|
|
if code == 200:
|
|
found.append((url, b))
|
|
|
|
print("\n=== WORKING ENDPOINTS ===", flush=True)
|
|
for url, body in found:
|
|
print(f" 200 {url}\n {body[:500]!r}", flush=True)
|
|
if not found:
|
|
print(" (none returned 200)", flush=True)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|