ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Apache全局屏蔽.git目录防源码泄露配置指南

Apache全局屏蔽.git目录防源码泄露配置指南 如果你用 Apache 部署过用 Git 管理的项目那你对站点根目录下那个.git文件夹应该不陌生——它常年躺在那里看起来人畜无害但只要 Web 服务器能读到它你的整个源码仓库就可能已经在公网上裸奔了。这不是危言耸听网上现成的工具扫到.git/config两分钟就能把仓库完整拖下来。这篇文章就专门解决一个问题如何在 Apache 里做全局配置禁止任何站点、任何路径下的.git目录被外部访问并顺手把.svn、.env这类敏感文件也一起拦住。1. 为什么不能放任 .git 目录暴露在 Web 根目录1.1 一个真实攻击链条从 .git/config 到源码备份很多人觉得源码放在服务器上只要别人不知道路径就行但.git目录的问题不在于路径暴露而在于它的结构是可预测的。Git 仓库的元数据全部固定放在.git下面config、HEAD、index、refs/heads/master这些路径对每个仓库来说都是相同的。攻击者根本不需要猜直接请求http://你的站点/.git/config如果返回 200这就是一个明确的信号仓库可以被拖取。打开.git/config能看到什么最典型的是[remote origin]里的 url 字段它可能直接指向你的 GitHub 私有仓库地址甚至顺手带上用户名和公司域名信息。接下来的攻击就更暴力了用git-dumper这类工具它会把.git目录下的对象文件按照 Git 的存储格式一个个下载回来然后本地git checkout就能还原出绝大多数历史版本的文件。你以为删掉了服务器上的敏感内容Git 历史里全留着。这是一个连新手都能操作的攻击路径不需要任何权限也不需要漏洞只要 Apache 把这个目录当普通静态文件返回就行。1.2 什么样的部署方式最容易踩雷最容易中招的是这么几种情况第一种把 Apache 的 DocumentRoot 直接指向git clone出来的目录这是最危险的因为整个仓库原封不动暴露第二种用 CI/CD 工具把代码同步到 web 根目录时同步工具把.git目录也一起带过去了第三种开发机上顺手scp -r整个项目目录上传到服务器.git自然也跟着上去。很多人觉得我平时访问.git/config也没返回啊这通常是因为运气好或者恰好碰到了服务器软件带默认隐藏文件保护但 Apache 默认只保护.ht*文件并不会自动帮你挡掉.git。这就是为什么必须在 Web 服务器层面做全局的强制规则而不是依赖某一次部署时恰好没传上去。2. 动手配置前先把 Apache 的访问控制机制理清楚2.1 403 还是 404两种返回码的取舍直接拒绝访问可以返回 403 Forbidden配置简单粗暴。但 403 有个副作用它告诉访问者这个路径存在但是不允许看。扫描器拿到 403 之后就可以确认这里确实有个.git目录接下来会加大火力尝试更多工具和更多路径。如果你希望更隐蔽一些可以直接返回 404 Not Found让请求者认为这个目录压根不存在。对正常的合法用户来说他们本来就不需要访问.git所以 404 不会影响任何功能。从实际效果看两者都能阻止下载区别仅在于暴露程度。我自己的习惯是公网面向的场景一律用 404内网或测试环境用 403 也无所谓反正能看到响应的人本来就不该有恶意意图。下面给的配置示例里我会把两种写法都列出来你按需选择。2.2 Apache 2.4 的 Require 语法与 2.2 的旧语法Apache 2.4 之后目录访问控制的标准写法从旧的Order/Deny语法换成了Require语法Require all denied如果你还在用 2.2 或者老版本那要写成Order allow,deny Deny from all这里有个非常容易踩的坑2.4 版本里如果混用旧语法Apache 会直接报错甚至拒绝启动报错信息形如AH00526: Syntax error on line ...。所以在动手前先确认一下服务器版本。大部分现代发行版默认都是 2.4 了这篇文章的示例默认基于 Apache 2.4如果你确实是 2.2把Require all denied替换成上面那两行即可。2.3 DirectoryMatch、LocationMatch、FilesMatch 的适用边界Apache 里有几个看似都行的指令用错地方会出大问题。DirectoryMatch匹配的是文件系统路径配置在DirectoryMatch块里LocationMatch匹配的是 URL 路径配置在LocationMatch块里。这两者大部分场景下效果一致但有一个关键差异如果站点根目录下存在符号链接或者通过Alias把某个外部目录映射到 URL 上那么文件系统路径和 URL 路径可能对不上这时候DirectoryMatch可能匹配不到而LocationMatch是按照用户实际请求的 URL 来匹配的反而更稳定。FilesMatch看起来也可以用来匹配文件名但注意它匹配的是路径的最后一个部分。.git目录下的文件叫config、HEAD这些名称FilesMatch匹配不到层级结构所以不推荐用它做整个目录的屏蔽。这个区别很多人搞混我见过有人用FilesMatch \.git配了半天结果请求/.git/config照样能访问就是这个原因。3. 全局屏蔽配置的四种写法与选择3.1 方式一httpd.conf 里的 DirectoryMatch全局生效如果你的 Apache 管理着多个站点最省事的方式是在主配置文件里加一段DirectoryMatch它对所有虚拟主机生效。这里说的全局是真正意义上的全局和放在哪个VirtualHost里没有关系只要你把它放在 httpd.conf 的顶级位置不在任何 VirtualHost 内部。DirectoryMatch \.[.]git($|/) Require all denied /DirectoryMatch注意这里的正则在非 Windows 路径里可以直接写\.git($|/)但有些环境里反斜杠转义会有兼容性问题所以更稳妥的写法是直接用字符类[.]代替转义后的点DirectoryMatch [.]git($|/) Require all denied /DirectoryMatch这个正则会匹配任何文件系统路径中包含/.git或/.git/的目录。($|/)的作用是防止误伤类似mygitproject这样的目录——它的意思是.git后面必须是路径结尾或者斜杠。匹配到了就返回 403。如果你想返回 404可以配合RedirectMatch使用方法我放在后面。3.2 方式二VirtualHost 中的 LocationMatch单站点定向控制如果只想针对某一个站点做限制或者你的服务器上有多个虚拟主机但只有部分站点用了 Git 管理那就把规则写进对应的VirtualHost块里。推荐用LocationMatch因为它是基于 URL 匹配的语义更好理解VirtualHost *:80 ServerName example.com DocumentRoot /var/www/example LocationMatch /[.]git($|/) Require all denied /LocationMatch /VirtualHost这样配置只对example.com生效其他站点不受影响。如果你确认这台服务器上所有站点都需要保护直接把方式一的配置放到 httpd.conf 里就不用每个虚拟主机都复制一遍了。3.3 方式三conf.d 独立配置文件统一管理大型服务器上我不太喜欢直接改 httpd.conf 主文件毕竟主文件被系统包管理器管理着升级时容易冲突。更推荐的方式是在/etc/httpd/conf.d/或/etc/apache2/conf-available/下新建一个独立文件比如deny-sensitive.conf内容就是方式一的配置然后启用它。对应的操作路径因系统而异CentOS/RHEL 系把文件放到/etc/httpd/conf.d/deny-sensitive.confUbuntu/Debian 系把文件放到/etc/apache2/conf-available/deny-sensitive.conf然后执行a2enconf deny-sensitive这种做法的好处是配置模块化任何一台新服务器上线时只需要拷贝这个文件就能获得同样的防护不用记着一长串配置。3.4 方式四没有 httpd.conf 权限时的 .htaccess 方案虚拟主机用户、共享主机用户通常没有权限改主配置这时候.htaccess就是唯一选择。在网站根目录下新建或编辑.htaccess加入RewriteEngine On RewriteRule ^(.*/)?[.]git/ - [F][F]表示返回 403 Forbidden。如果你更希望返回 404可以改用RedirectMatch 404 (?i)[.]git($|/)注意(?i)让匹配不区分大小写这样/.GIT/config也能拦下来。.htaccess方案有个前提站点的Directory配置里必须允许AllowOverride All或至少AllowOverride FileInfo否则.htaccess里的规则不会生效。这一点是共享主机环境里最容易忽略的配置半天没反应多半就是 AllowOverride 没开。3.5 顺手把 .svn、.env 一起挡掉既然已经动到访问控制这一层了建议把其他常见的敏感路径一并处理。.svn和.git结构类似一旦暴露同样能拖出历史版本.env文件里经常存着数据库密码、API 密钥。可以合并成一个正则DirectoryMatch (^|/)([.]git|[.]svn|[.]env|[.]DS_Store|env[.]php)($|/) Require all denied /DirectoryMatch实际用的时候我不建议把正则写得太花哨保持可读性更重要。你可以拆成多个DirectoryMatch块或者在上面那个独立配置文件里分别列几行以后维护时一眼就能看懂。4. 配置生效验证与常见问题排查4.1 改完配置之后必须做的两步语法检查和重载无论你改了 httpd.conf 还是 conf.d 下的文件都要执行语法检查apachectl configtest看到Syntax OK再重载服务systemctl reload httpd或者使用apache2ctl系的环境systemctl reload apache2注意是 reload 不是 restart。reload 会平滑加载新配置不会中断现有请求。如果语法检查报错Apache 会拒绝重载并保持旧配置继续运行——这会导致一个隐蔽的问题你以为配置已经生效了其实服务器还在用旧配置怎么测都是无效。所以第 1 步永远先跑apachectl configtest。4.2 用 curl 模拟请求验证返回码配置好后不要只在浏览器里试浏览器会缓存结果不够直观。用 curl 看状态码最干净curl -I http://your-server/.git/config curl -I http://your-server/.git/HEAD curl -I http://your-server/subdir/.git/config按照你的配置方式预期看到的响应是 403 或 404。如果看到 200说明配置没有生效。需要确认几个细节第一条命令请求的是/.git/config如果你的站点有一套重写规则把请求都做了重定向curl 默认不跟随重定向返回 301 也算正常这时候加-L跟随重定向再看最终状态码。4.3 排查链路为什么你的配置没有生效我排过太多次这种我明明写了规则的问题基本上跑不出这几类原因第一配置写错了层级。LocationMatch必须放在 Server 配置级或 VirtualHost 内不能包在Directory里DirectoryMatch不能放在Location里。结构错了Apache 在语法检查时就会报错但有些人语法检查没过就直接 reload等于白改。第二虚拟主机覆盖问题。如果AllowOverride All开启了站点根目录里的.htaccess可能会覆盖主配置文件里的规则。尤其是某些框架或面板生成的.htaccess里写了Require all granted会把你的Require all denied顶掉。我的排查习惯是先在目标站点根目录下看有没有.htaccess有的话临时改名再访问一次看看是否恢复 403/404就能确认是不是被它覆盖了。第三符号链接直穿。站点目录里如果存在符号链接指向外部路径Apache 的Directory匹配基于实际文件路径可能会匹配到你意想不到的位置。这种情况下LocationMatch更可靠因为它只认 URL。第四请求被其他 RewriteRule 抢先处理了。如果你有一个RewriteRule .* index.php之类的入口规则位于访问控制规则之前.git的请求可能已经被推进了应用层路由而 Apache 的目录访问控制规则还没执行到。最简单的排查方式是把.git的屏蔽规则放在 RewriteRule 之前或者使用DirectoryMatch这类与重写模块相互独立的机制来拦截。5. 纵深防御Apache 之外还能做点什么5.1 不在 Web 根目录放版本库让规则只是最后防线Apache 规则再严密也只是挡拆更好的策略是压根不让.git出现在服务器上。如果你的部署流程还停留在登录服务器git clone然后改 DocumentRoot 指过去请花十分钟升级一下发布代码用git archive导出干净快照或者rsync同步时用--exclude .git。服务器上的正式站点目录里就不应该有.git有的话等于人为制造攻击面。Apache 规则是用来兜底的不是用它来给错误部署方式擦屁股的。顺便说一句Git 服务端仓库一般会放在/var/git这种和 Web 根目录完全隔离的位置通过 SSH 协议访问做到用户名密码校验。不要把裸仓库也丢到 web 目录下面——裸仓库没有工作副本但依然可以被下载泄露形式不同而已。5.2 定期检查站点目录里的隐藏文件配好规则之后建议每季度扫描一遍站点根目录下的隐藏文件和敏感文件find /var/www -name .git -o -name .svn -o -name .env | head -50跑出来的结果如果是空说明部署流程是干净的如果发现.git说明最近一次发布又有人把仓库带进 Web 根目录了。发现后先删除再排查发布脚本哪里出了问题。这种发现即删的动作不要完全依赖自动化至少第一次上线时要人工过一遍。5.3 记录响应日志留意扫描行为最后提一个容易被忽略的小技巧访问.git的请求本身可以作为入侵信号。在 Apache 访问日志里搜一下grep \.git /var/log/apache2/access.log能看到大量来自不同 IP 的.git/config请求说明已经有扫描器盯上你的站点了。即使它没成功下载也意味着你的 IP 进入了某个扫描目标列表接下来要更留意其他路径的异常访问。日志不是专门为.git设计的但它确实是最便宜的告警通道之一。我排查到这条规律后每次新站点上线都会先看一周日志再决定要不要额外加 WAF 规则。整个配置过程捋下来其实并不复杂选择哪种写法完全取决于你的服务器环境和站点数量多站点共享一台服务器上统一做全局DirectoryMatch最省心只能改.htaccess的场景用RedirectMatch 404最简洁。配置本身只是十几行的事情真正的价值在于理解它为什么有效、在哪种情况下会失效。我个人更倾向于在部署流程里直接杜绝.git进入 Web 根目录这样 Apache 里那层规则就只是防御纵深中的一道保险而不是唯一的保命符。如果你现在的站点还没有做过这一步防护现在动手改还来得及几分钟的事别等扫描器先找到你。
返回列表