-
-
[原创]OpenSSL漏洞分析(CVE-2021-3450)
-
发表于: 2021-4-26 10:21 8832
-
前段时间OpenSSL项目组发布了一个CA证书绕过的漏洞(CVE-2021-3450),主要就是X509_V_FLAG_X509_STRICT标志可对证书链中存在的证书进行其它安全检查,默认情况下未设置。从OpenSSL版本1.1.1h开始,添加了一项检查以禁止在链中显式编码椭圆曲线参数的证书,这是附加的严格检查。执行此检查时出现一个错误,这意味着先前检查的结果会被覆盖,该检查用于确认链中的证书是有效的CA证书。
漏洞位置在 check_chain_extensions 这个函数里面,大概就是在进行X509_V_FLAG_X509_STRICT 标志验证的时候,没有判断check_ca的结果,就进行X509_V_FLAG_X509_STRICT 标志的验证,这样会把之前check_ca的验证结果给覆盖掉,从而绕过ca检查。
通过官方的介绍,此漏洞触发需要显式设置 X509_V_FLAG_X509_STRICT 验证标志,那么该怎么设置这个标志呢???
通过对补丁文件的分析及源码的分析,发现需要在初始化X509_store的时候显式设置才行
这里只需要调用 X509_STORE_set_flags 函数设置一下X509_V_FLAG_X509_STRICT 标志即可
···
···
···
ret
=
X509_check_ca(x);
/
/
这里返回ca检查的结果
switch (must_be_ca) {
case
-
1
:
if
((ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)
&& (ret !
=
1
) && (ret !
=
0
)) {
/
/
当ret不等于
0
和
1
的时候,会把ret置为
0
,及证书有问题
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_CA;
}
else
ret
=
1
;
break
;
case
0
:
if
(ret !
=
0
) {
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_NON_CA;
}
else
ret
=
1
;
break
;
default:
/
*
X509_V_FLAG_X509_STRICT
is
implicit
for
intermediate CAs
*
/
if
((ret
=
=
0
)
|| ((i
+
1
< num || ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)
&& (ret !
=
1
))) {
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_CA;
}
else
ret
=
1
;
break
;
}
/
/
这里是判断是否验证 X509_V_FLAG_X509_STRICT 这个标志·
/
/
这里应该就是漏洞的主要原因,没有判断上面check_ca的返回值直接进行 X509_V_FLAG_X509_STRICT 的验证
/
/
如果check_ca的返回结果异常,在继续进行其他验证,会把异常结果覆盖,从而绕过ca检查
/
/
所以补丁文件在这里加上了ret>
0
的时候才进行 X509_V_FLAG_X509_STRICT 的验证
if
((ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT) && num >
1
) {
/
*
Check
for
presence of explicit elliptic curve parameters
*
/
ret
=
check_curve(x);
/
/
这里进行 X509_V_FLAG_X509_STRICT 标志验证,会把check_ca的结果给覆盖掉
if
(ret <
0
)
ctx
-
>error
=
X509_V_ERR_UNSPECIFIED;
else
if
(ret
=
=
0
)
ctx
-
>error
=
X509_V_ERR_EC_KEY_EXPLICIT_PARAMS;
}
if
((x
-
>ex_flags & EXFLAG_CA)
=
=
0
&& x
-
>ex_pathlen !
=
-
1
&& (ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)) {
ctx
-
>error
=
X509_V_ERR_INVALID_EXTENSION;
ret
=
0
;
}
ret
=
X509_check_ca(x);
/
/
这里返回ca检查的结果
switch (must_be_ca) {
case
-
1
:
if
((ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)
&& (ret !
=
1
) && (ret !
=
0
)) {
/
/
当ret不等于
0
和
1
的时候,会把ret置为
0
,及证书有问题
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_CA;
}
else
ret
=
1
;
break
;
case
0
:
if
(ret !
=
0
) {
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_NON_CA;
}
else
ret
=
1
;
break
;
default:
/
*
X509_V_FLAG_X509_STRICT
is
implicit
for
intermediate CAs
*
/
if
((ret
=
=
0
)
|| ((i
+
1
< num || ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)
&& (ret !
=
1
))) {
ret
=
0
;
ctx
-
>error
=
X509_V_ERR_INVALID_CA;
}
else
ret
=
1
;
break
;
}
/
/
这里是判断是否验证 X509_V_FLAG_X509_STRICT 这个标志·
/
/
这里应该就是漏洞的主要原因,没有判断上面check_ca的返回值直接进行 X509_V_FLAG_X509_STRICT 的验证
/
/
如果check_ca的返回结果异常,在继续进行其他验证,会把异常结果覆盖,从而绕过ca检查
/
/
所以补丁文件在这里加上了ret>
0
的时候才进行 X509_V_FLAG_X509_STRICT 的验证
if
((ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT) && num >
1
) {
/
*
Check
for
presence of explicit elliptic curve parameters
*
/
ret
=
check_curve(x);
/
/
这里进行 X509_V_FLAG_X509_STRICT 标志验证,会把check_ca的结果给覆盖掉
if
(ret <
0
)
ctx
-
>error
=
X509_V_ERR_UNSPECIFIED;
else
if
(ret
=
=
0
)
ctx
-
>error
=
X509_V_ERR_EC_KEY_EXPLICIT_PARAMS;
}
if
((x
-
>ex_flags & EXFLAG_CA)
=
=
0
&& x
-
>ex_pathlen !
=
-
1
&& (ctx
-
>param
-
>flags & X509_V_FLAG_X509_STRICT)) {
ctx
-
>error
=
X509_V_ERR_INVALID_EXTENSION;
ret
=
0
;
}
/
/
这里必须显式设置才行!!!!
X509_STORE_set_flags(store, X509_V_FLAG_X509_STRICT);
/
/
验证的时候设置一下,即可触发漏洞,绕过ca检查
/
/
这里必须显式设置才行!!!!
X509_STORE_set_flags(store, X509_V_FLAG_X509_STRICT);
/
/
验证的时候设置一下,即可触发漏洞,绕过ca检查
/
/
根证书
-
-
>签发中间证书
-
-
>签发待验证证书
/
/
这里构造一条证书链
static const char
*
roots_f
=
"ca.cer"
;
/
/
根证书
static const char
*
untrusted_f
=
"crt.cer"
;
/
/
待验证证书
static const char
*
interrept
=
"crt_inter.cer"
;
/
/
中间证书
static
int
test_chains_cert()
{
int
ret
=
0
;
int
i;
X509
*
x
=
NULL;
STACK_OF(X509)
*
untrusted
=
NULL;
BIO
*
bio
=
NULL;
X509_STORE_CTX
*
sctx
=
NULL;
X509_STORE
*
store
=
NULL;
X509_LOOKUP
*
lookup
=
NULL;
STACK_OF(X509)
*
intermediates
=
NULL;
X509
*
icert
=
NULL;
BIO
*
bio_in
=
NULL;
store
=
X509_STORE_new();
if
(store
=
=
NULL)
goto err;
lookup
=
X509_STORE_add_lookup(store, X509_LOOKUP_file());
if
(lookup
=
=
NULL)
goto err;
if
(!X509_LOOKUP_load_file(lookup, roots_f, X509_FILETYPE_PEM))
goto err;
/
/
这里必须显式设置才行!!!!
X509_STORE_set_flags(store, X509_V_FLAG_X509_STRICT);
/
/
x
=
load_cert_pem(untrusted_f);
if
((bio
=
BIO_new_file(untrusted_f,
"r"
))
=
=
NULL)
goto err;
if
((x
=
PEM_read_bio_X509(bio, NULL,
0
, NULL))
=
=
NULL)
goto err;
sctx
=
X509_STORE_CTX_new();
if
(sctx
=
=
NULL)
goto err;
intermediates
=
sk_X509_new_null();
bio_in
=
BIO_new_file(interrept,
"r"
);
icert
=
PEM_read_bio_X509(bio_in, NULL, NULL, NULL);
BIO_free(bio_in);
sk_X509_push(intermediates, icert);
if
(!X509_STORE_CTX_init(sctx, store, x, intermediates))
goto err;
printf(
"here here\n"
);
i
=
X509_verify_cert(sctx);
if
(i
=
=
0
&& X509_STORE_CTX_get_error(sctx)
=
=
X509_V_ERR_INVALID_CA) {
/
*
This
is
the result we were expecting: Test passed
*
/
fprintf(stderr,
"X509_verify_cert is Failed. error: %d\n"
, GetLastError());
goto err;
}
fprintf(stderr,
"X509_verify_cert is OK! return -> %s\n"
, i ?
"true"
:
"false"
);
err:
X509_STORE_CTX_free(sctx);
X509_free(x);
BIO_free(bio);
sk_X509_pop_free(untrusted, X509_free);
X509_STORE_free(store);
return
ret;
}
/
/
根证书
-
-
>签发中间证书
-
-
>签发待验证证书
/
/
这里构造一条证书链
static const char
*
roots_f
=
"ca.cer"
;
/
/
根证书
static const char
*
untrusted_f
=
"crt.cer"
;
/
/
待验证证书
static const char
*
interrept
=
"crt_inter.cer"
;
/
/
中间证书
static
int
test_chains_cert()
{
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-5-21 14:47
被陌殇编辑
,原因:
赞赏
他的文章
- [原创]第一题,签到题wp 1955
- [原创]OpenSSL漏洞分析(CVE-2021-3450) 8833
- [原创]某国内知名(动物命名)摄像头启动流程及固件解密 15910
- [原创]Android应用完整性保护总结 17450
看原图
赞赏
雪币:
留言: