ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Seninfo敏感信息扫描工具:原理、实践与避坑指南

Seninfo敏感信息扫描工具:原理、实践与避坑指南

简介:敏感信息扫描工具Seninfo是一套基于Python实现的源码安全检测资源,面向开发者、代码审计及渗透测试人员,用于批量检查项目中可能存在的密码字段、身份证号、手机号、邮箱和IP地址等敏感数据。压缩包共60个文件,以38个pyc编译模块和21个py源码文件为主,整体体积仅314KB;其中seninfo.py为入口脚本,附带xlwt等依赖库源码与编译版,excel-formula.g为Excel公式解析的语法文件,便于用户直接运行,也可二次修改检测规则。工具内置常见敏感信息匹配规则,支持将扫描结果导出为Excel文件,并可通过自定义规则扩展检测模式,适合在代码提交前、安全审计或渗透测试阶段快速定位泄露风险,提升自查效率。目前已有1557人学习下载,对于希望低成本引入敏感信息扫描能力的Python项目团队而言,是一份轻量且实用的工具资源。

1. 敏感信息扫描工具Seninfo:一款面向代码审计的规则化检测器

在代码审计过程中,最怕的不是逻辑漏洞,而是某个成员把测试环境的数据库连接串随手提交到了仓库里。敏感信息扫描工具Seninfo就是为这类场景设计的自动化检测器,它通过正则规则、熵检测与文件类型过滤三层机制,在源码目录、Git历史提交和发布压缩包中快速定位疑似泄露的API密钥、Token、私钥和数据库连接信息。对开发团队来说,它可以作为提交前的自检手段;对渗透测试而言,它能在信息收集阶段快速圈定攻击面。本文从工具原理、安装部署到实际使用,完整讲清这套扫描流程,以及我踩过的那些坑。

2. 扫描器的检测原理:规则匹配、熵检测与文件过滤三层机制

2.1 第一层:基于正则的规则匹配

Seninfo的核心检测能力建立在规则引擎上。每一条规则由四个要素组成:规则ID、匹配模式(正则表达式)、严重级别和说明文字。工具内置了大约120条初始规则,覆盖常见的云厂商密钥格式、JWT Token、私钥文件头部、通用密码字段等。

# 自定义规则示例:检测阿里云AccessKey - id: custom-aliyun-ak pattern: "(LTAI)[A-Za-z0-9]{12,20}" description: "阿里云AccessKey ID" severity: high tags: ["cloud", "aliyun"]

规则文件的加载逻辑很简单。YAML格式的规则文件里,pattern字段是Pythonre模块兼容的正则串,severity分为critical、high、medium、low四档。加载时工具会预编译所有正则,扫描过程中按顺序逐个匹配文件内容,命中后记录行号和上下文。这里的性能关键在于正则的写法——如果写成.*开头或者包含大量回溯分支,扫描速度会指数级下降。我在实际使用中习惯在规则里使用re2语法风格,避免灾难性回溯。

2.2 第二层:基于香农熵的高随机性检测

正则规则只能命中已知格式的密钥,遇到自定义格式或者经过简单编码的Token就会失效。Seninfo的第二层检测机制是熵检测。香农熵衡量一段数据的随机程度,密钥和Token通常是由伪随机算法生成的,其字符分布均匀,熵值明显高于普通代码文本。

import math from collections import Counter def shannon_entropy(data: str) -> float: if not data: return 0.0 freq = Counter(data) length = len(data) entropy = -sum((count / length) * math.log2(count / length) for count in freq.values()) return entropy

上述代码是Seninfo熵计算的核心逻辑。实现上,工具先将候选字符串按长度切分成滑动窗口,默认窗口长度是16个字符,步长为4。然后对每个窗口计算熵值,如果熵值高于阈值(默认为4.5),就标记为疑似高随机性字符串。这里的阈值设置很关键——设低了会大量误报,设高了会漏掉短密钥。我在调优时的经验是:8字符以上的密钥,阈值设4.2比较合理;如果扫描的是代码量很大的仓库,可以抬高到4.6来减少噪音。

