三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Git目录泄露漏洞:从原理到实战利用与防御

Git目录泄露漏洞:从原理到实战利用与防御

1. 项目概述:从一次实战演练看Git目录泄露的攻防本质

最近在复盘一个CTF靶场里的Web题目,题目名字就叫“Git目录泄露”。这其实是一个在真实渗透测试和CTF比赛中都非常经典的漏洞场景,但很多新手朋友可能只停留在“用工具扫一下,然后下载源码”的层面。这次我打算结合这道题,以及我这些年遇到过的各种情况,把Git目录泄露这个漏洞从原理到利用,再到防御,彻底掰开揉碎了讲清楚。这不仅仅是写一个Writeup,更是想分享一套遇到这类问题时,你应该怎么想、怎么做的完整思路。

简单来说,Git目录泄露漏洞,就是开发或运维人员不小心把项目根目录下的.git文件夹部署到了线上Web服务器的可访问目录里。这个.git文件夹是Git版本控制系统的核心,里面存放了项目的所有版本历史、分支信息、提交记录,甚至在某些情况下,能直接还原出完整的项目源代码。攻击者一旦能访问到这个目录,就相当于拿到了项目的“开发后台”,审计源码、寻找数据库配置、硬编码的密钥、未完成的敏感功能接口都成为了可能。这道题就是一个绝佳的实战案例,它模拟了真实环境中由于配置疏忽导致.git目录暴露的场景,我们的目标就是利用这个泄露,找到隐藏的Flag。

2. 漏洞原理与初步探测:.git目录里到底有什么?

在开始实战之前,我们必须先搞清楚,我们试图访问的.git目录,到底是个什么东西,为什么它如此重要。

2.1.git目录结构解析

当你执行git init初始化一个仓库时,Git会在项目根目录创建这个.git文件夹。它的结构大致如下,每一个文件都有其特定用途:

.git/ ├── HEAD # 指向当前所在的分支 ├── config # 项目特有的配置信息 ├── description # 仓库描述信息 ├── hooks/ # 客户端或服务端的钩子脚本 ├── info/ # 全局性排除文件(.gitignore的补充) ├── objects/ # **核心**:Git的数据对象库,所有文件内容、提交、树对象都压缩存储在这里 │ ├── pack/ # 打包后的对象文件(用于节省空间) │ └── [0-9a-f][0-9a-f]/ # 松散对象存储,按SHA-1哈希的前两位分目录 └── refs/ # 指向各个分支、标签的指针 ├── heads/ # 分支指针 └── tags/ # 标签指针

对于攻击者而言,最有价值的是HEADindex(暂存区文件,不一定总是存在)、logs/(操作日志)以及objects/目录。特别是objects/目录,它使用SHA-1哈希值作为文件名来存储所有数据。只要我们能获取到足够的对象文件,理论上就能重建项目的某个版本,甚至整个历史。

2.2 如何判断存在Git泄露?

在实战中,我们不会一上来就盲目扫描。通常会有一些蛛丝马迹引导我们怀疑存在Git泄露。

  1. 直接访问探测:这是最简单粗暴的方法。直接在目标URL后拼接/.git/,例如http://target.com/.git/。如果服务器配置不当,没有禁止目录列表,你可能会看到一个403 Forbidden(这通常意味着目录存在但无列表权限),甚至是一个目录列表(返回200状态码,列出文件)。更精确的探测是访问/.git/HEAD文件,这个文件几乎总是存在的,内容通常是ref: refs/heads/master。如果能成功下载这个文件,那么Git泄露几乎可以确认。

  2. 使用目录扫描工具:当直接访问被屏蔽或需要批量检测时,工具就派上用场了。像dirsearchgobusterffuf这类工具,加载一个包含/.git//.git/HEAD/.git/config等常见路径的字典进行爆破,效率很高。

    # 使用dirsearch的示例命令 python3 dirsearch.py -u http://target.com -e php,html,js -w /path/to/wordlists/git.txt

    这里的git.txt字典文件就需要包含针对.git目录的常见路径。

  3. 观察网站特征:有时网站底部、robots.txt文件或注释里会提到使用了Git。例如在robots.txt里看到Disallow: /.git/,这反而是一个强烈的暗示。或者About页面写着“Powered by Git version x.x.x”,这些都值得深入探查。

