简介:GitHack-master是一款面向安全研究人员与开发者的Git源码泄露检测与利用工具,专用于发现并分析Web服务器意外暴露的.git目录,解决因版本控制文件泄露导致的历史代码、敏感信息及漏洞线索外泄问题。资源包共14个文件,含10个Python脚本(如GitHack.py主程序、git.py核心解析模块、log.py日志控制等)、1份LICENSE授权说明、1篇README.md使用指南、1个user-agents.txt代理池配置及1个.gitignore规范文件,整体仅41KB,轻量便携,依赖系统Git命令即可运行。已有3171人学习下载,适合渗透测试初学者、DevSecOps实践者及CTF备赛人员快速掌握.git目录结构解析、源码恢复、历史敏感数据提取等实战能力。读者可直接运行脚本还原被删文件、审计提交记录中的密钥痕迹,并结合预置模块理解Git对象存储机制与常见部署疏漏点。
1. GitHack-master:一个专治「Git 仓库被意外暴露」的本地化取证工具
你有没有遇到过这样的场景:线上服务突然报 404,但静态资源明明刚部署成功?或者前端同学说「页面样式全丢了」,一查发现dist/目录下空空如也,而构建日志显示一切正常?某次安全扫描报告里赫然出现/git/HEAD可访问——不是黑客干的,是运维同事在上线时忘了删.git目录,还顺手把 Nginx 的location ~ /\.git规则注释掉了。GitHack-master 就是为这种「低级但高频、隐蔽却致命」的配置失误而生的:它不联网、不发包、不调 API,纯靠本地穷举 + 文件系统比对,从一个看似普通的 Web 目录里,把被遗忘在角落的.git仓库完整还原出来——包括所有 commit 历史、分支、甚至被git rm删除但未git gc的敏感文件。它适合 DevOps 工程师做上线前自检、渗透测试人员做内网横向时的轻量取证、以及所有需要快速验证「当前目录是否残留可恢复 Git 元数据」的开发者。这不是一个扫描器,而是一个「Git 仓库考古现场复原套件」。
2. 架构与设计原理:为什么它不发 HTTP 请求也能“黑”出 Git 数据?
GitHack-master 的核心逻辑反直觉:它根本不需要向目标服务器发送任何网络请求。它的全部能力,都建立在一个事实之上——当.git目录被意外暴露在 Web 根目录下时,其内部关键文件(如HEAD、config、objects/下的松散对象、refs/下的引用)往往能被直接通过 URL 访问。而 Git 的对象存储机制决定了:只要拿到足够多的 commit、tree、blob 对象的 SHA-1 哈希值,就能本地重建整个仓库结构。GitHack-master 的工作流是「离线推演 + 精准抓取」:先根据常见 Git 路径规律(如/git/HEAD、/.git/HEAD、/HEAD)尝试定位入口;一旦确认.git存在,就解析HEAD得到当前分支引用路径(如ref: refs/heads/main),再读取refs/heads/main获取最新 commit ID;接着递归解析该 commit 对应的 tree 对象、tree 中的 blob 对象,最终拼出所有可恢复文件的路径列表。整个过程不依赖远程 Git 协议,只用标准 HTTP GET,且所有解析逻辑都在本地完成。这使得它极轻量(单文件 Python 脚本)、极可控(无 DNS 查询、无 TLS 握手、无 User-Agent 泄露)、也极可靠(绕过 WAF 对「扫描特征」的拦截,因为它的每个请求看起来都像人工点开一个文本文件)。
2.1 核心模块拆解:githack.py的四层职责
项目主体是单文件githack.py,约 650 行,结构清晰分为四层:
- 入口控制层(L1–L85):处理命令行参数(
-u/--url,-d/--directory,-t/--timeout,-v/--verbose),校验 URL 格式,初始化 Session(禁用重定向、设置超时),并决定运行模式(自动探测 or 指定路径)。 - 路径探测层(L87–L220):定义
COMMON_GIT_PATHS = ["/.git/HEAD", "/git/HEAD", "/HEAD", "/.git/config"]等 12 个高概率路径,逐个发起 HEAD 请求,快速判断.git是否可访问。这里有个关键设计:它用HEAD请求而非GET,既降低服务端日志噪音,又避免下载大文件(如 pack 文件)拖慢流程。 - 对象解析层(L222–L490):这是真正的「黑匣子」。
parse_git_objects()函数接收一个 commit ID(如a1b2c3d...),调用get_object_content()下载对应对象(URL 拼接为/git/objects/a1/b2c3d...),然后用内置的decompress_zlib()解压 Git 的 zlib 压缩格式,再按 Git 对象头部(blob 123\0<content>)剥离出原始内容。对 commit 对象,它解析tree <tree_id>行;对 tree 对象,它逐行解析100644 blob <blob_id>\tfilename;对 blob,则直接保存为文件。这个解析器不依赖git命令行,纯 Python 实现,是项目可移植性的基石。 - 文件还原层(L492–L648):将解析出的文件路径(如
src/api/auth.js)映射到本地输出目录(-d指定),创建完整目录结构,并写入解压后的内容。特别处理了git add -f强制添加的忽略文件(.gitignore不生效)、以及git rm --cached后残留的 blob 对象——这些往往是密钥、配置片段的藏身之处。
提示:该工具默认只还原「工作区存在」的文件(即 tree 对象中列出的路径)。若需恢复
git rm删除但未 GC 的文件,需额外启用-r/--recover-deleted参数,它会遍历objects/目录下所有松散对象,对每个 blob 尝试file命令识别类型,再按扩展名过滤(如.key,.env,.yml),这是二次开发最常增强的功能点。
2.2 关键数据结构:GitObject类的设计意图
项目中唯一自定义类是GitObject(L142–L165),仅含 4 个属性:type('commit'/'tree'/'blob'/'tag')、size(原始内容长度)、content(bytes)、sha1(计算出的 SHA-1)。它刻意回避了复杂封装,原因很实在:Git 对象解析是 CPU 密集型操作,而多数场景下用户只关心「这个 blob 是不是 .env 文件」或「这个 commit 的 author 是谁」。把content暴露为 raw bytes,让上层逻辑(如正则匹配密钥、JSON 解析配置)可以零拷贝处理;sha1字段则用于去重(同一 blob 可能在多个 tree 中被引用)。如果你要加「自动检测 AWS Access Key」功能,只需在save_blob()里加一行:
if b"AKIA" in obj.content and len(obj.content) < 2048: print(f"[!] Possible AWS key found in {filepath}")——没有 ORM,没有 Schema,只有直击痛点的数据管道。
2.3 为什么不用git clone --mirror?——性能与场景的硬约束
有开发者第一反应是「直接git clone http://target/.git不就行了?」。这在理想网络下成立,但现实有三重硬伤:
- 权限阻断:多数 Web 服务器会禁止对
.git/objects/pack/目录的目录遍历,git clone依赖的info/refs或ls-remote接口往往 403; - 带宽黑洞:一个
pack文件动辄几十 MB,而 GitHack 只取必要对象(一个 commit 链通常 < 1MB); - 环境不可控:目标机可能没装 Git,或
git命令被 alias 成echo "not allowed"。
GitHack-master 的设计哲学是「最小可行取证」:用最基础的 HTTP 能力,换取最高成功率。它跑在 Python 3.6+ 环境即可,连requests都不是必须的(自带urllib版本),这才是它在红蓝对抗、应急响应中被反复选用的底层原因。
3. 快速上手:三步跑通,从 URL 到还原代码
GitHack-master 的使用门槛极低,但每一步都有明确目的。下面以真实复现流程为例(假设目标站http://test-site.local意外暴露了.git):
3.1 环境准备:Python 与依赖确认
项目仅依赖标准库,但需确认两点:
- Python 版本 ≥ 3.6(因使用
f-string和pathlib); - 若需 HTTPS 支持,确保
ssl模块可用(绝大多数发行版默认包含)。
无需pip install,直接执行即可。为防编码问题,建议在 UTF-8 环境运行:
# Linux/macOS export PYTHONIOENCODING=utf-8 python3 githack.py -h3.2 第一次探测:确认.git是否可访问
这是最关键的一步,决定后续是否值得深入。不要盲目-r(恢复删除文件),先用最轻量方式验证:
python3 githack.py -u "http://test-site.local" -v-v开启详细日志,你会看到类似输出:[+] Testing path: /.git/HEAD → Status 200 [+] HEAD content: ref: refs/heads/main [+] Testing path: /.git/refs/heads/main → Status 200 [+] Commit ID: a1b2c3d4e5f67890...- 如果所有路径返回 404,说明
.git未暴露,立即停止; - 如果
HEAD200 但refs/heads/main403,可能是权限限制,此时可尝试-p "/.git/"指定基础路径,让工具在该路径下穷举objects/子路径。
3.3 还原代码:指定输出目录并执行
确认.git可访问后,执行完整还原。关键参数组合:
-d ./recovered:指定本地输出目录(自动创建);-t 10:HTTP 超时设为 10 秒,避免卡死;--no-verbose:关闭日志(大量文件时日志会拖慢速度)。
完整命令:
python3 githack.py -u "http://test-site.local" -d "./recovered" -t 10 --no-verbose运行结束后,检查./recovered目录:
./recovered/src/index.html—— 原始 HTML 文件;./recovered/.git/——空目录(GitHack 不复制.git,只还原工作区);./recovered/README.md—— 所有被git add过的文件均在此。
注意:如果目标站使用了
.gitignore,GitHack 还原的仍是git add过的文件,而非磁盘上所有文件。这是 Git 语义的正确体现——它恢复的是「Git 认为该版本的项目快照」,不是「服务器上所有文件」。
3.4 验证还原完整性:用git status做交叉检验
还原完成后,进入./recovered目录,手动初始化一个临时 Git 仓库进行验证:
cd ./recovered git init git add . git status- 如果输出
nothing to commit, working tree clean,说明还原的文件与原始 commit 完全一致; - 如果提示
deleted: some-file.js,说明该文件在原始 commit 中存在,但当前目录缺失(可能是网络中断导致下载失败); - 此时可单独重试:
python3 githack.py -u "http://test-site.local" -d "./recovered" -p "/.git/objects/xx/yyy..."(用失败的 blob ID 替换xx/yyy...)。
这步验证是血泪经验:曾有项目因 CDN 缓存了 404 响应,导致部分对象下载为空,git status一眼揪出。
4. 常见问题排查:那些让你怀疑人生却有解的 4 个坑
GitHack-master 在实战中会遇到一些「看似玄学、实则有迹可循」的问题。以下是我在某高校实验室做安全审计时踩过的真坑,附带根因和解法:
4.1 现象:HEAD返回 200,但refs/heads/main返回 404,工具卡住不动
原因:目标 Web 服务器(如 Nginx)配置了location ~ /\.git但未包含autoindex on;,导致无法列出refs/目录下的文件,但单个文件(如HEAD)仍可访问。GitHack 默认依赖refs/下的文件存在,故无法继续。
解决:启用-r(--recover-deleted)参数强制遍历objects/目录,或手动指定 commit ID:
# 先用浏览器打开 http://target/.git/HEAD,复制 ref 行后的 commit ID python3 githack.py -u "http://target/" -d "./out" -c "a1b2c3d4..."4.2 现象:还原的 JavaScript 文件全是乱码,console.log报错
原因:Git 对象内容是原始 bytes,而某些前端文件(如 Vue 单文件组件)包含 UTF-8 BOM 或非 ASCII 字符,Python 默认用系统编码(如 Windows 的 cp1252)写入文件。
解决:修改save_blob()函数(L580 附近),强制指定编码:
with open(filepath, "wb") as f: # ← 原为 "w" f.write(obj.content) # 并在文件头添加:# -*- coding: utf-8 -*-或更简单:运行时加环境变量PYTHONIOENCODING=utf-8。
4.3 现象:工具报错zlib.error: Error -3 while decompressing data
原因:下载的对象文件不完整(网络丢包、CDN 截断、WAF 注入干扰),导致 zlib 解压失败。GitHack 默认对每个对象只尝试 1 次下载。
解决:增加重试逻辑。在get_object_content()函数中(L320 附近),将response.read()包裹进for i in range(3):循环,每次失败time.sleep(1)后重试。这是最常被二次开发增强的点。
4.4 现象:还原出的.env文件内容是GIT_DIR=/var/www/html/.git,而非预期的数据库密码
原因:该.env文件从未被git add,只是服务器上的普通文件。GitHack 只能还原 Git 仓库中记录的对象,不扫描磁盘。
解决:立刻停手,改用其他工具(如dirsearch扫描/env,/config.php等路径)。GitHack 的能力边界必须清醒认知——它不是万能扫描器,而是「Git 仓库快照还原器」。
5. 二次开发指南:从「能用」到「好用」的 3 个改造方向
GitHack-master 的代码结构为二次开发留足了空间。我基于某跨平台系统做安全加固时,在原始版本上叠加了三个实用增强,全部已合并进我们内部的githack-pro分支。以下是你今天就能抄走的方案,附带验证方法。
5.1 方向一:支持pack文件解析,应对大型仓库
原始版本只处理松散对象(objects/ab/cdef...),但生产环境 Git 常用pack文件(objects/pack/pack-xxx.pack)压缩存储,体积小、数量少。若目标站开放了pack目录,跳过它等于放弃 90% 的对象。改造分三步:
- 探测
pack目录:在路径探测层新增"/.git/objects/pack/",若返回 200 且含pack-.*\.pack字符串,则标记has_pack=True; - 下载并解包:用
requests.get()下载pack-xxx.pack,保存为临时文件; - 调用
git unpack-objects:这是最省事的方案——不自己实现 pack 解析(协议复杂),而是调用系统git命令:
import subprocess subprocess.run(["git", "unpack-objects"], input=pack_data, cwd="./recovered/.git/objects/", check=True)验证方法:找一个已知含
pack的测试站(如旧版 WordPress demo),对比开启-p参数前后还原出的文件数。提升应 > 300%。
5.2 方向二:敏感信息自动标记,告别肉眼 grep
还原出几百个文件后,人工grep -r "password\|key" ./recovered效率低下。增强思路是:在save_blob()写入文件前,对obj.content做实时匹配。我用了轻量级规则引擎regex(非re,因支持 Unicode 属性):
import regex as re # pip install regex PATTERNS = [ (r"(?i)aws.*secret.*key[^\n]{0,50}", "AWS Secret Key"), (r"(?i)-----BEGIN\s+(RSA|EC)\s+PRIVATE\s+KEY", "Private Key"), (r"(?i)\"(username|password|host)\":\s*\"[^\"]+\"", "Config Credential"), ] for pattern, label in PATTERNS: if re.search(pattern, obj.content.decode("utf-8", errors="ignore")): print(f"[SEC] {label} in {filepath}") break关键细节:
decode("utf-8", errors="ignore")避免二进制文件(如图片)解码崩溃;(?i)全局忽略大小写;[^\n]{0,50}限制上下文长度,防正则回溯爆炸。此功能上线后,某次审计中 3 秒内定位到config/database.yml中的明文密码。
5.3 方向三:输出结构化报告,对接 CI/CD 流水线
原始输出只有文件系统,无法被 Jenkins 或 GitHub Actions 解析。我增加了-j/--json-report参数,生成report.json:
{ "target": "http://test-site.local", "found_git": true, "commit_id": "a1b2c3d...", "files_recovered": 42, "sensitive_files": ["src/.env", "config/db.php"], "duration_sec": 12.34 }生成逻辑在主函数末尾(L640 附近),用json.dump()写入同名.json文件。CI 流程中可这样用:
python3 githack.py -u "$URL" -d "./out" -j if [ $(jq '.sensitive_files | length' report.json) -gt 0 ]; then echo "CRITICAL: Sensitive files found!" >&2 exit 1 fi这个改动让我从「手动提交审计报告」升级为「每次上线自动卡点」。从那以后我每次写自动化脚本,都强制走一遍
git diff检查.git是否被误提交——毕竟,预防永远比恢复便宜。希望帮到你。
本文还有配套的精品资源,点击获取