2.3 第三层:文件类型与路径过滤器

如果对仓库里的所有文件做全量正则匹配和熵计算,效率会很低,而且会引入大量无效告警。Seninfo在扫描前会经过一个文件过滤器,这个过滤器基于扩展名白名单、路径黑名单和文件内容特征三层判断。

# 文件过滤规则配置示例 include_extensions: [".py", ".js", ".java", ".go", ".php", ".rb", ".yaml", ".yml", ".json", ".env", ".properties", ".conf"] exclude_paths: ["node_modules", ".git", "vendor", "dist", "build", ".idea"] exclude_extensions: [".min.js", ".map", ".png", ".jpg", ".lock"] skip_binary: true max_file_size: 1048576

include_extensions列表限定了只扫描这十几类常见的源码和配置文件,避免扫描图片、视频等无意义文件。exclude_paths排除依赖目录和构建产物,这些目录里的密钥即使存在,对代码审计的参考价值也很低。max_file_size限制单文件最大1MB——超过这个体积的文件要么是打包产物,要么是数据文件,逐行扫描耗时且收益低。文件过滤器是减少误报的第一道闸门,它的配置质量直接决定整个扫描结果的可用性。

3. 部署与基础使用:从安装到首次扫描的完整命令

3.1 安装方式与运行环境准备

Seninfo使用Python 3.8+开发,支持通过包管理器直接安装,也可以从源码构建。我在做渗透测试时更倾向于源码构建,因为可以在虚拟环境里跑,不污染系统Python环境。

# 方式一:包管理器安装 pip install seninfo # 方式二:源码构建 git clone https://example.com/seninfo.git cd seninfo python -m venv .venv source .venv/bin/activate pip install -r requirements.txt python setup.py install # 验证安装 seninfo --version

包管理器安装适合在CI/CD机器上快速部署,源码构建适合需要修改检测逻辑的场景。注意Python版本必须不低于3.8,低于这个版本会出现语法兼容问题。另外在ARM架构的机器上,有些依赖需要编译安装,编译前需要装好libffi-dev和gcc工具链。我在某台ARM服务器上遇到过cffi库编译失败的情况,换了cffi的预编译wheel之后才解决。

3.2 基础扫描:目录扫描与Git历史扫描

Seninfo的核心命令是scan,它支持扫描本地目录和Git仓库两种模式。目录扫描直接递归遍历目录下所有符合条件的文件;Git历史扫描则会遍历所有提交记录,检测在过去任何一次提交中是否出现过敏感信息。

# 扫描当前目录 seninfo scan --path . --format json --output result.json # 扫描指定Git仓库(含历史提交) seninfo scan --path ./myrepo --git-mode --since 2023-01-01 --format table # 排除指定目录 seninfo scan --path . --exclude "tests/fixtures" --exclude "docs/screenshots" --severity high

第一个命令是基础用法,--path指定扫描路径,--format json输出结构化结果,--output指定保存位置。第二个命令进入Git历史扫描模式,--since参数只检查指定日期之后的提交,这个参数在持续集成的增量扫描中非常实用。第三个命令的--exclude支持多次传递来排除多个路径,--severity high表示只看高严重级别的结果。我的习惯是首次全量扫描不加过滤参数,把结果存成JSON,然后二次过滤分析,而不是一开始就限制太严漏掉线索。

3.3 扫描结果解析与筛选

扫描完成后输出结果的质量决定了工具的实际价值。Seninfo的输出结果中每条记录包含文件路径、行号、规则ID、命中内容、严重级别和上下文代码片段。在渗透测试场景里,我更关心的是命中内容的类型分布和文件路径的敏感性。

# 统计不同严重级别的结果数量 seninfo scan --path . --format json --output result.json cat result.json | jq '.findings | group_by(.severity) | map({severity: .[0].severity, count: length})' # 只看命中私钥文件的结果 cat result.json | jq '.findings[] | select(.rule_id | contains("private_key")) | {file: .file_path, line: .line_number, snippet: .context}'

