162 lines
5.4 KiB
Python
Executable File
162 lines
5.4 KiB
Python
Executable File
#!/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()) |