告别正则堆砌:一种基于信息熵与词汇占比的 JS 硬编码高精度发现方案
字数 4829
更新时间 2026-04-22 13:59:38

基于信息熵与词汇占比的JS硬编码检测技术教学文档

1. 概述

本教学文档详细介绍一种针对JavaScript代码中硬编码密钥的检测方案。该方案通过量化分析字符串的统计特征,实现高精度、低误报的自动识别,旨在替代传统基于正则表达式堆砌的检测方法,提升安全扫描的效率和准确性。

2. 问题定义与技术约束

2.1 检测目标与工程约束

检测目标是从海量前端代码中精准定位硬编码的密钥、Token等敏感信息。这是一个二分类任务:输入代码中的字符串,输出判断是否为敏感信息。

在实际工程落地时,算法必须同时满足以下四项硬性指标:

  • 高召回率:安全扫描宁可多报,不可漏报。真实密钥的检出率需稳定在80%以上。
  • 可控的误报率:告警必须可处理、可复核。误报比例需控制在50%以内,避免安全团队被无效噪音淹没。
  • 轻量无外部依赖:不依赖网络请求、大模型API或重型运行时,保证离线可用、部署简单、推理成本趋近于零。

2.2 硬编码密钥的统计特征抽象

通过分析硬编码密钥的特性,定义了以下三个维度的统计特征:

  1. 高信息熵(随机性强)

    • 密钥通常由随机生成器或密码学算法输出
    • 字符分布均匀,缺乏人类可读的拼写规律
  2. 低自然语言占比

    • userProfilefetchData等代码标识符不同,密钥极少由完整的英文单词拼接而成
    • 使用分词工具处理后,命中通用词典的比例通常低于30%
  3. 典型长度区间

    • 受限于加密标准与传输协议,主流密钥长度通常集中在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等技术词汇)

双路分词融合策略
为提升技术复合词的识别率,采用以下融合策略:

  1. 驼峰切分:将Uint8Array切分为['uint', '8', 'array']
  2. 使用wordninja进行常规分词
  3. 取两种分词结果的并集去重
# 驼峰切分函数示例
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 敏感关键词提权

在统计特征基础上引入轻量语义信号,提升边界样本召回率。当字符串中包含特定敏感关键词(如passwordsecretkey等)时,适当提高其得分,避免因长度过短等因素导致的漏报。

4. 工程实现与性能优化

4.1 架构设计

(文档中未详细描述架构设计部分,但基于算法描述可推断以下关键点)

  1. 模块化设计:将特征计算、评分模型、规则过滤等模块解耦
  2. 批量处理:支持对大量字符串的并行或批量处理
  3. 缓存优化:对词典加载、分词结果等进行缓存,避免重复计算
  4. 资源控制:限制单次处理的内存和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。

处理建议

  1. 敏感关键词提权机制已覆盖此类样本(如password相关)
  2. 可单独设置短串阈值下限,避免因过度衰减导致的漏报
  3. 针对特定长度的字符串调整Sigmoid衰减参数

6. 扩展方向与最佳实践

6.1 多语言识别

当前技术方案主要针对JavaScript,但算法原理是通用的,可在Python、Java等多种语言中进行测试和适配。不同语言可能需要调整以下方面:

  1. 技术词汇表:补充目标语言特有的技术术语
  2. 分词策略:适配目标语言的命名约定(如Python的下划线命名、Java的驼峰命名)
  3. 字符集处理:考虑不同语言的字符编码特性

7. 实战局限性与设计边界

7.1 “疑似”不等于“可利用”

本算法的核心目标是异常检出而非有效性验证。

  1. 统计学异常:算法能精准识别出长相异于普通代码的"随机串"
  2. 逻辑学缺失:仅靠静态特征,无法感知该字符串的生命周期(如已过期的Token、测试环境密钥)或业务含义(如前端生成的临时Request ID)
  3. 结论:绝大部分检出的高分样本可能并无实际利用价值。本方案定位为"高效过滤器",旨在将人工或大模型从万级噪音中解放出来,将审查范围压缩至"可触达"的量级

7.2 无法规避的“随机性”噪音

由于密钥在数学特征上与某些非敏感随机串高度重合,以下两类样本在纯数学层面是"不可分"的:

  1. 结构化标识符:UUID、GUID、部分高熵的哈希值(如SHA-256摘要)
  2. 混淆干扰项:经过重度混淆的JS变量名或属性名,其熵值与密钥几乎一致

这些字符串虽然没有实际安全意义,但符合"高熵、低语义"的特征,会被算法判定为阳性。

7.3 检测能力的权衡

  1. 以空间换效率:本方案选择了轻量化的数学模型,放弃了极耗资源的深度语义流分析(Taint Analysis)。这意味着无法通过数据流来判定一个字符串是否进入了敏感汇聚点(Sink)

  2. 召回率优先:为了防止错过那一颗"真针",接受了少量的"石子"进入终选名单。实战数据证明,将20,000条样本压缩至20条,即便其中18条不可用,剩余2条的获取成本也已实现了数量级的下降

8. 附录:核心代码索引

完整实现代码可参考以下资源:

  • 代码仓库:JScanner2/processor/analysis/secret/secret_scanner.py at master · hmx222/JScanner2

注意:当前实现依赖wordninja和NLTK库,部署前需确保相关依赖已正确安装。实际使用时应根据具体业务场景调整参数阈值和词典内容。

相似文章
相似文章
 全屏