使用jq工具可以快速对JSON结果做二次筛选。第一条命令按严重级别分组统计,快速掌握整体情况;第二条命令过滤出私钥相关的告警,这在代码审计里通常优先级最高。手动复核时,我会把高严重级别的告警逐条打开源文件确认,中等风险级别的则先看上下文再批量处理。这里的经验是:不要盲目相信工具的输出标签,一定要看上下文代码是不是真的会把数据发出外网。

3.4 配置文件的优先级与加载顺序

Seninfo支持通过配置文件持久化参数,避免每次扫描都要敲一长串命令。工具启动时会按以下顺序加载配置:命令行参数优先级最高,其次是项目根目录下的.seninfo.yml配置文件,最后是用户目录下的~/.config/seninfo/config.yml默认配置。

# .seninfo.yml 示例 scan: path: "." git_mode: true since: 2024-01-01 exclude: - "tests/fixtures" - "docs" severity: medium output: format: json output: "scan_result.json" show_context: true rules: custom_rules: "custom_rules.yml" disable_default: false

配置文件的scan.path字段定义了默认扫描路径,output.format定义了输出格式,rules.disable_default可以完全禁用内置规则只使用自定义规则。我推荐在项目根目录维护一份.seninfo.yml,这样不同开发者执行seninfo scan时的行为一致,避免因为参数不同导致结果对不上。配置加载顺序的设计让团队可以通过用户级配置设定默认行为,再用项目级配置覆盖,命令行参数则在自动化流水线里按需传参。

4. 落地代码审计流程:CI集成、SARIF报告与手动复核闭环

4.1 在GitLab CI中集成增量扫描

代码审计不能只靠审计员手动执行工具,更有效的做法是把它嵌入到持续集成流水线中,让每次代码提交都自动触发扫描。GitLab CI的配置只需要在.gitlab-ci.yml中添加一个Job。

seninfo-scan: stage: security image: python:3.11-slim script: - pip install seninfo - seninfo scan --path . --git-mode --since $CI_COMMIT_BEFORE_SHA --format sarif --output seninfo.sarif artifacts: reports: sast: seninfo.sarif expire_in: 2 weeks only: - merge_requests

这个Job的关键参数是--since $CI_COMMIT_BEFORE_SHA,它让扫描器只检查当前提交相对上一个提交的新增或修改内容,大幅降低扫描耗时。format sarif产生SAST标准报告格式,GitLab可以把结果直接展示在合并请求的“安全”标签页里。only: merge_requests限定只在MR阶段触发,避免每次push都跑全量扫描拖慢开发节奏。在集成过程中我踩过的一个坑是:开发分支的历史提交里带着一堆敏感信息,增量扫描不会告警,因为--since限定了时间范围——这时候需要在MR被合并前由审计员手动执行一次全量扫描兜底。

4.2 SARIF输出与其他工具的联动

SARIF(Static Analysis Results Interchange Format)是静态分析工具的通用输出格式。Seninfo生成SARIF文件后,你可以导入到支持该格式的安全平台、VS Code扩展甚至是自定义告警机器人里。

# 生成SARIF格式报告 seninfo scan --path . --format sarif --output report.sarif # 用Python快速解析SARIF提取关键字段 python -c " import json with open('report.sarif') as f: data = json.load(f) for run in data['runs']: for result in run['results']: loc = result['locations'][0]['physicalLocation'] print(f\"{result['ruleId']} | {loc['artifactLocation']['uri']}:{loc['region']['startLine']} | {result['message']['text']}\") "

上面的Python脚本用标准库解析SARIF文件,提取规则ID、文件路径、行号和告警信息。这段脚本很适合接入企业内部的自定义告警系统——比如把脚本输出重定向到Webhook,扫描出高优先级密钥就马上推送到即时通讯群。SARIF格式的结构比JSON输出更规范化,有runs和results两层结构,方便多工具聚合展示。我在一个模拟项目X中同时对接了Seninfo和一个第三方依赖扫描工具,两者的SARIF结果能统一展示在同一份报告里。

4.3 与代码审计流程的配合:从告警到整改

工具扫描只是起点,真正产生价值的是告警处理流程。我把Seninfo的告警处理分成三类:确认的敏感信息泄露、疑似硬编码凭据、低风险噪音。

