2. OCSP请求结构:拆开看看里面到底有什么
要做溯源分析,首先得知道OCSP请求长什么样。就像修车你得先看懂发动机舱,对吧?我刚开始接触OCSP协议时,对着抓包文件一头雾水,后来花了一整天硬啃RFC 6960,才把每个字段摸清楚。今天我把这些经验提炼出来,咱们一步步拆解。
2.1 OCSP请求的整体格式
OCSP请求本身是个ASN.1结构,用DER编码。说白了就是套了好几层的盒子。最外层是OCSPRequest,里面包含tbsRequest和可选的signature。实际溯源中,我们关注的核心都在tbsRequest里。
关键点: 大多数攻击请求是不带签名的,因为签名会增加计算开销。如果你看到签名,反而要留意——可能在做身份伪装。
2.2 核心字段逐个解析
下表列出了我在溯源时必看的几个字段,以及它们的溯源价值。
| 字段 | 类型 | 溯源关注点 |
|---|---|---|
| serialNumber | INTEGER | 证书唯一编号,用于定位目标证书 |
| certID | CertID结构 | 包含哈希算法、颁发者信息、公钥哈希等 |
| issuerNameHash | OCTET STRING | 颁发者DN的哈希值,可关联CA |
| issuerKeyHash | OCTET STRING | 颁发者公钥的哈希值,比nameHash更稳定 |
| requestExtensions | Extensions | 经常藏匿自定义OID,用于传输额外恶意信息 |
嗯,这里要特别注意certID。它其实是个复合结构,里面包含了hashAlgorithm、issuerNameHash、issuerKeyHash和serialNumber。我的习惯是:先提取serialNumber和issuerKeyHash,这两者组合基本能唯一确定一张证书。
2.3 代码示例:用Python提取关键字段
假设你抓到一个OCSP请求的DER字节流,怎么解析?我通常用asn1crypto库,简单直接。
from asn1crypto import ocsp
# raw_der 是从pcap中提取出的OCSP请求字节
req = ocsp.OCSPRequest.load(raw_der)
tbs = req['tbs_request']
for single_req in tbs['request_list']:
cert_id = single_req['req_cert']['cert_id']
serial = cert_id['serial_number'].native
issuer_key_hash = cert_id['issuer_key_hash'].native.hex()
print(f"serialNumber: {serial}")
print(f"issuerKeyHash: {issuer_key_hash}")
# 检查扩展
exts = single_req['req_cert'].get('single_request_extensions')
if exts:
for ext in exts:
oid = ext['extn_id'].dotted
print(f"扩展OID: {oid}")
小技巧: 有些恶意OCSP请求会把攻击载荷伪装成扩展OID的值,比如在nonce扩展里塞base64编码的命令。我曾在一次溯源中发现某个C2服务器用1.3.6.1.5.5.7.48.1.2(nonce OID)的值传递心跳指令。
2.4 高频请求的“指纹”特征
为什么说OCSP请求结构能帮我们溯源?因为攻击者写死一个定时任务去查OCSP时,往往会把所有字段写得一模一样。你想想看,正常浏览器每次查询的serialNumber都不同,但攻击脚本可能只查一张固定的测试证书。这就形成了指纹。
我归纳了几个常见的攻击特征:
- serialNumber固定: 每次请求都查同一个序列号,大概率是恶意脚本
- issuerKeyHash固定但nameHash变化: 可能是伪造了DN,想绕过简单哈希过滤
- 扩展字段异常长: 超过500字节的扩展就值得深挖
- 多请求中certID完全一致: 可疑,除非是重放攻击
2.5 一张图看懂OCSP请求结构
下面我画了张结构图,把各层级关系和溯源关键点标了出来。你重点关注certID和requestExtensions这两个分支。
2.6 溯源实战:从请求结构看出门道
我记得有一次在应急响应中,客户说他们的CA服务被疯狂请求。我抓了一个包,发现serialNumber全是同一个值——0x1234567890ABCDEF。正常OCSP查询怎么可能全部查同一张证书?明显是个定时任务在反复查询。顺着这个issuerKeyHash,我关联出那台机器上驻留的恶意程序,它每30秒查一次CA,确认证书是否有效,其实是在做心跳保活。
避坑指南: 我曾经以为issuerNameHash比issuerKeyHash更稳定,结果翻车了。有的CA会换DN但同一对密钥,nameHash变了但keyHash不变。所以我建议你优先用issuerKeyHash做关联,别走弯路。
另外,如果你看到请求中有OCSPNonce扩展,别放松警惕。有些攻击者会在nonce里编码C2指令。比如把nonce写成0x636D64:65786563(对应ASCII "cmd:exec"),然后服务器解析这些字节执行。我自己就遇到过这种“巧妙”的做法,当时如果只盯着serialNumber,就漏掉了。
2.7 如何批量提取OCSP请求中的certID
用手工一个个看肯定不行。我一般会用tcpdump + Python配合。这里给个简化版的批量提取脚本思路:
- 用tcpdump抓取OCSP端口(通常80或2560)的POST请求载荷
- 用Python循环解析每个DER包
- 把certID中的serialNumber和issuerKeyHash输出成CSV
- 按issuerKeyHash分组,统计每个证书被查询的次数和频率
# 伪代码示意
for der_packet in ocsp_stream:
req = ocsp.OCSPRequest.load(der_packet)
cert_id = req['tbs_request']['request_list'][0]['req_cert']['cert_id']
sn = cert_id['serial_number'].native
kh = cert_id['issuer_key_hash'].native.hex()
print(f"{sn},{kh}")
你想想看,只要把这份CSV导入Excel,按issuerKeyHash排序,很快就能发现那些被高频查询的“明星”证书。溯源入口就这么打开了。
一点心得: 不必追求完美解析所有字段。我个人经验是,抓住serialNumber、issuerKeyHash和可能的扩展OID,80%的溯源任务就够用了。剩下的20%遇到特殊情况再细看。