
简介这套资料面向使用爬虫技术开展网站安全检测的Java/Python开发者聚焦暗链批量检查、敏感信息泄露排查与敏感关键字扫描等典型安全审计场景适合计算机相关专业学生完成毕业设计、课程设计也适合企业安全测试人员作为巡检工具雏形。压缩包共十个文件、约十二点三兆包含Python主扫描脚本、两个exe辅助工具、六个txt规则与目标列表以及一份md说明文档目录结构精简能直接对照运行并二次改造。内容已通过导师指导答辩评审分达九十五分源码经过运行验证功能稳定。目前已有137人学习下载文档详细、资料齐全从规则配置、目标域名导入到批量扫描与结果输出均有说明可帮助读者快速构建暗链与敏感信息监测能力。1. 爬虫工具批量暗链检查一次把全站隐藏链接和敏感信息扫干净的实操记录网站被植入暗链这种事光用浏览器看是看不出来的——攻击者把外链藏进display:none或者透明 iframe页面视觉上毫无异常搜索引擎蜘蛛抓到的源码里却全是外链。这个项目就是为这个场景准备的用爬虫工具对一批站点做批量暗链检查、敏感信息泄露和敏感关键字检查。工具链由aljcscan.py主脚本、httpx.exe探活、hakrawler.exe抓链接、rules.txt规则文件组成输入targets.txt域名列表输出命中的 URL、规则和上下文。文档详细、资料齐全适合网站运维、安全测试人员也适合拿来做毕设或课设。2. 工具链拆解httpx 探活、hakrawler 抓链、aljcscan 规则匹配是怎么串起来的2.1 先探活而不是先抓包httpx.exe 的输入、输出与参数选择批量巡检最容易犯的第一个错误是拿到域名列表就直接去抓页面内容。实际执行的时候你会发现几十个域名里总有几个已经不再解析、服务端超时或直接拒绝连接这些无效目标会拖慢整个流程还会在报告里制造一堆垃圾记录。httpx.exe在这里的角色就是一道筛网用一个并发进程池去请求每个域名把存活的、能正常返回状态码的目标过滤出来供下一阶段使用。httpx 的典型用法是从文件读目标./httpx.exe -l targets.txt -o alive.txt -threads 50 -sc -title-l指定目标列表文件每行一个域名带不带http://前缀都行工具会自动补全协议。-o把存活结果写到alive.txt这一步的输出就是下一阶段爬虫的输入也是整条工具链的第一个中间产物。-threads控制并发请求数默认 50。在本地机器上跑五六十个线程没什么压力但如果目标全是公网站点建议降到 20避免触发 WAF 的封禁策略。-sc在输出里带上 HTTP 状态码方便你一眼看出哪些返回 200、哪些是 403 或 301。-title顺手抓一下页面标题标题里出现异常关键词时本身就是一个值得人工复核的暗链信号。我第一次跑这个项目时用的就是默认参数结果三分之一的目标超时alive.txt里全是空行和错误记录。后来翻 README.md 才确认这套工具链的预期流程是先探活再抓取targets.txt是给人看的主清单alive.txt才是给 hakrawler 用的真实输入。资源清单里带的是 Windows 可执行文件在 Linux 上跑要留意执行权限Windows 下直接.\httpx.exe即可。拿到工具后先执行./httpx.exe -h看一眼参数确认和你手上 README 描述的一致再动手能省不少返工时间。2.2 hakrawler 的递归抓取与 url.txt 的生成hakrawler.exe 是这一步的主角它从存活站点里把页面 URL 全部抠出来供 aljcscan 去匹配规则。以前自己写爬虫总要在链接提取、相对路径拼接、去重上花很多时间hakrawler 把这几件事一次性做掉了。它的核心行为是给一个种子地址解析页面里所有a href、script src、form action、iframe 等引用把结果输出成纯 URL 列表而不是保存页面内容所以很适合直接对接下游的规则扫描器。常见做法是让 httpx 的输出通过管道直接喂给 hakrawlercat alive.txt | ./hakrawler.exe -subs -depth 2 -plain url.txt-subs控制要不要抓子域名的链接。如果targets.txt里只写了主域名但站点有m.example.com、api.example.com这类子域开启后会被一并收进链接列表如果希望严格限定在主域范围内就把它关掉避免子域上的私密页面也被拉出来。-depth是抓取深度2 表示从种子页面开始再往下抓两层。实际使用中暗链经常藏在三级目录的页面上深度 1 会漏深度 3 以上会显著变慢大多数情况 2 是一个合理的折中。-plain只输出 URL 本身不带来源路径、状态码等附加字段这样每行都能直接当成下游请求的地址。-scope是防外链扩散的关键参数。不加限制时爬虫会把页面上引用的外站链接也抓进来后面扫描时全是无关检测。我一般会视情况加上域名范围比如-scope example.com。这一步最需要注意的地方是hakrawler 的输出应该重定向到url.txt而不是覆盖targets.txt。项目资源里同时给了这两个文件用途完全不同——targets.txt是初始资产清单url.txt是待检测 URL 集合混了之后整个扫描范围就乱了。跑完之后可以wc -l url.txt看一下收集了多少条链接如果只有十几条说明站点页面结构简单或者抓取深度不够需要调整深度参数重跑。2.3 aljcscan.py 主脚本三类检查在一个进程里怎么分工到了主脚本这一步前面收集的 URL 会被逐个请求然后和规则文件做匹配。aljcscan.py是整个工具链的核心它在一个进程内完成三类检查暗链检查、敏感信息泄露检查、敏感关键字检查。从资源结构来看脚本的设计思路是把检测项抽象成规则规则写在rules.txt里脚本只负责拿 URL、取页面、跑规则。虽然资源描述里提到了 Java 相关关键词但这个版本的实际主脚本是 Python 写的依赖 requests 库Windows 和 Linux 都能跑。简化后的核心逻辑大致是这个样子import requests from concurrent.futures import ThreadPoolExecutor def load_rules(path): 读取规则文件跳过空行和注释行 rules [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if line and not line.startswith(#): rules.append(line) return rules def scan_one(url, rules): 请求单个 URL返回命中的规则列表 try: r requests.get(url, timeout10) html r.text hits [rule for rule in rules if rule in html] if hits: return url, hits except requests.RequestException as e: return url, [request error: {}.format(e)] return None rules load_rules(rules.txt) with open(url.txt, r, encodingutf-8) as f: url_list [line.strip() for line in f if line.strip()] with ThreadPoolExecutor(max_workers10) as pool: for result in pool.map(lambda u: scan_one(u, rules), url_list): if result: print({} - {}.format(result[0], ; .join(result[1])))这段代码的要点是两个参数timeout10控制单个请求的超时时间避免某一个页面卡死拖垮整个进程max_workers10控制并发请求数意思是同一时刻最多有 10 个请求在跑既能利用并发提速又不至于把目标站点打到连接拒绝。实际 aljcscan.py 会比这个示例复杂比如会把暗链规则和敏感信息规则分开处理命中后截取上下文片段但整体轮廓是一致的。真正跑起来之后输出的每行都是一条告警URL 后面跟着命中的规则。这时候你会明白为什么 httpx 和 hakrawler 要在前面做过滤——如果url.txt里混着大量死链这一阶段的一半请求都会超时报告里一半是request error真正有价值的告警反而被淹没。工具链的意义就在这里每一级只做一件事上一级的输出就是下一级的输入链路清晰出问题也容易定位。3. 规则引擎与命中逻辑从 rules.txt 一行规则到告警报告3.1 rules.txt 的典型分类与暗链规则写法rules.txt是这个项目里最值得花时间研究的文件它决定了你能不能发现问题也决定你会不会被误报淹死。打开之后它就是纯文本文件每行一条规则#开头的是注释。按项目要实现的三个功能规则可以分成三组。第一类是暗链特征规则。暗链的常见隐藏手段是 CSS 样式所以规则要覆盖这些关键词# 暗链识别规则 display:none visibility:hidden font-size:0 position:absolute color:transparent z-index:-999这些写法背后对应的是真实攻击手法display:none让整个链接在页面里不占空间position:absolute可以把链接定位到屏幕外font-size:0让文字小到肉眼不可见。规则引擎拿到页面源码后只要发现这些特征就说明页面里可能存在隐藏外链需要进一步看上下文。第二类是敏感信息泄露规则身份证号、手机号、AccessKey 这类信息用正则匹配更高效。# 敏感信息规则 \d{17}[\dXx] 1[3-9]\d{9} AKIA[0-9A-Z]{16}第三类是敏感关键字规则直接把词表放进去每行一个词命中即告警。这里的规则内容是纯文本的匹配逻辑就是字符串包含判断简单直接但也要承担误报风险。3.2 匹配是字符串还是正则这个细节直接决定漏报率规则引擎最关键的实现细节是匹配方式。如果aljcscan.py对每一条规则做的是简单的in判断那rules.txt里写display:none就必须和源码里的写法完全一致差一个空格就漏报如果支持正则就要小心正则写得太贪婪导致性能问题。我拿到资源后先仔细看了aljcscan.py的规则加载和匹配函数确认它是逐行读取、按行判断。用字符串包含的场景下一个常见的优化是把暗链规则写成多个变体display:none display: none display: none ; visibility:hidden visibility: hidden这些变体看起来啰嗦但能显著减少漏报。反过来如果 aljcscan.py 里实际用的是正则匹配一条display\s*:\s*none就能覆盖上面三种写法。建议动手跑批量之前先确认规则文件的格式是纯字符串还是正则这决定了你后面写规则的方式。我在第一次使用时就没注意这点按正则语法写了几条规则结果匹配逻辑把\d当成普通反斜杠加字母处理安静地漏掉了一批手机号排查了很久才发现是格式不匹配。3.3 命中输出与上下文截取报告里至少要包含四个字段把规则文件准备好之后就要考虑输出报告的长相了。本地验证时可以简单点每行输出URL - 规则但落地给客户或者汇报用至少要带四样东西命中的 URL、命中的规则、上下文片段、检测时间。上下文片段是暗链人工复核的基础——你总不希望收到一份告警后还要自己打开页面去搜索关键词出现的位置。我一般在aljcscan.py的基础上会做一个小改造命中规则后在源码里找到规则的位置截取前后各 80 个字符作为上下文写入结果文件。这样在复现时直接看上下文就知道是display:none出现在链接的style属性里还是出现在某个 CSS 文件的内容中前者才是真正的暗链后者大概率是正常样式。还有一个排序细节把高频规则放前面可以稍微提升一点扫描速度。虽然规则之间没有短路关系但把耗时长的正则放在文件后面大多数 URL 只需要做前几条快速字符串匹配线程就不会被长正则拖住。规则文件按「暗链特征 → 敏感信息正则 → 关键字词表」的顺序排配合注释后续维护时能少花很多时间。4. 端到端跑通从 targets.txt 准备到报告落盘的完整命令串4.1 三个输入文件的准备规则跑之前先把三个输入文件理清楚。targets.txt是初始资产清单每行一个域名或完整 URLexample.com www.example.org http://192.168.1.10httpx 会自动补协议所以写不写http://都行。url.txt由 hakrawler 生成不用手工维护。rules.txt按上一章的规则组织。这里补充一个经验不要直接修改targets.txt作为输出文件保持原始资产列表不变每次扫描结果放到独立目录里后面做增量对比时才有的比。4.2 三种场景下的完整命令串场景一单个站点快速验证。先用一条命令把探活、抓取、扫描全串起来适合拿到工具链后先拿自己的测试站验证一遍流程是否正常echo example.com | ./httpx.exe -sc -title | ./hakrawler.exe -depth 2 -plain | python aljcscan.py -r rules.txt这里的管道写法适合快速验证但批量场景不推荐因为中间产物全部丢失出了问题不好回溯。场景二批量全流程。这是我在真实巡检时最常用的方式# 1. 探活把存活目标过滤出来 ./httpx.exe -l targets.txt -o alive.txt -threads 20 -sc -title # 2. 从存活站点抓取所有页面 URL cat alive.txt | ./hakrawler.exe -subs -depth 2 -plain url.txt # 3. 执行规则扫描输出完整报告 python aljcscan.py -i url.txt -r rules.txt -o report.txt # 4. 按规则类型二次分类方便人工处理 grep -E display:none|visibility:hidden report.txt hidden_links.txt grep -E 1[3-9][0-9]{9}|AKIA[0-9A-Z]{16} report.txt sensitive_leak.txt第 4 步的 grep 分类是很多人会忽略的环节。aljcscan 输出的是混杂的告警列表暗链、敏感信息、敏感关键字混在一起直接看很难判断优先级。用 grep 按规则特征分文件之后暗链类、信息泄露类各自归堆处理起来清晰很多。如果脚本输出的是URL - 规则格式grep 的目标就是每行末尾的规则部分如果输出的字段更多需要先看一眼报告格式再写 grep。4.3 输出目录 recon_domains 与 files 的用途项目资源里有recon_domains和files两个目录它们的用途从命名上就能猜出大概recon_domains用来按域名归类侦察结果每个域名一个子目录或一个文件方便回头看单个资产的检测情况files用来存放中间产物比如每条请求的原始响应体、抓到的链接快照等。我实际使用时会按日期归档mkdir -p recon_domains/$(date %Y%m%d) mv report.txt hidden_links.txt sensitive_leak.txt recon_domains/$(date %Y%m%d)/这样每次巡检的结果都留在独立的日期目录里不会覆盖上一次的记录。这个习惯坚持下来后你会发现排查复现和溯源都非常方便——告警单上写的是哪天的扫描数据目录里就放着那天的完整结果。关于files目录有一点要注意如果扫描目标多、页面大把原始响应体一张不落地存下来磁盘会涨得很快。我一般只保存命中告警的页面响应体没命中的不落盘这样既保留了复核素材也不会让磁盘吃紧。5. 避坑指南暗链检测里最容易翻车的五个场景5.1 页面里有暗链但 aljcscan 没报现象人工打开某个页面源码肉眼能看到隐藏链接但扫描报告里没有这条告警。原因暗链不是直接写在 HTML 里而是由 JavaScript 动态渲染到页面上的。hakrawler 抓取的是静态 HTML没有执行 JS 的能力所以动态生成的暗链对它来说是黑匣子。解决对疑似被黑的页面单独做一轮无头浏览器抓取再把抓到的渲染后源码交给 aljcscan 跑一遍规则。实际操作是先用aljcscan.py扫一遍全站找出那些带有外链特征但未命中的 URL再用无头浏览器补扫关键页面。5.2 误报爆炸display:none 出现在正常代码里现象display:none规则报了几百条告警打开看大部分是正常业务代码。原因规则是纯字符串匹配页面里任何一处display:none都会被命中包括 CSS 内部样式、正常隐藏的弹窗占位符、前端框架的样式类名。解决把暗链规则从「页面任意位置匹配」收窄到「a标签的 style 属性内匹配」。暗链的本质是隐藏的链接如果display:none出现在a的 style 里才是真正的高危特征出现在 CSS 文件里大概率是正常布局。这条可以通过改 aljcscan.py 的匹配逻辑实现先提取所有a标签再在标签内部匹配 CSS 隐藏特征误报率会明显下降。5.3 中文关键字匹配出来是乱码现象检测报告里中文关键词变成了代之类的乱码grep 根本没法过滤。原因目标页面是 GB2312 或 GBK 编码而 aljcscan.py 用 UTF-8 解码响应体中文字符全部错位。解决在 requests 请求后根据响应头或页面 meta 指定编码。实操时用requests的response.encoding结合apparent_encoding做探测或者直接兼容两种常见编码解码两遍、取任一命中。从那以后我再也不敢假定目标站一定用 UTF-8规则文件里的中文字符必须和页面实际编码对应。5.4 批量扫描卡死、内存暴涨现象跑了几百个 URL 之后Python 进程内存持续上涨最后系统卡死。原因hakrawler 抓取时没有限制深度和范围把几十万条 URL 全灌给了 aljcscan或者 aljcscan 的并发设置太大线程池堆积了大量未响应请求。解决给 hakrawler 明确加-depth 2和-scope把 URL 总量控制在几千条以内aljcscan 的max_workers从 10 起调不要一上来就 50。如果真的需要大范围扫描按域名拆分任务逐个跑避免单进程承担全部压力。5.5 探活结果全是超时或 403现象httpx 对一批公网域名全返回超时或者全部 403alive.txt 形同虚设。原因目标站点有反爬策略拦截了默认 UA 的请求或者本地网络环境对目标站点的连接质量差请求超时阈值设得太短。解决给 httpx 加上-ua参数用浏览器 UA 请求降低被 403 的概率同时把-timeout适当调大。如果是内网环境先确认目标站的访问协议是 HTTP 还是 HTTPShttpx 会自动探测但偶尔也会判断失误这时可以在 targets.txt 里直接写完整 URL减少一次错误的协议重定向。6. 把误报率降下来报告收敛与增量复核的四个实操技巧第一轮扫描建议先做「最小规则集」。只放暗链特征和敏感信息正则不加任何敏感关键字词表。原因是词表匹配的误报率通常最高——站点的正常内容里出现某些词不代表违规但「页面源码里出现隐藏链接」这个特征基本是稳定的高危信号。先把最小规则集的告警全部复核一遍确认没有误报再把关键字词表一条条加进去每加一条跑一轮看到明显误报就撤掉。这个过程听起来慢但比一次性丢出几百条告警然后逐条过滤要快得多。报告要按日期落盘并分门别类。我在第 4 章已经说过按日期归档这里再往前推一步把每次的告警列表作为基线下一次扫描只关注「新增的告警」。做法是保留一份baseline.txt新报告生成后用diff比较cat recon_domains/20250101/report.txt | sort baseline.txt cat recon_domains/20250115/report.txt | sort current.txt diff baseline.txt current.txt | grep ^ new_alerts.txt这个操作的意义在于同一批站点在无变化的情况下每次扫描结果应该是相近的真正需要关注的不是重复出现的已知告警而是本次新冒出来的命中。增量复核帮我抓住过好几次被挂马后的首页异动而没有被日常告警噪声淹没。命中告警的响应体要留一份快照这是所有技巧里性价比最高的一个。aljcscan 输出告警时顺手把命中页面的原始 HTML 存到files/目录文件名用「URL 的 MD5 时间戳」。后面做人工复核时直接在快照里搜索规则关键词就能看到完整的上下文——是布局样式还是真实暗链一目了然。如果响应体在扫描后发生变化快照还能当证据保留下来。最后一个技巧是给报告加时间戳字段并排序输出。aljcscan 默认按扫描顺序输出告警顺序就是 URL 的访问顺序没什么规律。我一般会在输出前按「命中规则分组组内按 URL 排序」这样同一个规则命中的所有地址会归在一起批量处理时不用来回翻报告。配合recon_domains目录的归档结构一次巡检从命令到归档到复核都形成了固定流程。说到底这套工具链的价值不在代码本身而在规则和流程。规则写得松告警全是噪声规则写得紧暗链就从眼皮底下溜走。我后来每次跑批量之前都强制自己先拿五个已知正常的站点跑一遍基线确认规则不会大面积误伤再对真实目标执行。这个习惯帮我少填了无数张告警单。希望帮到你。本文还有配套的精品资源点击获取