基于信息熵与词汇占比的JS硬编码检测技术教学文档
1. 概述
本教学文档详细介绍一种针对JavaScript代码中硬编码密钥的检测方案。该方案通过量化分析字符串的统计特征,实现高精度、低误报的自动识别,旨在替代传统基于正则表达式堆砌的检测方法,提升安全扫描的效率和准确性。
2. 问题定义与技术约束
2.1 检测目标与工程约束
检测目标是从海量前端代码中精准定位硬编码的密钥、Token等敏感信息。这是一个二分类任务:输入代码中的字符串,输出判断是否为敏感信息。
在实际工程落地时,算法必须同时满足以下四项硬性指标:
- 高召回率:安全扫描宁可多报,不可漏报。真实密钥的检出率需稳定在80%以上。
- 可控的误报率:告警必须可处理、可复核。误报比例需控制在50%以内,避免安全团队被无效噪音淹没。
- 轻量无外部依赖:不依赖网络请求、大模型API或重型运行时,保证离线可用、部署简单、推理成本趋近于零。
2.2 硬编码密钥的统计特征抽象
通过分析硬编码密钥的特性,定义了以下三个维度的统计特征:
-
高信息熵(随机性强)
- 密钥通常由随机生成器或密码学算法输出
- 字符分布均匀,缺乏人类可读的拼写规律
-
低自然语言占比
- 与
userProfile、fetchData等代码标识符不同,密钥极少由完整的英文单词拼接而成 - 使用分词工具处理后,命中通用词典的比例通常低于30%
- 与
-
典型长度区间
- 受限于加密标准与传输协议,主流密钥长度通常集中在8~64个字符之间
- 过短多为弱口令或测试占位符,过长则多为Base64编码块或证书片段
基于上述规律,将检测问题转化为"基于可计算统计特征的模式识别",通过相互独立的指标构建评分模型,让算法通过"数据分布差异"自动区分密钥与普通代码字符串。
3. 核心算法设计
3.1 多维度计算
3.1.1 熵值特征\(E(s)\):随机性度量
采用归一化香农熵消除长度偏差:
\[E(s) = \frac{H(s)}{\log_2(\min(|s|, |\Sigma|))}, \quad H(s) = -\sum_{c \in \Sigma} p_c \log_2 p_c \]
其中:
- \(p_c\)为字符\(c\)的出现频率
- \(\Sigma\)为字符集
- \(H(s)\)为字符串\(s\)的香农熵
长度校准机制:
短字符串因采样不足会导致熵值虚高,影响检测准确性。引入Sigmoid衰减因子进行校准:
\[E_{\text{cal}}(s) = E(s) \cdot \sigma(L; L_0, k), \quad \sigma(L) = \frac{1}{1 + e^{-k(L - L_0)}} \]
参数设定:
- \(L_0 = 12\)(校准中心)
- \(k = 0.3\)(衰减速率)
当字符串长度\(l=7\)时,\(\sigma \approx 0.4\);当\(l=20\)时,\(\sigma \approx 0.95\)。该机制实现短串压制、长串保留的效果。
3.1.2 语义占比特征\(P(s)\):自然语言倾向
通过分词与词典匹配量化字符串的"人类语言"程度:
\[P(s) = \frac{\sum_{w \in W_{\text{valid}} \cap D} |w|}{\sum_{w \in W_{\text{valid}}} |w|} \]
其中:
- \(W_{\text{valid}}\):分词后长度>3的有效词集合
- \(D\):NLTK通用词典∪技术词表(包含api, token, config等技术词汇)
双路分词融合策略:
为提升技术复合词的识别率,采用以下融合策略:
- 驼峰切分:将
Uint8Array切分为['uint', '8', 'array'] - 使用wordninja进行常规分词
- 取两种分词结果的并集去重
# 驼峰切分函数示例
def _camel_split(s):
return re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)', s)
# 融合策略
all_words = set(wordninja_split(s) + _camel_split(s))
注:当前实现依赖wordninja分词器和NLTK词典,对部分单词可能不敏感,需要手动补充
TECH_WORDS技术词汇表。
3.2 评分模型:线性加权 + Sigmoid映射
3.2.1 基础分计算
采用线性加权模型计算基础得分:
\[\text{Base}(s) = w_e \cdot E_{\text{final}}(s) - w_p \cdot P(s) \]
权重设定:
- \(w_e = 0.95\)(正向权重,鼓励高熵值)
- \(w_p = 1.2\)(负向权重,惩罚高自然语言占比)
权重设计原理:
负向权重略高于正向权重,可对边界模糊样本优先判定为正常代码。在保障核心密钥检出能力的同时严格控制误报。若需进一步降低漏报风险,可适当提高正向权重、降低负向权重,依托后续大模型验证环节兜底,在提升召回率的同时避免告警噪音泛滥。
3.2.2 概率映射与阈值决策
通过Sigmoid函数将基础分映射为概率值:
\[\text{Score}(s) = \frac{1}{1 + e^{-\beta(\text{Base}(s) - \mu)}} \]
参数设定:
- \(\beta = 3.5\)(斜率参数)
- \(\mu = 0.45\)(中心点参数)
动态阈值策略:
为平衡长短字符串的差异,采用动态阈值:
\[\theta(s) = \theta_0 \cdot \begin{cases} 0.9 & |s| < 16 \\ 1.1 & |s| > 100 \\ 1.0 & \text{otherwise} \end{cases} \]
其中\(\theta_0 = 0.72\)为基准阈值。
判定规则:\(f(s) = I(\text{Score}(s) \geq \theta(s))\),其中\(I(\cdot)\)为指示函数。
注意:上述所有数值参数来源于本地数据实验,非全局最优解,实际部署时应根据具体场景调整。
3.3 工程兜底:确定性规则过滤
3.3.1 路径/URL拦截
设计依据:真实密钥不会以./、../或/开头,该规则误杀率为0。
实现方式:在统计特征计算前,先检查字符串是否以这些模式开头,若是则直接判定为正常代码字符串。
3.3.2 敏感关键词提权
在统计特征基础上引入轻量语义信号,提升边界样本召回率。当字符串中包含特定敏感关键词(如password、secret、key等)时,适当提高其得分,避免因长度过短等因素导致的漏报。
4. 工程实现与性能优化
4.1 架构设计
(文档中未详细描述架构设计部分,但基于算法描述可推断以下关键点)
- 模块化设计:将特征计算、评分模型、规则过滤等模块解耦
- 批量处理:支持对大量字符串的并行或批量处理
- 缓存优化:对词典加载、分词结果等进行缓存,避免重复计算
- 资源控制:限制单次处理的内存和CPU使用,避免影响宿主应用
5. 效果评估
5.1 测试集A
测试集A聚焦典型前端场景,样本总量1397条,经人工标注与上下文复核,确认真实硬编码密钥(正样本)为19条。
实验设置:
- 模型以阈值0.72进行判定
- 输出21条阳性结果(注:阳性仅代表风险评分越界,属"疑似硬编码",需结合AST上下文或LLM进行二次裁决)
检测结果:
- 有效拦截17条真实密钥
- TP(真正例)= 17
- FP(假正例)= 4
- FN(假反例)= 2
高分检出样本分布(前22条,脱敏处理):
| 序号 | 脱敏后特征名称 | 长度 | 特征得分 |
|---|---|---|---|
| 1 | 3086d**********e49284eb15 | 32 | 0.7872 |
| 2 | 17ae96a***************************719501ee6 | 40 | 0.7828 |
| ... | ... | ... | ... |
| 22 | evp_*********ey | 14 | 0.6790 |
注:高分样本呈现明显的分数断层与聚类特征,表明算法能有效区分异常字符串。
核心性能指标:
- 准确率 (Accuracy):99.86%
- 精确率 (Precision):80.95%
- 召回率 (Recall):89.47%
- F1-Score:85.00%
5.2 测试集B
测试集B样本总量1937条,涵盖UUID、GUID、URN、Hex哈希、加密密钥等多种结构化标识符。经标注确认,真实正样本9条。
检测结果:
- 模型检出阳性10条(含2条误报)
- 漏报1条
- TP = 8,FP = 2,FN = 1
核心性能指标:
- 准确率 (Accuracy):99.84%
- 精确率 (Precision):80.00%
- 召回率 (Recall):88.89%
- F1-Score:84.21%
5.3 边界样本分析
5.3.1 高分误报(需人工复核)
特征分析:含技术词但长度适中,\(P \approx 0.5\),\(E\)经校准后仍较高。
这类样本在统计特征上与真实密钥相似,但实际可能是代码中的特殊标识符或混淆后的变量名。需要通过人工复核或结合上下文分析进行最终判断。
5.3.2 低分漏报(需阈值调优)
特征分析:长度\(L=6\)触发长度衰减,\(E\)从0.92衰减至0.25。
处理建议:
- 敏感关键词提权机制已覆盖此类样本(如
password相关) - 可单独设置短串阈值下限,避免因过度衰减导致的漏报
- 针对特定长度的字符串调整Sigmoid衰减参数
6. 扩展方向与最佳实践
6.1 多语言识别
当前技术方案主要针对JavaScript,但算法原理是通用的,可在Python、Java等多种语言中进行测试和适配。不同语言可能需要调整以下方面:
- 技术词汇表:补充目标语言特有的技术术语
- 分词策略:适配目标语言的命名约定(如Python的下划线命名、Java的驼峰命名)
- 字符集处理:考虑不同语言的字符编码特性
7. 实战局限性与设计边界
7.1 “疑似”不等于“可利用”
本算法的核心目标是异常检出而非有效性验证。
- 统计学异常:算法能精准识别出长相异于普通代码的"随机串"
- 逻辑学缺失:仅靠静态特征,无法感知该字符串的生命周期(如已过期的Token、测试环境密钥)或业务含义(如前端生成的临时Request ID)
- 结论:绝大部分检出的高分样本可能并无实际利用价值。本方案定位为"高效过滤器",旨在将人工或大模型从万级噪音中解放出来,将审查范围压缩至"可触达"的量级
7.2 无法规避的“随机性”噪音
由于密钥在数学特征上与某些非敏感随机串高度重合,以下两类样本在纯数学层面是"不可分"的:
- 结构化标识符:UUID、GUID、部分高熵的哈希值(如SHA-256摘要)
- 混淆干扰项:经过重度混淆的JS变量名或属性名,其熵值与密钥几乎一致
这些字符串虽然没有实际安全意义,但符合"高熵、低语义"的特征,会被算法判定为阳性。
7.3 检测能力的权衡
-
以空间换效率:本方案选择了轻量化的数学模型,放弃了极耗资源的深度语义流分析(Taint Analysis)。这意味着无法通过数据流来判定一个字符串是否进入了敏感汇聚点(Sink)
-
召回率优先:为了防止错过那一颗"真针",接受了少量的"石子"进入终选名单。实战数据证明,将20,000条样本压缩至20条,即便其中18条不可用,剩余2条的获取成本也已实现了数量级的下降
8. 附录:核心代码索引
完整实现代码可参考以下资源:
- 代码仓库:
JScanner2/processor/analysis/secret/secret_scanner.py at master · hmx222/JScanner2
注意:当前实现依赖wordninja和NLTK库,部署前需确保相关依赖已正确安装。实际使用时应根据具体业务场景调整参数阈值和词典内容。