技术分享:如何在没有公钥的情况下实现JWT密钥滥用

 

概述

这个故事要从我们之前的一个老项目开始说起,当时我们的任务是验证业务应用程序如何处理事务的数字签名,以判断应用是否遵守相关的安全原则。

这个应用程序使用了RSA签名,当时我们在常用OpenSSL API设置了断点,但并未被触发,研究半天之后,我们意识到开发人员实现了安全领域的人最喜欢称之为“Textbook RSA”的东西。这当然导致了报告中的红色标记和开发的大规模延迟,但也给我们带来了一些不寻常的问题需要解决。

其中一个问题源于这样一个事实:尽管我们可以对方案提出多个理论上的攻击,但在这个应用程序中使用的公钥并没有在任何地方发布,没有它,我们就没有实际攻击的起点。

需要注意的是,虽然公钥密码系统保证私钥不能从公钥、签名、密文等中派生,但通常没有这样的公钥保证!事实上,熟悉加密堆栈交换的技术人员提出了一个非常简单的解决方案:只需找到所有可用消息签名对之差的最大公约数(GCD)即可。这里有几点值得注意:

1、RSA公钥是(n,e)整数对,其中n是模,e是公共指数。由于e通常是一些硬编码的小数字,我们只对求n感兴趣。

2、尽管RSA涉及大量的数字,但自古以来就有非常有效的算法来寻找数字的GCD(我们不必进行暴力因子分解)。

3、虽然这种方案有一定的概率性,但在实践中我们通常可以尝试所有可能的答案。此外,我们的机会随着已知消息签名对的数量而增加。

在我们的例子中,只要两个签名就可以恢复公钥。此时,我们有了一个基于gmpy2库的快速实现,它允许我们使用大型整数和Python中的现代高效算法。

如果你是一个正在考虑/使用JWT(或任何东西)的开发人员,请至少花点时间阅读这篇文章!这里还有一些替代方案。

理论上,当使用RSA私钥对JWT签名时,攻击者可能会将签名算法更改为HMAC-SHA256。在验证过程中,JWT实现可以看到这个算法,但是使用配置的RSA公钥进行验证。问题是对称验证过程假设使用相同的公钥生成MAC,因此如果攻击者拥有RSA公钥,那么他也可以伪造签名。

但实际上,公钥很少可用(至少在黑盒设置中是这样)。但正如我们前面看到的,我们也许可以用一些代数来解决这个问题。问题是:是否有任何实际因素可以阻止这种利用?

 

CVE-2017-11424分析与利用

为了证明此方法的可行性,我们将针对PyJWT版本1.5.0中的一个漏洞进行分析,该漏洞允许密钥混淆攻击,如前一节所述。该库使用黑名单来避免密钥参数“看起来”像对称方法中的非对称密钥,但在受影响的版本中,它忽略了“BEGIN RSA PUBLIC key”头,这将允许PKCS#1格式的PEM编码公钥被滥用。

根据技术文档描述,RSA密钥将作为PEM编码字节数组提供给编码/解码API(也进行签名和验证)。为了使我们的攻击发挥作用,我们需要基于消息和签名对创建此数组的完美副本。让我们从影响签名值的因素开始介绍:

1、字节顺序:JKS整数表示的字节顺序与gmpy2匹配。

2、消息规范化:根据JWT标准,RSA签名是在令牌的Base64URL编码部分的SHA-256散列上计算的,不需要对分隔符、空格或特殊字符进行规范化。

3、消息填充:JKS规定了确定性PKCS#1v1.5填充。使用适当的低级加密API将为我们提供符合标准的输出,而不会与ASN.1发生冲突。

这些问题都不大,因为我们可以通过对原始代码进行一些修改,就能够以成功地重新创建JWT令牌的Base64URL编码的签名表示。

1、字段顺序:理论上我们可以提供任意顺序的e和n。幸运的是,PKCS#1在ASN.1结构中定义了严格的参数顺序。

2、序列化:ASN.1结构的DER(和PEM)编码是确定的。

3、附加数据:PKCS#1没有为公钥定义附加(可选)数据成员。

4、布局:虽然从技术上讲,在不使用标准换行符的情况下解析PEM数据是可行的,但文件通常是用64个字符的换行符生成的。

如我们所见,PKCS#1和PEM几乎不允许更改,因此,如果生成符合标准的PEM文件,则很有可能与目标文件匹配。在其他输入格式(如JWK)的情况下,灵活性可能会导致同一密钥的大量可能编码,从而阻止攻击。

由于pyasn1和asn1包的缺陷和文档不足,经过大量的研究之后,asn1tools最终被证明可以用来创建定制的DER(以及PEM)结构。

生成的输出与原始公钥完全匹配,因此我可以成功演示令牌伪造,而无需有关非对称密钥的初步信息:

漏洞利用演示:https://asciinema.org/a/389722

我们使用JKS标准的2048位密钥进行了测试:在笔记本电脑上对两个签名运行GCD算法只花了不到一分钟的时间,该算法为PKCS#1生成了两个很容易测试的候选密钥。

像往常一样,所有代码都可以在【GitHub】上获得。如果您需要帮助将此技术集成到Super Duper JWT Haxor工具中,请使用Issue tracker!

 

总结

人们不应该依赖公钥的保密性,因为这些参数不受数学陷门的保护。

本文展示了进攻性安全的工程方面,理论和实践可能相去甚远:虽然这里的主要数学技巧似乎不直观,但实际上很容易理解和实现。使开发变得困难的是找出所有这些实现细节,使纸笔公式在实际计算机上工作。有趣的是,对于任何使用数字证书和密钥的人来说,这里至少有2/3的工作是关于读取标准、使ASN.1工作等等(更不用说在Python3:P中不断转换字节数组和字符串了),似乎这些标准的僵化使得所需密钥的格式更可预测,开发也更可靠!

 

参考资料

1、http://cs.wellesley.edu/~cs310/lectures/26_rsa_slides_handouts.pdf
2、https://crypto.stackexchange.com/a/30301/7826
3、https://crypto.stackexchange.com/a/33644/7826
4、https://en.wikipedia.org/wiki/Euclidean_algorithm
5、https://pypi.org/project/gmpy2/
6、https://www.howmanydayssinceajwtalgnonevuln.com/
7、https://pyjwt.readthedocs.io/en/stable/usage.html
8、https://nvd.nist.gov/vuln/detail/CVE-2017-11424
9、https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail
10、https://tools.ietf.org/html/rfc3447#appendix-A.1.1
11、http://ratmirkarabut.com/articles/ctf-writeup-google-ctf-quals-2017-rsa-ctf-challenge/
12、https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
13、https://asn1tools.readthedocs.io/en/latest/
14、https://github.com/silentsignal/rsa_sign2n

(完)