注意:直接访问/.git/目录时,返回403状态码比返回404更有希望。403表示服务器理解请求但拒绝授权,说明这个路径是存在的;404则表示路径不存在。这是一个重要的排查技巧。

3. 利用工具进行源码还原:从碎片到完整项目

确认漏洞存在后,下一步就是尝试把泄露的源码“偷”回来。这里我们主要讨论两种主流方法:使用自动化工具和手动分析还原。

3.1 自动化利器:GitHack与GitTools

对于大多数情况,自动化工具是首选,能快速还原源代码。

GitHack:这是一个非常经典的Python脚本。它的原理是递归地下载.git目录下所有能访问到的文件,然后根据Git的对象存储机制,解析index文件、HEADrefs来重建工作目录。

python2 GitHack.py http://target.com/.git/

需要注意的是,GitHack有Python2和Python3的不同版本,使用时需注意环境。它的优点是简单快捷,但有时在复杂的仓库结构或存在pack文件时可能还原不完整。

GitTools:这是一套更强大、更手工化的工具集,包含多个脚本,如gitdumper.shextractor.sh等。我更喜欢用它,因为它更可控。

# 1. 使用gitdumper下载整个.git目录 ./gitdumper.sh http://target.com/.git/ ./output_folder # 2. 使用extractor从下载的.git文件夹中提取源码 ./extractor.sh ./output_folder ./extracted_code

gitdumper.sh会尝试下载所有它能找到的Git对象文件,extractor.sh则负责解析这些对象并还原出文件。这个过程能让你更清楚地看到Git是如何存储数据的。

3.2 手动还原:理解Git对象存储

了解手动还原有助于你在工具失效时进行排查,也能加深对漏洞原理的理解。Git对象主要有四种:blob(文件内容)、tree(目录结构)、commit(提交信息)、tag(标签)。

  1. 定位当前分支:首先查看HEAD文件,它指向当前分支,如ref: refs/heads/master
  2. 找到最新提交:根据HEAD的指引,查看refs/heads/master文件,里面是一个40位的SHA-1哈希值(如a1b2c3...),这就是最新提交的ID。
  3. 下载提交对象:提交对象存储在.git/objects/a1/b2c3...(哈希前两位作目录,后38位作文件名)。你需要下载这个文件。它是经过zlib压缩的,可以用pythongit cat-file命令查看。
    # 在本地创建一个临时git仓库并查看对象 mkdir temp && cd temp && git init # 将下载的object文件放入 .git/objects/a1/b2c3... 位置 git cat-file -p a1b2c3... # 可以看到提交的详细信息,包括其对应的tree对象哈希
  4. 递归解析tree和blob:从提交对象中找到顶层tree对象的哈希,然后下载并解析它。tree对象列出了该提交下的所有文件和子目录(对应子tree)及其哈希。继续递归下载和解析这些blob(文件内容)和tree(子目录),最终就能还原出整个项目快照。

这个过程非常繁琐,但能让你彻底明白,即使.git/index(暂存区)文件丢失,只要objects/目录下的关键对象还在,源码就有可能被恢复。

4. 实战Writeup深度剖析:不止于下载源码

现在,让我们回到题目本身。假设我们通过工具已经成功下载了泄露的源码。真正的挑战才刚刚开始——代码审计。题目往往不会把Flag明晃晃放在下载的源码根目录里。

4.1 审计思路与常见危险函数

拿到源码后,我通常会按照以下步骤进行快速审计:

  1. 文件清单梳理:先用tree命令或直接浏览,了解项目结构。重点关注:

    • config/,inc/,includes/:配置文件、包含文件目录。
    • api.php,admin.php,upload.php:功能接口文件。
    • flag.php,key.txt,.env:可能直接存放敏感信息的文件。
    • database.php,config.inc.php:数据库配置文件。
  2. 搜索敏感关键词:使用grep进行全局搜索是最高效的方式。

    # 搜索可能包含flag的字符串 grep -r "flag\|key\|secret\|password\|token" --include="*.php" --include="*.txt" --include="*.env" . # 搜索危险函数(PHP为例) grep -r "eval\|assert\|system\|exec\|shell_exec\|passthru\|popen\|proc_open\|`(反引号)" --include="*.php" . # 搜索数据库操作语句,寻找SQL注入点 grep -r "SELECT\|INSERT\|UPDATE\|DELETE.*\$_GET\|\$_POST\|\$_REQUEST" --include="*.php" . # 搜索文件操作函数,寻找文件包含/读取漏洞 grep -r "file_get_contents\|include\|require\|fopen.*\$_GET\|\$_POST" --include="*.php" .
  3. 跟踪核心业务流程:找到网站的主入口文件(通常是index.php),顺着它的逻辑,看它如何接收参数($_GET,$_POST,$_REQUEST),如何调用其他文件,最终输出什么。题目中的漏洞往往就藏在某个参数的处理流程中。

