1. 破题:这不是攻击教程,而是防御者的资产排查清单
看到"Google/GitHub Hacking"这个标题,可能有人会下意识认为这是攻击者的技巧合集。我先说清楚我的立场:我在这里讲的是防御视角的主动暴露面排查——也就是用搜索语法去发现“自家或客户公司有哪些敏感信息被公开到了互联网上”,然后做收敛、处置、持续监控。这个思路在甲方安全团队里叫"暴露面管理",在乙方叫"红队信息收集",本质殊途同归:如果你不主动发现这些入口,真正有恶意的人就会替你发现。
我先用一个具体的例子说明这件事有多现实。假设某个公司有一台面向公网的备份服务器,运维图省事,把一份数据库备份压缩包放在站点根目录的/backup/路径下,文件名叫db_backup_2024.zip。如果没有搜索语法,这个文件可能一直躺在那里不被人注意;但只要用site:example.com filetype:zip这类指令扫一下,它就会暴露在结果里。你问一个普通运维"你们公司有没有这种情况",他大概率摇头;但你去搜索引擎里查一下,往往能查到意外惊喜。
这个内容适合谁来学?我认为最值得花时间的是三类人:
- 甲方安全工程师、运维工程师,需要定期梳理公司数字资产暴露面,做上线前安全自检。
- 开发工程师,想知道自己的代码提交、测试配置、注释里的账号密码会不会被公开搜索引擎收录。
- 做威胁情报和安全研究的朋友,用自动化监控的方式跟踪外部针对特定关键词的泄露线索。
这篇文章的核心就一句话:搜索语法的本质,是你用公开的数据源换取暴露面情报。而自动化监控,是把“查一次”变成“每天查”“每次发布都查”,从一次性的信息猎捕进化成长效的资产哨兵机制。后面我会陆续展开:搜索语法怎么用、怎么组合出高精度查询、怎么用脚本做成定时巡检,以及在落地过程中容易踩的坑。
2. 整体思路:为什么搜索引擎能成为暴露面情报源
2.1 搜索引擎收录的不仅是网页,更是"资产指纹"
大多数人对搜索引擎的理解是“找网页的”。但从安全工作者的视角来看,搜索引擎其实是一个巨大的被动资产测绘数据库。只要某个资源被爬虫抓取过,并且没有被robots.txt明确屏蔽,它的 URL、标题、文件类型、部分正文文本就可能被索引。
我习惯把被索引的内容分成三类:
- 显性资产:公司官网、帮助中心、开发者文档、开放 API 页面。这些本来就是公开给用户看的,暴露不叫泄露。
- 半显性资产:带参数的后台入口、测试环境页面、未打补丁的框架报错页、上传目录。这些往往不在官网导航里,但爬虫可能通过外链或目录遍历收录到了。
- 隐性资产:备份文件包、配置文件、日志文件、带有敏感字段的源码片段。这是最危险的一类,因为它们设计的初衷就不该出现在公网。
Google Hacking(通常叫 Google Dorking)的核心思路,就是针对第三类来做“按图索骥”:你不需要知道某个公司有没有泄露,你只需要用语法去匹配那些只有目标机密文件才有的特征。
2.2 从"打猎"到"放哨"的思维转变
很多人刚接触搜索语法时,会兴奋地搜着玩:今天搜到某个站点的配置,明天看到某个仓库的历史密钥。但发现一个删一个,只是被动响应。你真正该建立的,是持续发现-验证-处置-监控的闭环:
- 第一次做全量排查时,用一批高覆盖率的查询语句,把已知域名、已知代码托管组织、已知邮箱后缀全部扫一遍。
- 发现的问题分优先级处置:高危的立刻下线、轮换密钥,中危的排期修复,低危的记录在案。
- 处置完之后,不是结束,而是把这些查询语句保存下来,做成定时任务,每 12 小时或每天跑一轮新的查询。
- 监控输出的结果与上次比对,只把新增的、变化的内容丢到通知渠道里。这样就从“被动等人告诉你”变成“主动等着情报上门”。
这也是我把文章标题定为“从信息猎人进化到资产哨兵”的原因。猎人靠嗅觉去搜一次,哨兵靠流程去盯长期。
2.3 这套方案的适用范围与现实局限
搜索语法不是万能的,先认清边界,能少走很多弯路:
- 它只能发现已被公开索引的内容。如果某个文件设置了严格的访问控制,或者刚上传还没被爬虫收录,搜索引擎是看不到的,这是时效差。
- 它受制于搜索引擎的处理能力。Google 能索引 web 网页、部分文件类型,但对动态页面、需要登录的内容无能为力。
- GitHub 代码搜索只能搜到代码托管平台公开仓库中被索引且属于默认分支的文件。某个分支、某个提交历史里藏了密钥,不一定能被搜到,除非用专门的提交历史搜索。
所以我的定位是:搜索语法是暴露面发现的“第一漏斗”,它能用低成本筛出 60% 的常见问题,剩下的靠自动化工具、日志审计、源码扫描去做。这篇文章里,我重点把第一漏斗走通。
3. 搜索语法拆解:Google 与 GitHub 的高阶姿势
3.1 Google 搜索语法速查表与应用示例
虽然现在很多人日常搜索习惯只用关键词,但做暴露面排查时,语法指令是必须掌握的。我按实用程度整理了一批:
| 语法 | 作用 | 暴露面排查示例 |
|---|---|---|
site: | 限定搜索某个域名或站点 | site:example.com查看该公司被收录的所有页面 |
inurl: | 匹配 URL 中的关键字段 | inurl:/backup/找备份目录;inurl:/wp-admin/找后台 |
intitle: | 匹配页面标题关键词 | intitle:"index of"找目录列表页 |
filetype: | 限定文件扩展名 | filetype:env找环境变量文件;filetype:sql找数据库备份 |
intext: | 匹配页面正文字段 | intext:"password"配合站点限定找敏感文本 |
cache: | 查看搜索引擎缓存的历史版本 | 页面已下线但缓存仍在时能捞出旧内容 |
* | 通配符,代替任意词 | site:example.com "admin*"匹配多种后台路径 |
- | 排除关键词 | site:example.com -filetype:html去找非网页文件 |
单独看每个语法都很简单,但实际排查中几乎都是组合使用的。我给你举几个我经常用的组合:
site:example.com filetype:xls OR filetype:pdf OR filetype:docx:先摸一下该域名下有哪些非网页文件,这些通常是文档和表格,容易带内部信息。site:example.com inurl:phpMyAdmin:快速找数据库管理入口的暴露。intitle:"index of" inurl:/config/:找开放目录列表,往往能看到一堆配置文件。"@example.com" "password" filetype:log:用邮箱后缀和密码关键词,匹配日志泄漏。
这里我要特别强调一个实操感悟:Google 的搜索语法并不总是稳定的,同一个查询可能今天有结果明天没有,而且它会根据用户偏好调整排序。所以不要依赖单次结果,要用多组语句交叉验证,最好配合其他搜索引擎和工具平台一起看。
3.2 GitHub 代码搜索语法速查表与应用示例
GitHub 的代码搜索与普通网页搜索不同,它有一套自己的限定符(qualifier),在搜索框和 API 中都能用。我按功能分了几个组:
- 范围限定:
repo:指定仓库,owner:/user:/org:指定账号或组织。 - 代码内容限定:
language:指定编程语言,path:限定文件路径,extension:限定扩展名,filename:限定文件名。 - 时间限定:
created:/pushed:/committed:配合日期,比如pushed:>2024-01-01。 - 符号限定:
"精确字符串"、AND、OR、NOT,以及常见的size:按文件大小过滤。
举几个我在GitHub上排查泄露最常用的查询示例:
# 找疑似密钥文件 filename:.npmrc _authToken filename:.env APP_SECRET extension:yaml password: org:某组织名称 "api_key" NOT test path:config secret_keyfilename:.env APP_SECRET的意思是“在所有公开仓库中查找文件名是 .env 且内容包含 APP_SECRET 的文件”。这种组合查出来的基本上都是环境变量配置泄露,实战命中率很高。
你可能会问:GitHub 上这么多公开仓库,搜出来的结果不怕多吗?严格来说,单纯搜password结果会非常多,所以 GitHub 的检索逻辑更倾向于“限定文件路径或文件名”。对比一下这两种查询:
password:会命中大量源码中的变量赋值、函数参数,噪音大。filename:.npmrc _authToken只匹配 npmrc 文件里带认证 token 的记录,精准度极高。
这也是我在这篇文章里想反复强调的核心思路:搜索语法最大的门槛不是记住语法,而是理解搜索引擎如何构建索引,以及如何用低噪音的特征去过滤数据。
3.3 三类最容易泄露的高价值资产
结合我实际的排查经验,以下三类文件在 Google 和 GitHub 上的出现频率最高,处置优先级也最高:
- 环境变量文件:
.env、.env.production、application.yml、config.php、settings.py。里面通常有数据库口令、API Key、云平台密钥对。特征是文件名固定、字段命名规律。 - 云平台密钥对:
aws_access_key_id、AKIA...开头的密钥、阿里云AccessKey ID、腾讯云SecretId/SecretKey。这类一旦暴露,攻击者可以直接调用云 API 创建机器、读取存储桶。 - 数据库与中间件配置:
jdbc:mysql://连接串、redis密码、mongodb://链接、spring.datasource.password。特征是协议前缀明确、常出现在配置文件中。
围绕这三类,可以构建一个常规的“高价值目标查询清单”。后面我讲自动化监控的时候,会用这个清单作为默认规则库。
4. 核心细节解析:搜索语法背后的收录逻辑与误报根源
4.1 为什么你以为藏得很深,搜索引擎还是能扒出来
不少开发者和运维有过这样的困惑:“我明明没把这个文件放到官网导航里,怎么搜索引擎还能搜到?”原因有三个:
- 爬虫会顺着所有外链爬取。某个页面引用了
/backup/db.zip的下载链接,或者同事在内部文档里贴了完整 URL,爬虫顺藤摸瓜就能抓取。 - 很多 Web 服务器对目录请求默认返回目录列表,即使没有超链接,爬虫发现目录后会把里面的文件名全部索引。
- 公共组件、开源模板会预置默认路由,比如 PHPMyAdmin、Tomcat Manager、Swagger UI,这些路径是公开规律,爬虫会主动探测。
在 GitHub 场景下,逻辑更加简单:只要仓库是 public,任何文件默认对所有人可见。有的团队以为“只是放上去管理一下”就没事,却忽略了平台本身就会对公开代码建立全文索引。
4.2 高命中率组合查询的模式总结
我总结出一套“高命中率查询公式”,本质是三层结构的叠加:
限定范围(site/org/repo/path) + 限定文件特征(filetype/filename/extension) + 限定内容关键字(password/api_key/secret/token)
拿这个公式去套,基本不会跑偏。举几个实例:
site:example.com filetype:sql + "INSERT INTO" + "password":找 SQL 备份中可能存在的账号表。org:某开源组织 filename:application.yml + "spring.datasource.password":找 Spring Boot 项目配置。site:example.com "AccessKey ID" + "LTAI":匹配某云厂商 AccessKey 的特征前缀。
第三层的关键字选择有时候需要发散思维。比如密钥不一定叫password,也可能是passwd、pwd、secret、credential、auth_token、private_key。我的建议是建立一套关键词字典,按“基础词 + 扩展词”去轮换扩充。
4.3 误报的三类根源与过滤思路
凡是做过实际排查的人都知道,搜索结果的“脏”是常态。误报主要来自三个方向:
- 开发测试变量:Demo 项目、教程代码里大量出现
password = "123456"、api_key = "test",这类内容没有真实危害。 - 代码注释或文档示例:文档里写“请把
YOUR_API_KEY替换为真实值”,这种内容是写给使用者看的示例,并非泄露。 - 历史存量噪音:某些仓库被搜索引擎收录时内容很敏感,后来修复删除了,但索引缓存还会保留一段时间。
我通常的做法是:先按文件路径、文件名的特征过滤一轮(优先看.env、config 目录、部署目录),再根据内容上下文判断(有没有真实域名、IP、完整密钥前缀),最后才是人工复核。优先级永远是:文件特征 > 内容特征 > 上下文特征。
5. 实操过程:以 GitHub 为目标搭建暴露面监控脚本
5.1 思路概览与 API 选择
Google 搜索自动化相对受限,因为官方没有公开的网页搜索 API,第三方封装又不稳定。相比之下,GitHub 的代码搜索 API 开放、文档清晰,是自动化监控最合适的起点。我选择 GitHub 作为第一个自动化目标的原因有三个:
- 检索接口公开,且身份认证后使用更稳定。
- 搜索维度丰富:代码、仓库、提交历史、Issue 都能搜。
- 平台数据更新及时,代码推送后很快就能被检索到,适合做“上线即监控”。
整体流程是:用search/code接口跑一组预先设定的查询规则,把结果里的仓库名、文件路径、匹配片段存下来,与上次运行结果做差集,把新增项推送通知。
5.2 前期准备:Token 的申请与权限边界
调用 GitHub Search API 需要在账号设置里生成一个 Personal Access Token(简称 PAT)。我用的是 Fine-grained token,权限只需要勾选Public repositories下的Contents: Read-only,不需要写权限。设置原则是“最小够用”,token 只在脚本所在机器上保存,环境变量注入,不要写死在代码或任何公开配置文件里。
注意区分:代码搜索 API 的速率限制是每分钟 10 次请求,普通仓库搜索是每分钟 30 次,未认证的请求会被限制得更严格。后面设计脚本时,要把这个限制算进调度逻辑里,否则跑不了几轮就被限流。
5.3 查询规则库的设计
我把查询规则设计成一个双层结构:
- 基础规则:按文件特征匹配,适合捕捉新出现的泄露文件。例如
filename:.env、filename:.npmrc、filename:config.php、extension:sql。 - 内容规则:按高价值关键字匹配,适合捕捉已有仓库中新增的敏感内容。例如
spring.datasource.password、aws_access_key_id、api_key。
每个规则之间用逗号分隔,脚本按数组顺序轮询执行。设计时要给每个规则配一个“危险等级”字段,方便处置时排优先级。
5.4 代码实现:一个可用的 Python 监控脚本
下面这段脚本是我在实际工作中精简后的版本,适合用来理解完整逻辑。我把无关业务的部分剥掉,保留核心流程:
import requests import json import os from datetime import datetime, timedelta # 读取环境变量中的 token,不要硬编码 GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") SEARCH_URL = "https://api.github.com/search/code" # 查询规则列表,每一项是 (规则描述, 查询语句, 危险等级) RULES = [ ("env文件泄露", "filename:.env", "high"), ("npm凭证泄露", "filename:.npmrc _authToken", "high"), ("数据库口令", "path:config extension:yml password", "medium"), ("云密钥特征", "extension:json aws_access_key_id", "high"), ] HEADERS = { "Authorization": f"token {GITHUB_TOKEN}", "Accept": "application/vnd.github+json", } def search_code(query: str) -> list: """执行单条查询,返回结果中的代码片段元信息列表""" items = [] page = 1 while True: params = { "q": query, "per_page": 30, "page": page, } resp = requests.get(SEARCH_URL, headers=HEADERS, params=params, timeout=15) if resp.status_code != 200: print(f"查询失败: {resp.status_code}, 规则: {query}") break data = resp.json() items.extend(data.get("items", [])) page += 1 if page > data.get("total_count", 0) // 30: break # 保守限速,避免触发第二重限制 time.sleep(6) return items def load_seen() -> set: """加载已经处理过的结果标识,用于增量比对""" seen_path = "seen_results.json" if os.path.exists(seen_path): with open(seen_path, "r", encoding="utf-8") as f: return set(json.load(f)) return set() def save_seen(seen: set): with open("seen_results.json", "w", encoding="utf-8") as f: json.dump(list(seen), f, ensure_ascii=False) def format_notification(item: dict, rule_name: str, rule_level: str) -> str: """把结果格式化成消息文本,方便接入机器人通知""" repo_name = item["repository"]["full_name"] file_path = item["path"] html_url = item["html_url"] return f"[{rule_level}] {rule_name}\n仓库: {repo_name}\n文件: {file_path}\n链接: {html_url}" def main(): seen = load_seen() new_items = [] for rule_name, query, level in RULES: try: results = search_code(query) except Exception as e: print(f"规则执行异常: {rule_name}, error={e}") continue for item in results: # 用 仓库名 + 文件路径 做去重标识 repo = item["repository"]["full_name"] path = item["path"] marker = f"{repo}/{path}" if marker not in seen: seen.add(marker) new_items.append(format_notification(item, rule_name, level)) print(f"规则完成: {rule_name}, 本次新增: {len(new_items)}") save_seen(seen) # 新增结果写入本地文件,你需要再把它接入邮件/IM 机器人 if new_items: with open("alert_messages.txt", "w", encoding="utf-8") as f: f.write("\n".join(new_items)) print("发现新增敏感信息,请查看 alert_messages.txt") else: print("本次巡检无新增结果") if __name__ == "__main__": main()这段脚本的核心要注意三点:
- 增量去重用
仓库名/文件路径作为标识,能有效避免同一条记录反复报警。 - 搜索接口是按相关度排序,不是严格按时间排序。如果你关心“最近新增”,可以在规则里加上
pushed:>YYYY-MM-DD或committed:>YYYY-MM-DD精确定位。 - 脚本只是“监控端”,结果出来后建议再接一个通知渠道。我在企业里常用的是企业微信机器人或者邮件告警,格式就是上面
format_notification生成的纯文本。自用的话存到本地文件或者直接打印即可。
5.5 定时调度:从脚本到哨兵
脚本写完后,还要解决“谁来定时跑”的问题。最朴素的做法是服务器上挂 crontab:
0 9 * * * cd /path/to/script && /usr/bin/python3 monitor.py如果不想维护服务器,可以用 GitHub Actions 的schedule触发器,把脚本直接放在一个私有仓库里,每天定时执行一次,结果推送到仓库的 Issue 或外部通知服务。这样做的好处是不需要额外购买服务器,坏处是执行节点在云端,要留意脚本里的 token 权限隔离。
我个人更推荐在服务器上跑,因为排查脚本可能会持续演进出更复杂的数据处理逻辑,在本地可控性更强。初期阶段也没有必要搞容器化,一个虚拟环境 + 一个 cron 任务就够了。
5.6 针对 Google 搜索的自动化补充
Google 搜索没有官方 API,自动化方式比较有限。但有一个替代方案:用r.jina.ai这类自然语言搜索代理服务去间接检索,它对普通网页内容的效果还行。我试用过几次,作为辅助情报源是够用的,但不建议作为主监控渠道。
替代思路是:把主要监控精力放在代码托管平台和某些第三方威胁情报平台,它们有相对开放的检索接口,自动化程度更高。Web 搜索引擎的监控可以降低频率,每周手动核查一次即可。
6. 处置流程与避坑经验:发现泄露之后怎么办
6.1 泄露信息分级确认与处置路径
发现敏感信息后的第一反应不是删文件,而是先冷静评估。我一般按这个顺序走:
- 截图留存证据。记录发现时间、来源链接、内容片段,材料要保存在本地或内部系统。
- 判断有效性。打开文件看内容,确定里面是真实凭证还是测试占位符。比如
AKIA开头的密钥可以直接用官方工具校验;数据库口令可以尝试连通性测试,但不要真的去读取数据。 - 上报并定位责任人。通过仓库提交记录、文件路径、服务器访问日志找到对应的负责人,确认文件用途。
- 执行收敛。删除线上文件、回收云密钥、重置数据库口令、修改应用配置。
- 复盘根因。是运维流程问题、开发习惯问题,还是第三方服务导致的,要落成整改项。
这也解释了为什么我强调“哨兵”思维:查一次只能解决存量问题,如果不对流程做整改,同样的泄露模式隔几个月就会复发。
6.2 需要格外小心的两类“假泄露”
我在实际排查中遇到两种最容易被误判为“严重泄露”的情况,特地列出来帮大家避坑:
- 代码生成器的硬编码占位符:像
sk_test_XXXX、AKIAIOSFODNN7EXAMPLE这类字符串经常出现在框架文档、单元测试和 SDK Demo 中。它们的特征是前缀规则正确,但内容一眼就能看出是示例。确认时只需检索前 8 位字符是否匹配真实密钥校验规则,即可快速判断。 - 私有截图泄露:在某个公开仓库的 README 中,开发者贴了一张调试截图,里面恰好显示着数据库连接串。这类信息不会出现在代码搜索里,只能靠人工浏览仓库页面时发现。这也是为什么自动化工具不能完全替代人工巡检,它只能把“大部分常见问题捞掉”,剩下需要人眼判断。
6.3 与内部同事协作时的沟通技巧
涉敏信息处置不可避免地要拉上研发和运维协作。这里有一个非常现实的经验:别用“你泄露了密钥”这种话开头,容易激起防御心理。我习惯的说法是:“在公开代码库中发现了一个疑似配置信息,先确认这个环境是否还在使用,如果已停用我们直接清理,如果还在用需要一起替换。”
这种方式效率高很多。因为大多数配置文件泄露不是主观故意,而是习惯问题,提醒+协助解决比追责更能推动整改落地。
7. 常见问题与排查技巧实录速查表
下面这个速查表是我这段实操经验的浓缩版,把它打印出来贴在工位上,遇到问题直接对着查:
| 现象 | 原因 | 处理方式 |
|---|---|---|
| GitHub 搜不到目标代码,但确认仓库存在 | 代码搜索只索引默认分支,或仓库未被平台收录 | 确认当前分支是否为 default,检查仓库可见性 |
| 代码搜索提示要求登录 | 未认证的代码搜索受限 | 为脚本配置 token,或先登录网页端搜索 |
| 搜索结果不是最新提交 | 平台索引与仓库推送存在延迟 | 使用committed:>时间参数配合人工复核 |
| 查询结果噪音太大 | 关键字太宽泛 | 改用 filename/path 限定文件特征,再叠加内容关键字 |
| 搜索 API 报 403 | 速率限制触发 | 降低请求频率,token 权限检查,每两条规则间隔 6 秒以上 |
| 配置文件中有明文口令 | 常见的低级问题 | 立即轮换,并在仓库中移除;建议引入 secret scanner 工具做提交前检查 |
| 某个文件在网页搜索可见但 GitHub 搜不到 | 网页搜索收录了 GitHub 的页面缓存,代码搜索索引不同步 | 以代码搜索为准,网页搜索结果优先人工复核 |
我还想补充一条通用技巧:排查敏感信息时,不要只盯代码仓库的当前状态,提交历史也值得翻一翻。很多团队删掉了当前分支的密钥文件,但提交历史里还留着完整记录。GitHub 提交历史搜索的入口在搜索框切换“Commits”标签,语法同样支持关键词与仓库限定。不过要注意这事的合规性边界——对自家资产做历史排查没问题,对第三方资产做深度挖掘就要慎重,严格在授权范围内操作。
8. 写在最后:监控不是目的,收敛才是
如果你已经能熟练地用 Google 语法和 GitHub 语法找出敏感信息,我恭喜你掌握了“猎人”的技能。但真正让我觉得这套方法有价值的,不是“能搜出来”,而是“能在造成实际影响前发现并处置”。
我自己的体会是,搜索语法再怎么花哨,也只是暴露面管理的第一步。完整的安全工作流还需要:定期的自动化巡检、有效的告警通知、明确的处置 SOP,以及团队意识的培养。你可以先从最简单的 GitHub 脚本开始,跑通一轮“发现-通知-处置”流程,再逐步加入 Google 和其他平台的补充监控,慢慢形成属于自己团队的一套“资产哨兵机制”。
最后分享一个小建议:所有自动化脚本用到的查询规则库,平时就要注意维护。关键词是会和现实一起演变的——今年流行的框架配置项、云平台密钥前缀,可能过两年就换了。每个月花半小时更新规则库,比临时想起来查一次要管用得多。