告警类型判定标准处理动作
确认泄露内容是真实的生成密钥,可在代码中找到使用点立即撤销密钥、清理Git历史、通知责任人
疑似硬编码格式匹配但不确定是否为真实凭据检查上下文代码、查询密钥管理系统确认
噪音测试数据、Mock值、文档示例加入白名单、单条忽略

确认泄露类型需要最快速度响应——先撤掉线上密钥的权限,再清理历史提交,最后复盘为什么会写进代码。疑似硬编码类型的告警通常占据扫描结果的大部分,逐个确认非常耗时。我的做法是把上下文代码片段导出来批量检查,明显是测试数据或示例值的就直接标记忽略,需要进一步确认的再单独开票据跟踪。

4.4 报告归档与趋势对比

每次扫描产生的报告如果只是看完就丢,就失去了持续改进的意义。我在团队里要求每次全量扫描的JSON报告按日期归档,每月做一次趋势对比。

# 归档扫描报告并重命名 mkdir -p reports/2024Q4 seninfo scan --path . --format json --output reports/2024Q4/seninfo_$(date +%Y%m%d).json # 对比两次告警数量 old_count=$(cat reports/2024Q4/seninfo_20241201.json | jq '.findings | length') new_count=$(cat reports/2024Q4/seninfo_20241215.json | jq '.findings | length') echo "旧报告告警数: $old_count, 新报告告警数: $new_count"

趋势对比的核心指标有两个:总告警数量和“确认泄露”类告警数量。如果新报告的告警总量变多,通常意味着代码库规模增长或者新增了规则;如果“确认泄露”类告警数量没有下降,说明开发团队的整改动作执行不到位。我把每个季度的趋势数据汇总成表格,在评审会上用数据推动整改落实。归档报告还有一个好处:后续排查历史泄露时,不需要重新扫描历史仓库,直接翻归档的JSON文件就行。

5. 避坑排查:误报高发场景与四类典型踩坑记录

5.1 误报的根源:正则、熵与语境的三种错位

误报是敏感信息扫描工具最头疼的问题。Seninfo的误报根源可以归为三类。正则规则过宽是最常见的原因,比如内置规则里匹配通用密码字段的表达式是password\s*[=:]\s*["'][^"']+["'],只要代码里出现password = "123456"这种赋值就会告警。熵检测的误报则集中在长变量名和Base64编码数据上——一个命名良好的Python变量名如果足够长且包含数字,熵值也可能突破阈值。第三类误报是格式匹配成功但内容根本不是密钥,比如匹配到的是某公司的示例代码里的占位符。

排查误报时,我习惯先看命中的规则ID,再看上下文代码。规则ID能告诉你哪类规则触发了告警,上下文代码则透露真实语境。一条通用密码字段的告警出现在ORM模型定义里,大概率是Django的password = CharField()字段声明,这种误报通过简单的变量名白名单就能过滤掉。

5.2 踩坑一:正则回溯导致的扫描卡死

现象:扫描一个包含数万行代码的JS仓库时,工具在某个文件上卡了十几分钟没有输出,CPU占用率100%。

原因:内置规则中有一条匹配JWT Token的正则写成eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,},当遇到超长且带下划线的Base64字符串时,正则引擎陷入灾难性回溯,匹配过程呈指数级增长。

解决:把正则改写成原子分组避免回溯,eyJ[A-Za-z0-9_-]{10,}(?>\.[A-Za-z0-9_-]{10,}){2}。另外在扫描前对超过500KB的文件做截断处理,只扫描前200KB内容——JWT正常都在几百个字符以内,截断不影响命中率。从那以后我每次新增规则都会用一个1万行的测试文件验证匹配耗时,超过5秒就直接优化正则。

5.3 踩坑二:Git历史扫描绕过--since边界

现象:使用--git-mode --since 2024-01-01扫描时,发现2024年之前的敏感信息依然出现在结果中。

原因:--since参数的作用是限定遍历的提交范围,但某开发者把旧文件重命名后在新日期提交了。Git记录中这次提交对应的是新时间,而文件内容里的敏感信息来自旧版本。