4.2 案例精讲:从源码到漏洞利用

以一道常见的CTF题为例(类似输入内容中提到的mfw)。假设我们通过GitHack下载源码后,发现结构如下:

. ├── index.php ├── templates/ │ ├── home.php │ ├── about.php │ └── flag.php └── .git/ (已下载)

审计index.php,发现关键代码:

<?php $page = $_GET['page'] ?? 'home'; $file = "templates/" . $page . ".php"; assert("strpos('$file', '..') === false"); include($file); ?>

漏洞分析

  1. 代码获取page参数,拼接成文件路径。
  2. 使用assert()函数对拼接后的字符串进行安全检查,试图防止目录穿越(..)。
  3. 但这里存在一个字符串拼接+代码注入的经典漏洞。assert()的参数是字符串,且会被当作PHP代码执行。我们可控的$page被直接拼接进了这个字符串。

构造Payload: 我们的目标是读取templates/flag.php的内容。assert()的语句是:

assert("strpos('templates/'.$page.'.php', '..') === false")

如果我们传入page=abc') or system('cat templates/flag.php');//,那么拼接后成为:

assert("strpos('templates/abc') or system('cat templates/flag.php');//.php', '..') === false")
  • abc')提前闭合了strpos()的第一个参数。
  • or是逻辑或,由于strpos(...)可能返回false(未找到..),所以会执行后面的system()函数。
  • system('cat templates/flag.php');执行系统命令,读取flag文件。
  • //将后面的.php', '..') === false")全部注释掉,避免语法错误。

这样,我们就通过一个assert()函数,将文件包含漏洞升级为了代码执行漏洞,成功读取flag。

实操心得:遇到assert()eval()要格外警惕。它们的功能都是执行字符串形式的代码,是代码注入的高发区。审计时,一定要看用户输入是否未经严格过滤就直接拼接到这些函数的参数字符串中。

5. 进阶利用与深度挖掘:历史记录中的宝藏

有时,Flag并不在当前版本的源码里,而是藏在历史的某个提交中。这就是.git泄露比单纯源码泄露更危险的地方——它暴露了历史。

5.1 查看提交历史

在成功下载并还原.git文件夹后,你可以将其视为一个不完整的本地仓库。进入该目录,尝试使用git log命令查看提交历史。

cd downloaded_git_folder git log --oneline

如果logs目录和必要的引用信息完整,你就能看到所有的提交记录、作者、时间和提交说明。提交说明里可能会有“修复安全漏洞”、“移除硬编码密码”、“添加flag”等提示性信息。

5.2 比较文件差异

如果你怀疑Flag被写入后又删除,或者分散在多次提交里,就需要比较不同版本间的差异。

  1. 查看某次提交的改动git show <commit-hash>可以显示该次提交的具体改动内容。
  2. 比较两个提交之间的差异git diff <commit-hash-1> <commit-hash-2>可以比较两次提交的整体差异。如果你想看某个特定文件的变化,可以加上路径:git diff <hash1> <hash2> -- path/to/file
  3. 检查所有分支和标签:别忘了git branch -agit tag,看看有没有其他分支或标签里藏着东西。

在输入内容提到的leak_snake例题中,就需要对31个版本进行两两git diff,从差异中拼凑出完整的Flag。这个过程可以写简单的脚本自动化:

# 假设获取了所有提交的哈希列表 commits=$(git log --pretty=format:"%H" | tac) # 反转顺序,从旧到新 prev="" for commit in $commits; do if [ -n "$prev" ]; then echo "Diff between $prev and $commit:" git diff $prev $commit --no-prefix | grep -E "^\+[^\+]" | sed 's/^\+//' | tr -d '\n' echo fi prev=$commit done

