OpenID Connect部署中公私钥混淆的问题
我正在开发一个名为badkeys的工具,用于检查加密公钥中的已知漏洞。在德国OWASP日的一次关于badkeys的演讲问答环节中,有人问我是否曾用badkeys检查过OpenID Connect设置中的加密密钥。在此之前我从未这样做过。
OpenID Connect是一种单点登录协议,允许网页通过其他服务提供登录功能。每当你看到一个网页提供通过Google或Facebook账户登录时,背后的技术通常就是OpenID Connect。
像Google这样的OpenID提供商可以在预定义的URL位置发布JSON格式的配置文件,供与之交互的服务使用:https://example.com/.well-known/openid-configuration(Google的配置文件可以在这里找到)。
这些配置文件包含一个"jwks_uri"字段,指向包含用于验证认证令牌的加密公钥的JSON Web密钥集(JWKS)。JSON Web密钥是一种用JSON格式编码加密密钥的方式,而JSON Web密钥集是包含多个此类密钥的JSON结构(你可以在这里找到Google的)。
混淆公钥和私钥
JSON Web密钥有一个非常特殊的属性。加密公钥和私钥本质上只是一些大数字。对于大多数算法,公钥的所有数字都包含在私钥中。对于JSON Web密钥,这些数字使用url安全的Base64编码进行编码。
以下是一个ECDSA公钥的JSON Web密钥格式示例:
|
|
以下是相应的私钥:
|
|
你可能会注意到它们看起来非常相似。唯一的区别是私钥包含一个名为d的额外值,在ECDSA的情况下,这就是私钥值。对于RSA,私钥包含多个额外值(其中一个也称为"d"),但总体思路相同:私钥就是公钥加上一些额外值。
非常不寻常且我在其他任何上下文中都未见过的是,公钥和私钥的序列化格式是相同的。区分公钥和私钥的唯一方法是检查是否存在私有值。JSON通常以可扩展的方式解释,这意味着JSON字段中的任何额外字段如果对读取JSON文件的应用程序没有意义,通常会被忽略。
这两个事实结合起来导致了一个有趣且危险的特性。使用私钥而不是公钥通常可以工作,因为JSON Web密钥格式中的每个私钥也是一个有效的公钥。
你现在可以猜到这是怎么回事了。我检查了从OpenID配置收集的公钥中是否有实际上是私钥的情况。
实际发现
有9个主机存在这种情况。这些主机名属于一些知名公司,包括stackoverflowteams.com、stack.uberinternal.com和ask.fisglobal.com。这三个似乎都使用了Stackoverflow提供的服务,并且此后已修复。
短RSA密钥
7个主机配置了512位长度的RSA密钥。这种密钥早已被知道是可破解的,如今用相对较少的努力就可以做到。45个主机有1024位长度的RSA密钥,这被认为可以被强大的攻击者破解,尽管这种攻击尚未公开演示。
第一个成功的512位RSA公开攻击是在1999年进行的。当时需要在超级计算机上运行数月。如今,破解此类密钥几乎对所有人都是可行的。
在生产环境中使用示例密钥
在发现的密钥上运行badkeys揭示了另一种类型的漏洞。在运行扫描之前,我确保badkeys能够检测常见开源OpenID Connect实现中的示例私钥。我发现了18个受影响的主机,它们的密钥是这种"公共私钥",即相应私钥是现有公开可用软件包的一部分。
影响
总体而言,我发现了33个易受攻击的主机。在总共13,000个检测到的OpenID配置中,0.25%存在可能允许攻击者访问私钥的漏洞。
这种私钥破解的严重程度取决于具体情况。OpenID Connect支持OpenID提供商和消费者之间交换认证令牌的不同方式。令牌可以通过浏览器交换,在这种情况下最为严重,因为它简单地允许攻击者为任何身份签署任意登录令牌。
如何改进
我认为其中两个问题本可以通过更好的规范完全避免。
如前所述,JSON Web密钥对公钥和私钥使用相同的序列化格式是一个奇怪且不寻常的事实。这是一个设计决策,使得混淆公钥和私钥成为可能。
在现有规范中可以实施的一个缓解措施是让OpenID Connect实现检查JSON Web密钥集是否包含任何私钥。对于所有当前支持的值,这可以通过检查是否存在"d"值来轻松完成。
当前的OpenID Connect Discovery规范说:“JWK集不得包含私有或对称密钥值。“因此,检查它仅仅意味着实现正在强制执行现有规范的合规性。我建议在未来的标准版本中添加对此类检查的要求。
建议
对于短RSA密钥,令人惊讶的是这些协议中甚至允许512位密钥。OpenID Connect及其所基于的一切都相对较新。JSON Web密钥标准的第一个草案版本可以追溯到2012年——第一次成功攻击RSA-512的十三年后。
防止使用已知的示例密钥——我喜欢称它们为公共私钥——更难避免。如果人们同意使用标准化的测试密钥集,我们可以减少这些问题。RFC 9500包含一些用于此确切用例的测试密钥。