openssl 是目前最流行的 SSL 密码库工具,其提供了一个通用、健壮、功能完备的工具套件.
浅浅了解一下c_rehash 命令:
1. c_rehash是openssl中的一个用perl编写的脚本工具,c_rehash 为文件创建一个符号连接,并将此符号连接的名称设为文件的hash值,作用是让openssl在证书目录中能够找到证书,批量创建证书等文件 。
语法:c_rehash [-old] [-h] [-n] [-v] [ directory... ]
-old:使用1.0.0版本之前的老式hash(MD5,而不是SHA-1)去生成连接。
-h:显示一个简短的使用信息。
-n:不要移除已经存在的连接。当要同时保留新的和老式的连接时需要此选项。
-v:打印移除的老式连接和新创建的连接的信息。默认c_rehash只会列出被处理目录的信息。
2. 如果命令行中指定了目录名,则依次处理它们。
如果命令行中没有指定目录名,那么会去询问SSL_CERT_DIR环境变量。
(SSL_CERT_DIR环境变量:以冒号作为分隔,存放目录列表。)
如果环境变量中未指定目录,则默认的目录由安装时的信息指定。
通常是:/usr/local/ssl/certs。
回归正题:
Openssl命令注入漏洞形成的原因:
该漏洞由于c_rehash脚本未对外部可控数据进行有效过滤。
危害:
该漏洞在未授权的情况下,导致可操作/etc/ssl/certs/的攻击者,以脚本的权限执行任意命令,从而执行系统命令,导致远程代码执行。
影响版本:
OpenSSL 1.0.2
OpenSSL 1.1.1
OpenSSL 3.x
漏洞分析:
openssl/tools at master · openssl/openssl · GitHub //c_rehash源码
大致能看懂,源码解释来自CVE-2022-1292的分析_黑客技术
一部分源码:
# 将命令行传入的参数赋值给dirlist
if (@ARGV) {
@dirlist = @ARGV;
} elsif ($ENV{SSL_CERT_DIR}) {
@dirlist = split /$path_delim/, $ENV{SSL_CERT_DIR};
} else {
$dirlist[0] = "$dir/certs";
}
# 判断$dirlist[0]目录是否存在, 并把工作目录修改为$dirlist[0]
# 检查是否存在$openssl是否存在
if (-d $dirlist[0]) {
chdir $dirlist[0];
$openssl="$pwd/$openssl" if (!-x $openssl);
chdir $pwd;
}
# 检查$dirlist中的每一项是否存在且可写, 均满足则调用hash_dir函数
foreach (@dirlist) {
if (-d $_ ) {
if ( -w $_) {
hash_dir($_);
} else {
print "Skipping $_, can't write\n";
$errorcount++;
}
}
}
exit($errorcount);
接下来进入hash_dir函数, 也就是存在问题的函数
sub hash_dir {
my %hashlist;
print "Doing $_[0]\n";
chdir $_[0];
opendir(DIR, ".");
# 问题出在这里, 将改目录所有文件名读入到flist数组, 但没有处理, 导致文件名命令注入的可能
my @flist = sort readdir(DIR);
closedir DIR;
if ( $removelinks ) {
# Delete any existing symbolic links
foreach (grep {/^[\da-f]+\.r{0,1}\d+$/} @flist) {
if (-l $_) {
print "unlink $_" if $verbose;
unlink $_ || warn "Can't unlink $_, $!\n";
}
}
}
# 这里也不是很严格, 仅仅需要文件名含有所定义的关键字即可
FILE: foreach $fname (grep {/\.(pem)|(crt)|(cer)|(crl)$/} @flist) {
# Check to see if certificates and/or CRLs present.
my ($cert, $crl) = check_file($fname);
if (!$cert && !$crl) {
print STDERR "WARNING: $fname does not contain a certificate or CRL: skipping\n";
next;
}
link_hash_cert($fname) if ($cert);
link_hash_crl($fname) if ($crl);
}
}
#使用perl -d c_rehash "."调试一下, perl自带的调式工具。
#可以看到$flist[5]文件名中存在"`"特殊字符
#导致没有过滤反引号就直接将文件名拼接到了命令中,那么我们在文件名中添加反引号即可执行任意命令。
#由于文件名检测, 只要求文件名含有(pem)|(crt)|(cer)|(crl)字符即可, 所以恶意文件名通过检测, 进入check_file函数
sub check_file {
my ($is_cert, $is_crl) = (0,0);
my $fname = $_[0];
open IN, $fname;
while(<IN>) {
if (/^-----BEGIN (.*)-----/) {
my $hdr = $1;
if ($hdr =~ /^(X509 |TRUSTED |)CERTIFICATE$/) {
$is_cert = 1;
last if ($is_crl);
} elsif ($hdr eq "X509 CRL") {
$is_crl = 1;
last if ($is_cert);
}
}
}
close IN;
return ($is_cert, $is_crl);
}
主要检查文件头格式是否满足要求, 这样是为什么最开始制作恶意文件需要带上-----BEGIN CERTIFICATE-----
检查通过到link_hash_cert函数也就是注入发生的函数
sub link_hash_cert {
my $fname = $_[0];
# 这里对于传过来的参数也没有检查, 并且把$fname直接用``包裹, 直接执行命令, 导致$fname中的命令也可以执行
$fname =~ s/\"/\\\"/g;
my ($hash, $fprint) = `"$openssl" x509 $x509hash -fingerprint -noout -in "$fname"`;
...
漏洞修复:
OpenSSL 1.0.2 升级至 1.0.2ze (仅针对高级用户);
OpenSSL 1.1.1 升级至 1.1.1o;
OpenSSL 3.0 升级至 3.0.3;