解决:Git历史扫描不能只依赖提交日期过滤。正确的做法是先执行全量历史扫描,把结果归档为基线;后续增量扫描直接对比基线,只关注新增告警。这样耗时最长的全量扫描只在首次执行,后续每次扫描速度会快很多。我现在的CI配置里已经去掉了--since参数,改为全量历史扫描归档基线加上MR阶段的增量扫描。

5.4 踩坑三:.env文件被.gitignore排除后漏扫

现象:某次审计中,开发环境的.env文件明明在仓库里,扫描却没有产生任何告警。

原因:Seninfo在文件过滤阶段默认跳过被.gitignore匹配的文件。.env恰好被开发环境的全局Git忽略规则排除了,工具认为它不是版本控制的一部分。

解决:在.seninfo.yml配置中加一个skip_gitignore: false参数,强制工具扫描被Git忽略的文件。敏感信息扫描的目标本身就包含那些“不应该存在却存在”的文件,所以我现在的默认配置是忽略.gitignore规则全量扫描,再配合exclude_paths手动排除依赖目录。这样不会漏掉遗漏在仓库角落里的凭据文件。

5.5 踩坑四:误把编译产物当源码扫描

现象:扫描Spring Boot项目时产生了大量base64字符串告警,核实后发现全部来自target/classes目录下的编译产物。

原因:编译后的class文件被打包成压缩格式,工具按照文本方式读取时把字节流误判成高随机性字符串,熵值检测自然命中。

解决:在文件过滤器里把target、dist、build等编译输出目录加入排除列表。另外设置skip_binary: true让工具检测文件是否包含空字节来判断是否为二进制文件,二进制文件直接跳过不做文本扫描。这个修复之后告警总量直接降了八成,剩下的全是真正需要人工关注的敏感信息。

6. 进阶:自定义规则与熵阈值调优的调试技巧

Seninfo真正的威力在于自定义检测逻辑。内置规则覆盖通用场景,但每个团队都有自己的技术栈和凭据管理习惯,自定义规则才能精准命中那些内部系统的Token格式。我在某公司的Java项目里就遇到过一种内部网关签发的Token,格式是GW-加32位Hex,内置规则完全没有覆盖,写一条规则之后扫描效率提升明显。

写自定义规则时,我一般按下面的模板起步。

# 自定义规则模板 - id: custom-gw-token pattern: "GW-[a-f0-9]{32}" description: "内部网关签发的访问Token" severity: critical tags: ["internal", "token"] entropy_min: 3.5 context_keywords: ["authorization", "token", "gateway"]

entropy_min字段为这条规则单独指定熵值门槛,命中正则但熵值低于此值时不算告警。context_keywords字段做了二次上下文验证——如果命中的字符串附近出现authorization、token这些关键词,判定为真实凭据的可能性更高。这个机制是减少误报的关键:只有正则和上下文双重命中才触发告警,避免了频繁的无效报告。

调优规则时,我会准备一个双语样本集:正样本是从实际代码库里确认真实泄露的密钥片段,负样本是从公开代码库里抽取的普通字符串。然后写一个快速验证脚本跑一遍扫描器拿准确率。

# 在测试样本集上验证规则效果 seninfo scan --path ./test_samples/positive --format json --output positive_result.json seninfo scan --path ./test_samples/negative --format json --output negative_result.json positive_count=$(cat positive_result.json | jq '.findings | length') negative_count=$(cat negative_result.json | jq '.findings | length') echo "正样本命中: $positive_count, 负样本误报: $negative_count"

一个合格的规则应该在正样本上全部命中,在负样本上保持零误报。如果负样本有误报,先加context_keywords收紧上下文;如果仍不行,调整entropy_min抬高随机性门槛。测试过程中遇到过entropy_min设太高导致真实密钥漏报的情况——短密钥本身的熵值就不高,设置阈值时必须结合规则匹配的数据类型。从那以后我每次上线新规则,都强制先跑一遍历史样本集,确认不破坏旧规则的检测效果才发布。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进