声明
出品|且听安全(ID:QCyber)
以下内容,来自且听安全公众号的作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
漏洞信息
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` :
进行一z列参数赋值与类型转换后进入 `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` 函数):
伪造数据包肯定可以绕过检查,有兴趣的小伙伴可以自行深入分析协议解析过程完成构造!
修复方式
多处增加了SQL注入检查,比如`findCfgDeviceList` :
多处增加了路径穿越检查,比如 `exportDeviceList` :
缝缝补补修复的地方很多,但是对比分析后仍然感觉程序代码怎一个乱字了得!