178 lines
5.1 KiB
Python
178 lines
5.1 KiB
Python
"""ETERNAL StackExchange key-dork lane: search/advanced + withbody scan.
|
|
|
|
Anonymous quota ~300/day. One query per SLEEP (300s = 288/day). Dork
|
|
rotation + page cursor. body HTML scanned via key_patterns.scan_text.
|
|
mark_seen ONLY on success.
|
|
"""
|
|
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 threading
|
|
import time
|
|
from urllib.parse import quote
|
|
|
|
import requests
|
|
|
|
DB = os.environ.get("KF_DB") or os.path.join(KF_BASE, "data/keys.db")
|
|
LOG = os.path.join(KF_BASE, "data/stackex_eternal.log")
|
|
CURSOR_FILE = os.path.join(KF_BASE, "data/stackex_cursor.txt")
|
|
SEEN_FILE = os.path.join(KF_BASE, "data/stackex_seen.txt")
|
|
PAGES_PER_QUERY = 3
|
|
SLEEP = 300
|
|
|
|
from key_patterns import scan_text
|
|
|
|
DORKS = [
|
|
"ANTHROPIC_API_KEY", "sk-ant-api03", "sk-proj-", "OPENAI_API_KEY",
|
|
"ghp_", "gho_", "xoxb-", "AIza", "MISTRAL_API_KEY", "GROQ_API_KEY",
|
|
"HF_TOKEN", "glpat-", "dop_v1_", "whsec_", "xkeysib-", "napi_",
|
|
"tvly-", "dckr_pat_", "squ_", "glc_", "re_[A-Za-z0-9]", "shpat_",
|
|
"sq0atp-", "rubygems_", "lsv2_pt", "sentry dsn", "SECRET_KEY",
|
|
"PINECONE_API_KEY", "ELEVENLABS", "DEEPSEEK", "TOGETHER_API_KEY",
|
|
"FIREWORKS", "OPENROUTER", "PERPLEXITY", "GEMINI_API_KEY",
|
|
"GOOGLE_API_KEY", "AWS_SECRET_ACCESS_KEY", "STRIPE_API_KEY",
|
|
"SENDGRID", "TWILIO_AUTH_TOKEN", "MAPBOX", "MAILGUN",
|
|
]
|
|
|
|
GARBAGE = ("xxxx", "your", "YOUR", "EXAMPLE", "example", "n0tr3al",
|
|
"placeholder", "CHANGEME", "changeme", "dummy", "DUMMY",
|
|
"abcdefghijklmnopqrst", "1234567890abcdef")
|
|
|
|
UA = {"User-Agent": "keyfarm-research/1.0"}
|
|
|
|
|
|
def log(msg):
|
|
line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n"
|
|
with open(LOG, "a", encoding="utf-8") as f:
|
|
f.write(line)
|
|
|
|
|
|
conn = sqlite3.connect(DB, timeout=120, check_same_thread=False,
|
|
isolation_level=None)
|
|
lock = threading.Lock()
|
|
total = 0
|
|
|
|
|
|
def db_add(key, prov, repo):
|
|
global total
|
|
with lock:
|
|
conn.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", "stackex", repo))
|
|
total += 1
|
|
|
|
|
|
def load_cursor():
|
|
try:
|
|
with open(CURSOR_FILE, encoding="utf-8") as f:
|
|
return int(f.read().strip() or 0)
|
|
except Exception:
|
|
return 0
|
|
|
|
|
|
def save_cursor(c):
|
|
with open(CURSOR_FILE, "w", encoding="utf-8") as f:
|
|
f.write(str(c))
|
|
|
|
|
|
def load_seen():
|
|
s = set()
|
|
try:
|
|
with open(SEEN_FILE, encoding="utf-8") as f:
|
|
for line in f:
|
|
s.add(line.strip())
|
|
except Exception:
|
|
pass
|
|
return s
|
|
|
|
|
|
seen = load_seen()
|
|
|
|
|
|
def mark_seen(p):
|
|
seen.add(p)
|
|
with open(SEEN_FILE, "a", encoding="utf-8") as f:
|
|
f.write(p + "\n")
|
|
|
|
|
|
def scan_body(body, qid, n):
|
|
for prov, key in scan_text(body):
|
|
if isinstance(key, tuple):
|
|
key = key[0]
|
|
if any(g in key for g in GARBAGE) or len(key) < 30:
|
|
continue
|
|
db_add(key, prov, f"so:{qid}")
|
|
n += 1
|
|
return n
|
|
|
|
|
|
def cycle():
|
|
global total
|
|
total = 0
|
|
di = load_cursor() % len(DORKS)
|
|
dork = DORKS[di]
|
|
n_items = n_keys = 0
|
|
for page in range(1, PAGES_PER_QUERY + 1):
|
|
url = ("https://api.stackexchange.com/2.3/search/advanced"
|
|
f"?order=desc&sort=activity&q={quote(dork)}"
|
|
f"&site=stackoverflow&filter=withbody&pagesize=100&page={page}")
|
|
d = None
|
|
for attempt in (1, 2):
|
|
try:
|
|
r = requests.get(url, headers=UA, timeout=25)
|
|
if r.status_code != 200:
|
|
raise RuntimeError(f"http {r.status_code}")
|
|
d = r.json()
|
|
break
|
|
except Exception as e:
|
|
if attempt == 2:
|
|
log(f"query-err dork={dork} page={page} "
|
|
f"{type(e).__name__}")
|
|
return
|
|
time.sleep(5)
|
|
if not d:
|
|
break
|
|
items = d.get("items") or []
|
|
if not items:
|
|
break
|
|
for it in items:
|
|
qid = it.get("question_id")
|
|
sid = f"q{qid}"
|
|
if qid is None or sid in seen:
|
|
continue
|
|
n_items += 1
|
|
body = it.get("body") or ""
|
|
n_keys += scan_body(body, qid, 0)
|
|
# answers not fetched here; question body only (backoff budget)
|
|
mark_seen(sid)
|
|
if not d.get("has_more"):
|
|
break
|
|
|
|
save_cursor(load_cursor() + 1)
|
|
log(f"cycle: dork={dork} items={n_items} +{n_keys} keys "
|
|
f"({total} total inserts)")
|
|
|
|
|
|
def main():
|
|
log("stackex_eternal started")
|
|
if "--once" in sys.argv:
|
|
cycle()
|
|
return
|
|
while True:
|
|
try:
|
|
cycle()
|
|
except Exception as e:
|
|
log(f"cycle-err {type(e).__name__}: {e}")
|
|
time.sleep(SLEEP)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|