Advantech iView 多個不同類型匿名 RCE 漏洞分享
漏洞信息
2022.06 ZDI 發布多個 Advantech iView 漏洞信息 CVE-2022-2135&2143&2135 等 :

影響版本為 Advantech iView All versions prior to 5_7_04_6469。重點關注其中評分為 `9.8` 的高危漏洞,發現問題主要集中在 `NetworkServlet` ,整理的部分漏洞信息如下:
- `findCfgDeviceList` :When parsing the COLUMN and VALUE elements of the findCfgDeviceList action, the process does not properly validate a user-supplied string before using it to construct SQL queries. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `getAllActiveTraps` :When parsing the search_date_to and search_date_from elements of the getAllActiveTraps action, the process does not properly validate a user-supplied string before using it to construct SQL queries. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `performZTPConfig` :When parsing any element of the performZTPConfig action, the process does not properly validate a user-supplied string before using it to construct SQL queries. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `setTaskEditorItem` :When parsing the DESCRIPTION element of the setTaskEditorItem action, the process does not properly validate a user-supplied string before using it to construct SQL queries. An attacker can leverage this vulnerability to execute arbitrary code in the context of SYSTEM.
- `exportDeviceList` :When parsing the filename element of the exportDeviceList action, the process does not properly validate a user-supplied path prior to using it in file operations. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `findCfgDeviceListDetailsExport` :When parsing the filename element of the findCfgDeviceListDetailsExport action, the process does not properly validate a user-supplied path prior to using it in file operations. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `backupDatabase` :When parsing the backup_filename element of the backupDatabase action, the process does not properly validate a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `runProViewUpgrade` :When parsing the fwfilename element of the runProViewUpgrade action, the process does not properly validate a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
- `findSummaryUpdateDeviceList` :When parsing the COLUMN and VALUE elements of the findSummaryUpdateDeviceList action, the process does not properly validate a user-supplied string before using it to construct SQL queries. An attacker can leverage this vulnerability to execute code in the context of SYSTEM.
分析發現這些漏洞實現 RCE 的方式主要是以下三種方式:
- SQL 注入漏洞結合 `outfile` 實現 getshell
- 直接命令拼接 getshell
- 路徑穿越導致 getshell
查看 `NetworkServlet` 定義:


定位 `com.imc.iview.network.NetworkServlet#doPost` :

提取參數 `page_action_type` ,根據取值調用不同的函數。下面就選擇三個不同接口來分析三種不同 getshell 的方法。
方式1:SQL注入+outfile getshell

接口定義:


提取參數后,調用 `findCfgDeviceList` :

進入 `getDistinctDeviceTypes` :

進入 `buildSelectSQL` :

