0%

OPENSSL拒绝服务漏洞【CVE-2022-0778】

@TOC
废话不多说直接去看官方github的commit记录

https://github.com/openssl/openssl/commit/9eafb53614bf65797db25f467946e735e1b43dc9#

大哥的分析

https://github.com/drago-96/CVE-2022-0778

在这里插入图片描述
整个文件对应了函数BN_mod_sqrt,该函数接受4个参数,按照顺序我们称为ret,a,p,ctx,函数最终的实现效果是利用Tonelli/Shanks算法计算ret,Tonelli/Shanks算法是用来结算二次剩余的一种算法,关于二次剩余解释为存在一个数X的平方与n mod p同余,则称n为模p的二次剩余。
这个洞太考验数学了,对我这种半路出家的渣渣来说后面的数学推导过程真的是不要太难受。
关键的修改发生在这一部分
在这里插入图片描述
这一部分代码被包裹在一个大的循环里面,这里贴出来完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

while (1) {
/*-
* Now b is a^q * y^k for some even k (0 <= k < 2^E
* where E refers to the original value of e, which we
* don't keep in a variable), and x is a^((q+1)/2) * y^(k/2).
*
* We have a*b = x^2,
* y^2^(e-1) = -1,
* b^2^(e-1) = 1.
*/
if (BN_is_one(b)) {
if (!BN_copy(ret, x))
goto end;
err = 0;
goto vrfy;
}

/* find smallest i such that b^(2^i) = 1 */
/***********修改前的代码***********/
i = 1;
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
/***********修改前的代码***********/
/* Find the smallest i, 0 < i < e, such that b^(2^i) = 1. */
/***********修改后的代码***********/
for (i = 1; i < e; i++) {
if (i == 1) {
if (!BN_mod_sqr(t, b, p, ctx))
goto end;

} else {
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
if (BN_is_one(t))
break;
}
/***********修改后的代码***********/
/* If not found, a is not a square or p is not prime. */
if (i >= e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}

/* t := y^2^(e - i - 1) */
if (!BN_copy(t, y))
goto end;
for (j = e - i - 1; j > 0; j--) {
if (!BN_mod_sqr(t, t, p, ctx))
goto end;
}
if (!BN_mod_mul(y, t, t, p, ctx))
goto end;
if (!BN_mod_mul(x, x, t, p, ctx))
goto end;
if (!BN_mod_mul(b, b, y, p, ctx))
goto end;
e = i;
}
vrfy:
if (!err) {
/*
* verify the result -- the input might have been not a square (test
* added in 0.9.8)
*/
if (!BN_mod_sqr(x, ret, p, ctx))
err = 1;
if (!err && 0 != BN_cmp(x, A)) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
err = 1;
}
}
end:
if (err) {
if (ret != in)
BN_clear_free(ret);
ret = NULL;
}
if (used_ctx)
BN_CTX_end(ctx);
bn_check_top(ret);
return ret;
}

我们直接来看修改前的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while (1){
.....
i = 1;
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
....
if (!BN_mod_mul(b, b, y, p, ctx))
goto end;
e = i;
....
}

在外层循环中首先会给i赋值为1,然后调用BN_mod_sqr函数,该函数的含义为接收一个素数p与数b计算t=b^2 % p 如果计算成功则返回1,计算失败则返回0。如果没有计算成功就goto end直接退出循环,也就不会有拒绝服务的情况发生,当然我们需要让他正常返回计算结果。这是第一次外层循环需要满足的条件,然后调用BN_is_one函数判断t是否为1,如果此时t等于1那么就不会进入内层循环,而是直接到外层循环的底部将e的值赋为i,此时的i为1,也就是e为1,第一次外层循环最重要的工作也就是这一步为e赋值为1。这个时候我们就要想想怎么进入内层循环并让i==e恒不成立就能使得程序一直在内层循环晃悠而不能退出,很明显需要满足BN_is_one函数的计算结果为False也就是t不等于1。进入第二次外层循环时当BN_mod_sqr计算结果使得t不为1时会进入内层循环,然后计算i++,此时i=2 != e 也就不会进入内部的if语句,然后进入下一次内层循环,此时i再次自增,以后无论循环多少次都不会再有i=1=e的情况发生于是程序就在内层卡死了。那么现在需要实现的逻辑就是在第一次外层循环时使得t等于1,第二时不等于1,这么看来是不可能实现的除非b与p的值在后面的代码中发生了变化,不然不可能存在对相同的操作数操作两次结果不一样。恰好上面代码中使用了BN_mod_mul函数改变了b的值,每一次外层循环使用该函数时改变b的值,为b=by mod p ,而y的值为 tt mod p。
事情到了这里一切都已经明朗了起来,当然怎么找到符合条件的p和b的值是个问题,这里面的推导我看了存在大量的数学算法,直接给我整蒙了,查了一些资料也没有搞的很明白。
然后我去试了一下这个poc,发现确实有效,首先你的在你的设备上安装openssl,然后查看一下版本是不是存在漏洞
在这里插入图片描述
不好意思,好像有漏洞
然后你下载好poc,然后使用gcc编译
gcc -o my_bad_sqrt my_bad_sqrt.c -lcrypt
编译的过程中可能报错
在这里插入图片描述centos执行
yum install openssl-devel安装依赖
ubuntu执行
apt-get install libssl-dev安装依赖
然后运行编译好的可执行文件
./my_bad_sqrt
然后依旧会看到终端一直卡起了
在这里插入图片描述
我们去看一下CPU的占用情况
在这里插入图片描述
效果直接拉满好吗
看了一下poc的写法
在这里插入图片描述
很简单,先申请空间,然后将十进制字符串a与p转换为大数,然后调用BN_mod_sqrt函数,想来这个函数就在openssl/bn.h这个头文件里头。

Buy me a coffee.

欢迎关注我的其它发布渠道