这个脚本会依次比较相邻提交的差异,并提取出所有新增(+)的行,拼接起来,可能就能得到分散的Flag字符串。

5.3 恢复被删除的文件或分支

如果在历史中发现了删除文件的操作,你可以尝试恢复它。

# 找到删除该文件的提交 git log --all --full-history -- path/to/deleted_file # 在删除前的那个提交中,将文件检出 git checkout <commit-hash-before-delete> -- path/to/deleted_file

对于被删除的分支,可以通过git reflog命令查看仓库的引用日志,找到分支最后指向的提交,然后根据那个提交创建新分支来恢复。

6. 防御方案与最佳实践:让漏洞无处可藏

分析了这么多攻击手法,作为开发或运维人员,该如何避免成为受害者呢?防御必须从源头和部署两个环节抓起。

6.1 开发与构建环节

  1. 使用.gitignore:这是第一道防线,确保敏感文件(如配置文件、密钥、编译产物)不会被意外提交。但这对.git目录本身无效。
  2. 在构建脚本中排除.git:无论是使用Webpack、Maven、Gradle还是简单的Shell脚本,在将代码打包用于部署时,必须确保.git目录被排除在外。
    # 示例:使用rsync排除.git目录进行部署 rsync -avz --exclude='.git' ./src/ user@production-server:/var/www/html/
  3. 使用Docker的.dockerignore:如果使用Docker容器化部署,在Dockerfile的构建上下文中,利用.dockerignore文件排除.git,避免它被打进镜像层。

6.2 Web服务器配置

这是最关键的一环,确保即使.git目录被错误地放到了Web根目录,也无法被外部访问。

Apache配置: 在虚拟主机配置或.htaccess文件中,拒绝访问所有以点开头的目录。

<DirectoryMatch "/\.(git|svn|ht)"> Require all denied </DirectoryMatch> # 或者更直接地拒绝所有点开头 <DirectoryMatch "^\.|/\."> Require all denied </DirectoryMatch>

Nginx配置: 在server块中,添加location规则。

location ~ /\.(git|svn|ht) { deny all; access_log off; log_not_found off; } # 或者更通用 location ~ /\. { deny all; }

通用方法:在Web根目录下放置一个空的index.htmlindex.php文件到.git目录内,这样当访问/.git/时,服务器会默认展示这个索引文件,而不是目录列表,但更好的做法还是直接返回403。

6.3 自动化安全检查

将Git目录扫描纳入到CI/CD流水线或定期的安全扫描中。

  • 使用静态代码分析工具:如gitleaks,可以在代码提交时扫描仓库历史中是否意外包含了密钥、密码等敏感信息。
  • 使用漏洞扫描器:在部署前或对线上环境进行定期扫描时,使用如NucleiAcunetix等工具,它们都有检测.git.svn等目录泄露的模板。
  • 人工复查:在项目上线前,养成手动检查Web根目录下是否有.git文件夹的习惯。

7. 排查与应急响应:如果泄露已经发生

如果你怀疑或已经确认自己的网站存在Git泄露,应该立即采取以下步骤:

  1. 立即隔离:如果可能,将受影响的服务器或服务暂时下线,或通过防火墙规则立即阻断对/.git/路径的访问。
  2. 清除泄露源:登录服务器,彻底删除Web可访问目录下的.git文件夹。注意:不要只删除内容,要删除整个目录rm -rf .git
  3. 评估影响
    • 检查泄露的源码中是否包含数据库连接信息、API密钥、加密盐值、后台管理路径等敏感信息。
    • 立即更改所有涉及的密码、密钥、令牌。即使它们看起来是哈希或加密过的,也应视为已泄露。
    • 审查代码历史,看是否有已修复但历史上存在的高危漏洞被暴露。
  4. 代码审计与加固:对泄露的代码进行一次彻底的安全审计,修复发现的所有漏洞。特别是因为泄露而暴露的潜在漏洞。
  5. 监控与日志分析:检查Web服务器日志,看是否有大量访问/.git/及相关文件的异常请求,评估是否已被攻击者利用。
← 返回列表