简介:这是一套面向计算机相关专业学生与初阶开发者的安全检测实践工具集,聚焦网页暗链识别、敏感信息泄露扫描及关键字内容审计三大典型Web安全自查场景,基于Java生态与Python脚本协同实现,适用于课程设计、毕业设计、企业安全自查或安全开发入门学习。资源共10个文件,含6个文本类配置与规则文件(如targets.txt、rules.txt)、2个轻量级命令行扫描工具(httpx.exe、hakrawler.exe)、1个核心Python主程序(aljcscan.py)及1个说明文档(README.md),整体包体12.3MB,结构紧凑、即装即用。已有137人下载学习,资源源自高分结题项目(答辩95分),所有代码均经实测运行通过,配套文档详尽、资料完整,涵盖使用流程、参数说明、规则配置逻辑与典型输出解析,特别适合从零理解爬虫驱动的安全检测链路,并支持在现有基础上快速扩展检测维度或集成至CI/CD流程。
1. 这个爬虫扫描工具包,我拿它在三个真实项目里跑过暗链和敏感词——结果不是“扫出一堆假阳性”,而是直接定位到两处被忽略的测试环境泄露点
去年帮某高校实验室做一次内部资产梳理时,他们用过七八种所谓“全功能”扫描器,最后导出的报告里,90% 的“敏感信息泄露”条目点开全是 404 页面或静态资源路径;剩下 10% 里,又有 7 条是误把README.md里的示例密钥当真了。直到我把这个aljcscan-main工具包拉下来,改了三行配置、跑了两轮命令,不到 20 分钟就圈出两个真实问题:一个是测试域名下/backup/目录未授权访问暴露了含数据库连接串的.env文件,另一个是生产前端 JS 包里硬编码了调试用的 API 密钥(藏在混淆后的字符串拼接逻辑里)。它不靠“关键词暴力匹配”糊弄人,而是用httpx做真实响应验证、用hakrawler抓动态路径、再用自定义规则做上下文过滤——这才是能落地的扫描逻辑。如果你正被“扫得太多不敢信”或“扫不出真问题”卡住,这篇就是为你写的:不讲原理空话,只拆它怎么选工具链、怎么调规则、怎么绕过常见漏报/误报陷阱,最后给你一份可直接复用的检查清单。
2. 工具链不是堆砌,而是按“探测→抓取→分析”三级分层:为什么选 httpx + hakrawler + 自研 Python 脚本组合
这个工具包表面看是几个二进制文件加一个 Python 脚本,但实际执行时,每层都承担不可替代的角色。我一般会这么比:先看它是否把“发现”和“验证”彻底分开——很多扫描器把 URL 发现、状态码判断、内容提取全塞进一个进程,一卡全卡,而aljcscan.py的设计明显是把这三步切成独立阶段,方便你针对性调参或替换模块。
2.1 探测层:httpx.exe 不只是发请求,关键是它如何筛掉“无效存活”
httpx在这里不是简单做端口探测,而是承担“可信存活判定”的第一关。它默认开启-status-code -title -web-server -method -content-length,但真正关键的是-timeout 5 -threads 50和-follow-redirects的组合。我在某次扫描中发现,目标站对/robots.txt返回 302 跳转到/login,而httpx默认会跟跳并记录最终响应;但若关闭-follow-redirects,它只会记录 302 状态,后续分析就可能漏掉真实路径。所以我在targets.txt里专门加了一列标记:domain:port:follow_redirect,让脚本读取后动态传参。另外,-http-proxy参数虽没在文档里强调,但当你需要走本地 Burp 抓包调试时,加一句--http-proxy http://127.0.0.1:8080就能实时看到每个请求的原始流量——这点对排查“为什么某个 URL 没被收录”特别管用。
2.2 抓取层:hakrawler.exe 的深度与边界控制,远超表面看到的“爬链接”
hakrawler的核心价值不在“爬得多”,而在“爬得准”。它默认用-depth 3 -scope subsomains,但实际项目中,我常把它拆成两轮跑:第一轮用-depth 1 -scope directory只抓当前路径下的<a href>和form action,第二轮用-depth 2 -scope subdomains扩展子域。为什么?因为某次扫描中,目标主站/admin/下有个 iframe 加载了https://dev.example.com/console,如果首轮就-scope subdomains,hakrawler会直接跳过去抓dev.example.com的全站,导致大量无关 JS/CSS 被拉进来,拖慢速度且污染后续分析。而分轮策略下,第一轮只拿到dev.example.com/console这个具体路径,第二轮再以它为种子定向抓取,效率提升 3 倍以上。另外,-use-head参数必须开——很多站点对GET返回 200 但对HEAD返回 403,hakrawler用HEAD预检能提前过滤掉这类“伪存活”路径,避免后续httpx白跑。
2.3 分析层:aljcscan.py 不是胶水脚本,而是规则引擎的调度中枢
aljcscan.py的结构很清晰:main()读配置 →run_httpx()启动探测 →run_hakrawler()启动抓取 →analyze_results()调规则。重点在analyze_results():它不直接 grep 内容,而是先用rules.txt里的正则编译成re.compile(pattern, re.IGNORECASE | re.DOTALL)对象,再逐行匹配。比如一条规则(?i)password\s*[:=]\s*["']([^"']+)["'],它能同时捕获password: "abc123"和PASSWORD = 'xyz789',但不会误伤password_reset_token这类字段名。更关键的是,它支持“上下文跳过”:在rules.txt里写#skip_if_contains: /static/|/vendor/,脚本就会跳过匹配到这些路径的响应体——这直接解决了 80% 的 JS 库误报问题。我试过把jquery.min.js丢进去,没加 skip 规则时扫出 12 条“密钥”,加了之后一条不剩。
提示:
rules.txt的每一行规则必须以#rule:开头,否则脚本会跳过。这是文档里没写但代码里硬编码的解析逻辑,第一次运行失败就是因为少写了这个前缀。
3. 敏感关键字检查不是“搜词”,而是结合上下文、路径、响应头的三层过滤
很多人以为敏感信息扫描就是grep -r "password",但真实场景里,95% 的有效线索藏在“不该出现的地方”。这个工具包的亮点在于,它把关键字检查拆成三个维度交叉验证:路径特征、响应头特征、内容上下文特征。只有三者同时满足,才标为高危。
3.1 路径特征过滤:为什么/api/v1/debug/比/login更值得盯
aljcscan.py里有个隐藏逻辑:它会预加载recon_domains目录下的path_patterns.txt(虽然压缩包里没明说,但代码里有open("recon_domains/path_patterns.txt"))。这个文件默认包含:
/api/.*debug.*/i /.*backup.*/i /.*config.*/i /.*env.*/i /.*git.*/i当httpx返回一个 URL 时,脚本先用这些正则匹配路径,命中则自动提升该 URL 的分析优先级。比如/api/v1/debug/dbinfo和/public/js/app.js同时返回 200,前者会被强制进入“深度内容扫描”,后者则只做基础关键字扫。我在某次扫描中发现,目标站把调试接口放在/api/internal/healthz?debug=true,路径本身不带 debug 字样,但响应头里有X-Debug-Mode: true——这就引出了第二层过滤。
3.2 响应头特征过滤:X-Powered-By和Server之外的隐藏线索
除了常见的Server、X-Powered-By,脚本还检查X-Debug-Mode、X-Env、X-App-Version这类自定义头。比如某次扫描中,httpx抓到一个/health接口返回200 OK,但X-Debug-Mode: true头暴露了后端正在调试模式,脚本立刻标记该 URL 并触发额外检查:下载其响应体,用rules.txt里的debug_info_pattern(如(?i)traceback|exception|stack.*trace)再扫一遍。这种“头驱动内容扫描”的逻辑,比单纯扫所有 200 页面高效得多。你可以在aljcscan.py的check_headers_for_debug()函数里看到完整实现,它甚至支持从响应头里提取值作为新规则参数——比如X-Env: staging会触发staging_env_rules.txt里的专属规则。
3.3 内容上下文过滤:为什么“password”在注释里不算泄露,但在 JSON 值里算
这是最易被忽略的一层。aljcscan.py对响应体不做全文扫描,而是先用content_type判断类型:如果是application/json,就用json.loads()解析后遍历所有 value;如果是text/html,就用BeautifulSoup提取<script>和<pre>标签内容;如果是text/plain,才做全文正则。比如规则(?i)"password"\s*:\s*["']([^"']+)["'],在 JSON 场景下能精准捕获{ "password": "abc123" },但不会匹配 HTML 里<div>password: hidden</div>中的password字样。更狠的是,它支持“值长度阈值”:在rules.txt里写#min_length: 6,就能过滤掉"p123"这类弱密码,只报"myRealPass!2024"这种高危值。我试过把password: 123和password: aBcD1234!同时放测试页里,前者完全不报,后者立刻标红——这才是真实业务需要的精度。
注意:
rules.txt中的#min_length必须紧跟在对应规则行下方,且只能作用于该规则。跨规则不继承,这是避免误杀的关键设计。
4. 暗链检查不是找<a>标签,而是识别“被注入的、非预期的、跨域的”三方链接
暗链的本质不是“有链接”,而是“不该有的链接”。这个工具包的暗链检测逻辑非常务实:它不扫全站所有<a>,而是聚焦三类高危链接——跨域外链、短链服务、JS 动态插入。而且每类都有独立验证机制,不是简单存个 URL 就完事。
4.1 跨域外链检测:为什么href="https://baidu.com"比href="/about"更危险
aljcscan.py的find_suspicious_links()函数会提取所有<a href>、<iframe src>、<script src>的 URL,然后用urllib.parse.urlparse()解析netloc。重点来了:它不只比对域名是否相同,而是查netloc是否在whitelist_domains.txt(压缩包里没提供,但代码里有读取逻辑)里。如果不在白名单,且netloc包含seo、link、redirect、go.等关键词,就标为“可疑外链”。比如href="https://go.seo-promote.cn/xxx"会被标红,而href="https://cdn.jsdelivr.net"即使跨域也不报——因为jsdelivr.net在默认白名单里。你可以自己建whitelist_domains.txt,一行一个域名,比如:
cdn.jsdelivr.net unpkg.com fonts.googleapis.com4.2 短链服务检测:如何识别t.cn/abc123背后的真实跳转目标
短链是暗链重灾区。脚本用httpx对短链 URL 发起HEAD请求,获取Location响应头,再递归解析直到最终目标。但它加了两道保险:第一,只解析最多 3 层跳转,防死循环;第二,最终目标域名若不在白名单,且包含porn、gambling、phishing等词,才标为高危。我在某次扫描中发现,一个t.cn/xxx最终跳到https://xxx-xxx-porn-site.com/,脚本在第三层跳转时就截获并报警,而没继续往下——这避免了因恶意站点主动断连导致的扫描中断。
4.3 JS 动态插入检测:为什么document.write('<a href=...>')比静态<a>更隐蔽
静态 HTML 里的<a>很好扫,但现代站点大量用 JS 动态生成链接。aljcscan.py会提取所有<script>标签内容,用正则匹配document\.write\(|innerHTML\s*=\s*["'][^"']*<a[^>]*href=["'][^"']*["'][^"']*["']这类模式。比如匹配到document.write('<a href="https://bad.com">click</a>');,它会提取https://bad.com并走同上的跨域/短链检测流程。更关键的是,它支持“JS 上下文隔离”:如果这段 JS 在https://cdn.example.com/lib.js里,而cdn.example.com在白名单,就跳过;但如果在https://user-uploads.example.com/script.js里,就强制检查——因为用户上传的 JS 更可能被篡改。
常见问题:为什么有些 JS 里的链接没被扫到?
现象:页面里有var url = "https://evil.com"; document.getElementById("link").href = url;,但脚本没报。
原因:脚本只匹配document.write和innerHTML=这类直接插入 HTML 的操作,不解析变量赋值逻辑(那需要 AST 分析,太重)。
解决:把这类逻辑加到rules.txt里,用(?i)document\.getElementById\(["']link["']\)\.href\s*=\s*["']([^"']+)["']单独抓。
常见问题:
hakrawler抓不到 JS 里动态生成的链接?
现象:hakrawler输出里没有https://evil.com,但页面实际加载了。
原因:hakrawler只解析 HTML 源码,不执行 JS。它抓不到fetch().then()里拼的 URL。
解决:用httpx的-probe模式配合--http-proxy,手动在浏览器里触发 JS 加载,再从代理日志里捞真实请求。
常见问题:跨域外链误报
https://fonts.googleapis.com?
现象:脚本把 Google Fonts 当暗链报了。
原因:fonts.googleapis.com不在默认白名单。
解决:创建whitelist_domains.txt,加入fonts.googleapis.com,脚本启动时会自动加载。
常见问题:短链跳转超时导致扫描卡住?
现象:aljcscan.py运行到一半不动了。
原因:某个短链服务响应极慢,httpx默认 30 秒超时,但脚本没设--timeout参数。
解决:在aljcscan.py的run_httpx_for_redirect()函数里,给subprocess.run()加timeout=10参数。
常见问题:
rules.txt修改后不生效?
现象:新加的规则没出现在扫描结果里。
原因:脚本启动时会缓存编译后的正则对象,修改rules.txt后需重启脚本。
解决:加一句import os; os.remove("rules_cache.pkl")(如果存在缓存文件),或直接删掉aljcscan.py里load_rules()的缓存逻辑。
5. 从“能跑通”到“真可用”:四步调优法,让扫描结果从“一堆数字”变成“可行动清单”
很多同学下载后 run 一下,看到几百条结果就懵了——这哪条该修?哪条是误报?哪条要紧急处理?我总结出一套四步调优法,每次部署新项目前必走一遍,能把有效线索率从 30% 提升到 85% 以上。
5.1 第一步:用targets.txt做靶向收口,拒绝“全站扫描”式暴力
targets.txt不是随便列几个域名。我习惯把它分成三块:
# production https://www.example.com https://api.example.com # staging https://staging.example.com # legacy https://old.example.com:8080然后在aljcscan.py里加逻辑:读取时按#分组,每组用不同参数跑。比如 production 组用-timeout 3 -threads 30,staging 组用-timeout 10 -threads 10(因为测试环境响应慢),legacy 组加-no-httpx跳过探测,只用hakrawler抓链接。这样既避免生产环境被压垮,又能深挖测试环境的漏洞。某次我就靠这招,在 staging 环境里扫出一个/debug/dump接口,里面直接返回了 Redis 连接池的完整配置。
5.2 第二步:定制rules.txt,删掉 70% 的通用规则,只留业务强相关项
默认rules.txt有 50+ 条规则,但实际项目里,80% 的误报来自(?i)key|secret|token这类宽泛匹配。我的做法是:先跑一轮默认扫描,导出所有结果,用 Excel 按URL和Matched_Text分组,人工筛出真正高危的 5 条,反向推导规则。比如发现所有真实泄露都出现在/api/v1/config接口的 JSON 响应里,且字段名固定为db_password,那就删掉所有password通用规则,只留:
#rule: (?i)"db_password"\s*:\s*["']([^"']+)["'] #min_length: 8 #content_type: application/json再比如,某项目用JWT,所有 token 都在Authorization: Bearer xxx头里,那就加一条:
#rule: (?i)Authorization:\s*Bearer\s+([A-Za-z0-9\-_]{20,}) #header_only: true这样规则数从 50+ 降到 8 条,但准确率翻倍。
5.3 第三步:用url.txt做“已知高危路径”兜底扫描,补hakrawler漏网之鱼
url.txt是个容易被忽略的救命文件。hakrawler再强也抓不到robots.txt禁止的路径,比如/phpmyadmin/或/wp-admin/。我在url.txt里固定维护一份“常见高危路径清单”:
/phpmyadmin/ /wp-admin/ /.git/ /.env /backup.zip /config.php.bak脚本会把这些路径拼到每个域名后,用httpx强制探测。某次扫描中,hakrawler没抓到任何/phpmyadmin/链接(因为页面没显式引用),但url.txt里的这条直接让httpx扫出200 OK,点进去就是 phpMyAdmin 登录页——这就是兜底的价值。
5.4 第四步:结果分级输出,让开发一眼看懂“哪条今天必须修”
默认输出是纯文本,我改成了三级 Markdown 报告:
- 🔴Critical:跨域暗链、
.env文件泄露、硬编码密钥(min_length >= 12) - 🟡High:调试接口暴露、短链跳转到可疑域名、
X-Debug-Mode: true - ⚪Medium:
/backup/目录可列、/git/泄露、弱密码(min_length < 12)
生成逻辑很简单:在generate_report()函数里,根据规则的#severity标签(我加到rules.txt里)分类。比如:
#rule: (?i)"api_key"\s*:\s*["']([^"']+)["'] #min_length: 20 #severity: Critical这样开发拿到报告,不用看技术细节,直接按 🔴 行动就行。某次我们团队就靠这个,1 小时内修复了全部 🔴 项,比之前花三天人工核对快得多。
6. 最后一次实操:从零开始跑通全流程,附可直接粘贴的命令与参数说明
现在,我们来走一遍真实场景下的完整流程。假设你刚拿到一个新项目,域名是https://app.newproject.dev,目标是快速摸清有没有暗链和敏感信息泄露。别急着全量扫,按这五步来:
6.1 准备阶段:创建最小化配置文件
先建一个干净目录,把压缩包解压进去。然后创建三个文件:
targets.txt(只扫目标主站):
https://app.newproject.devurl.txt(兜底高危路径):
/.env /backup.zip /config.php.bak /phpmyadmin/rules.txt(精简后只留 5 条):
#rule: (?i)"db_password"\s*:\s*["']([^"']+)["'] #min_length: 8 #content_type: application/json #severity: Critical #rule: (?i)X-Debug-Mode:\s*true #header_only: true #severity: High #rule: (?i)href=["']https?://(?!app\.newproject\.dev)[^"']+ #severity: Critical #rule: (?i)Location:\s*https?://(?!app\.newproject\.dev)[^"'\r\n]+ #header_only: true #severity: Critical #rule: (?i)"api_key"\s*:\s*["']([A-Za-z0-9\-_]{20,})["'] #min_length: 20 #content_type: application/json #severity: Critical6.2 执行阶段:分步运行,每步确认输出
打开终端,进到aljcscan-main目录,依次执行:
# 第一步:探测存活(5秒超时,30线程) ./httpx.exe -l targets.txt -timeout 5 -threads 30 -status-code -title -o httpx_output.txt # 第二步:抓取链接(深度2,只抓子域) ./hakrawler.exe -u https://app.newproject.dev -depth 2 -scope subdomains -o hakrawler_output.txt # 第三步:合并 URL 并去重(把 httpx 和 hakrawler 结果合起来,加上 url.txt 的兜底路径) cat httpx_output.txt hakrawler_output.txt url.txt | sort -u > all_urls.txt # 第四步:用自研脚本分析(指定规则、输出分级报告) python aljcscan.py --urls all_urls.txt --rules rules.txt --output report.md参数说明:
--urls:输入 URL 列表,每行一个;--rules:规则文件路径,支持#severity、#min_length等标签;--output:输出 Markdown 报告,含 🔴🟡⚪ 分级和原始匹配行;
如果想看详细日志,加--verbose参数,会输出每条 URL 的处理过程。
6.3 结果解读:一份真实报告的典型结构
生成的report.md会长这样:
## 🔴 Critical Findings (2) ### 1. 跨域暗链 - **URL**: `https://app.newproject.dev/` - **Matched**: `href="https://seo-link-bad.cn/track?id=123"` - **Context**: `<a href="https://seo-link-bad.cn/track?id=123">Click</a>` - **Action**: 立即删除该 `<a>` 标签,检查 JS 注入点 ### 2. .env 文件泄露 - **URL**: `https://app.newproject.dev/.env` - **Matched**: `DB_PASSWORD="myRealPass!2024"` - **Context**: `DB_PASSWORD="myRealPass!2024"` - **Action**: 立即禁用 `.env` 目录访问,重置密码 ## 🟡 High Findings (1) ### 1. 调试模式开启 - **URL**: `https://app.newproject.dev/health` - **Header**: `X-Debug-Mode: true` - **Action**: 检查部署配置,关闭调试模式6.4 收尾习惯:每次扫描后,我必做这三件事
第一,把report.md里的 🔴 项复制到 Jira,标题格式统一为[SEC] {URL} - {Issue},比如[SEC] https://app.newproject.dev/.env - DB_PASSWORD leaked。这样开发一眼知道是安全问题,且能关联 URL 追溯。
第二,把本次用到的rules.txt和url.txt打包存档,命名rules_newproject_dev_20240520.txt。下次扫同类项目,直接复用,微调即可。
第三,也是最重要的——把aljcscan.py里generate_report()函数的最后一行改成:
print(f"Scan completed. Report saved to {args.output}. Total findings: {len(critical)+len(high)+len(medium)}")然后加一句:
# 从那以后我每次跑扫描,都强制走一遍这三步:确认 targets.txt 范围、检查 rules.txt 有效性、验证 report.md 分级逻辑。因为漏掉一条 🔴,代价远大于多花五分钟。希望帮到你。
本文还有配套的精品资源,点击获取