進行一系列參數賦值與類型轉換后進入 `checkForChassisUpdates` ,傳入的參數 `strSegment` 就是 GET 請求攜帶的參數 `segment` :
private boolean checkForChassisUpdates(String strSegment) { boolean bReturn = false; new String(); int nCount = 0; if (this.initDBServices()) { String strSQL = "SELECT COUNT(*) UpdateCount FROM device_tree_table as a, device_tree_table as b WHERE b.nDeviceTypeId = 18 AND b.dtBuildDate < '2012-07-09 08:04:00' AND a.nDeviceId = b.nParentId AND a.strDescription = '" + strSegment + "'"; Connection conn = this.getConnection();
try { Statement stmt = conn.createStatement();
ResultSet resultset; for(resultset = stmt.executeQuery(strSQL); resultset.next(); nCount = resultset.getInt("UpdateCount")) { }
...}
沒有對參數進行任何檢查,直接拼接進入了 SQL 查詢語句,導致出現 SQL 注入漏洞:

因為 Advantech iView 默認集成的 MySQL 數據庫版本較低,支持 `outfile` ,所以可以利用 SQL 注入直接寫入 webshell:


方式2:mysqldump 命令注入 getshell

接口定義:


提取請求參數 `backup_filename` ,通過 `checkFileNameIncludePath` 和 `checkSQLInjection` 對進行檢查。
- `checkFileNameIncludePath` 函數檢查是否存在路徑穿越,并對 `\webapps\` 等進行了過濾,很顯然 `/webapps/` 就可以繞過。

- `checkSQLInjection` 函數對插件的 SQL 注入關鍵詞進行了過濾,也對 `getRuntime().exec` 等命令執行常用字符串進行了過濾,但是沒有過濾 `ProcessBuilder` 等其他可以命令執行的函數。

回到 `backupDatabase` 函數,通過過濾檢查后,將調用 `mysqldump` 進行數據庫備份:
if (!errFile && !sqlInj) { if (tempDBServices.getMySQLLocation()) { String strMySQLPath = tempDBServices.getMySQLPath(); if (tempDBServices.retrieveDbSettings()) { String strUser = tempDBServices.getStrLoginUserID(); String strPassword = tempDBServices.getStrLoginPassword(); if (tempSystemTable.findDbBackupFilePath()) { String strDbBackupFilePath = tempSystemTable.getDbBackupFilePath(); strDbBackupFilePath = strDbBackupFilePath + strFilename; if (OsUtils.isWindows()) { strExecuteCmd = "\"" + strMySQLPath; strExecuteCmd = strExecuteCmd + "bin\\mysqldump\" -hlocalhost -u " + strUser + " -p" + strPassword + " --add-drop-database -B iview -r \"" + strDbBackupFilePath + "\""; }
try { runtimeProcess = Runtime.getRuntime().exec(strExecuteCmd); ...
將請求的參數直接拼接進入了命令執行語句:

查看 `mysqldump` 支持的參數列表:

其中 `-r` 可以指定導出文件的路徑, `-w` 用于設置導出的數據條件,但其取值也會導出到文件中,所以我們可以用來傳遞 shell 內容。最終通過構造特殊的 `backup_filename` 參數實現 getshell :
方式3:路徑穿越可導致 getshell

接口定義:


提取參數 `filename` ,傳入 `exportDeviceList` 函數,沒有檢查存在路徑穿越風險:

重點關注 `getExportDevices` 和 `createExportFile` 函數:
- `getExportDevices`

StringBuffer strSQL = new StringBuffer("SELECT a.nDeviceId AS \"Device Id\", b.strDescription AS \"Segment\", a.strDeviceTypeId AS \"Device Type\", a.strIPAddress AS \"IP Address\", a.strDNSName AS \"Device Domain Name\", a.strMACAddress AS \"MAC Address\", a.strGetCommunity AS \"Get Community\", a.strSetCommunity AS \"Set Community\", a.strDeviceUser AS \"Device User\", a.strDevicePassword AS \"Device Password\" FROM device_tree_table a, device_tree_table b WHERE a.nParentId = b.nDeviceId");String strColNames = "Device Id, Segment, Device Type, IP Address, Device Domain Name, MAC Address, Get Community, Set Community, Device User, Device Password";
首先從數據表 `device_tree_table` 中查詢符合條件的設備列表 `tempDeviceList` ,然后通過 `setExportList` 賦值給 `m_ExportList` 私有變量。
- `createExportFile`

通過 `getExportList` 提取 `m_ExportList`變量并賦值給 `tempRowOutputList` ,然后寫入路徑可控文件。
從上面分析可知,如果可以將 shell 寫入 `device_tree_table` ,那么結合路徑穿越可以將 shell 寫入任意目錄。比較幸運的是在 `NetworkServlet` 中可以找到添加設備的接口 `addDevices` :



組合 `addDevices` 和 `exportDeviceList` 應該就可以實現 getshell 。
在漏洞復現時發現, `addDevices` 在多個地方需要通過發送 udp 廣播包檢查設備的存活性(比如 `Network#updateProViewFunction` 函數):

偽造數據包肯定可以繞過檢查,有興趣的小伙伴可以自行深入分析協議解析過程完成構造!
修復方式
`NetworkServlet` 加入了認證檢查(如此敏感的 `Servlet` 以前版本居然不需要認證):

多處增加了 SQL 注入檢查,比如 `findCfgDeviceList` :

多處增加了路徑穿越檢查,比如 `exportDeviceList` :

縫縫補補修復的地方很多,但是對比分析后仍然感覺程序代碼怎一個亂字了得!