TL;DR
In early 2021, we reported a few security issues to Cisco related to their RV34X series of routers, two of which have been recently patched. The issues in question were an authentication bypass and system command injection, both in the web management interface. These can be chained together to achieve unauthenticated command execution.
Cisco has released an advisory, and assigned CVE IDs as follows:
- RV34X /upload Authorization Bypass Vulnerabilityย (CVE-2021-1472)
- RV34X OS Command injection in Cookie string (CVE-2021-1473)
The issues have been fixed in firmware version 1.0.03.21 in the RV34X series. Cisco has noted that the RV26X and RV16X series are also affected by the authentication bypass issue, and has released firmware version 1.0.01.03 to address this.
This post contains a root cause analysis for these bugs. Enjoy!

ยฉ Cisco
Affected vendor & product | Cisco Small Business RV Series Router (www.cisco.com) |
Vulnerable version | RV34X 1.0.3.20 & below, RV16X/RV26X 1.0.01.02 & below. |
Fixed version | RV34X series: 1.0.03.21. RV16X/RV26X: 1.0.01.03. |
CVE IDs | CVE-2021-1472, CVE-2021-1473 |
Impact | 5.3 (medium) CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N 8.8 (high) CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L |
Credit | T. Shiomitsu, IoT Inspector Research Lab |
RV34X/RV26X/RV16X /upload Authorization Bypass Vulnerability (CVE-2021-1472)
While Cisco has noted that this issue affects other devices, I’ll only go over the specifics of how it affects the RV34X series here. On RV34X devices,ย theย webย managementย interface is served byย nginx
ย onย port 443.ย nginx
ย is configured (by files inย /etc/nginx/
) so that requestsย made to the URIsย /upload
,ย /form-file-upload
ย andย /api/operations/ciscosb-file:form-file-upload
are all passed to a CGI binary called upload.cgi
. Depending on which URI is requested, theย behaviorย ofย upload.cgi
is slightly different.
In firmware revisions earlier than 1.0.3.20, there was no real attemptย to restrict access to theseย upload.cgi
-related endpoints. In fact, aย set of command injection issues from late 2020ย affecting the RV34X seriesย were initially disclosed asย post-authentication issues but later revised to reflect the fact that these could be exploited pre-authentication (after a very charitable and publicly-uncredited researcher – cough cough – tipped off the Cisco PSIRT). These were tracked by the ZDI as ZDI-20-1100ย and ZDI-20-1101, and you can see the Cisco advisory here. While the ZDI advisories have not been updated and still show the initial lower CVSS rating โ the Cisco advisory and CVSS scores have been updated to reflect the pre-authentication nature of the bugs.
In 1.0.03.20, an authentication check was implemented. This was written into nginx
configuration, which you can see here:
The attempt hereย appears to beย toย checkย that someย Authorization
ย header is set, and/or that a file exists in theย /tmp/websession/token/
ย folder with the same name as theย requestย sessionid
ย cookie.ย Then a user is assumed to be authorized.
Unfortunately, thereโs a fatal flaw in this fix. The logic is suchย that any non-nullย Authorization
ย headerย would setย $deny
ย toย โ0โ. So,ย sending literallyย anyย valid-lookingย Authorization
ย headerย as part ofย aย requestย toย /upload
ย willย bypass the authorization check.
RV34X OS Command injection in Cookie string (CVE-2021-1473)
Once we have bypassed authentication, itโs then possible to interact directly with theย /upload
ย endpoint. Requests made to this endpoint areย passed directly to theย upload.cgi
ย binary byย theย nginx
uwsgi
ย CGI configuration.
Within theย main()
ย functionย inย upload.cgi
, theย HTTP_COOKIE
ย environmental variable isย read, and the value from theย sessionid
ย cookie is extracted using a simple series ofย strtok_r
ย andย strstr
. Thisย specificย sessionid
-reading logicย is notableย because,ย due to theย strtok_r
ย call, itโs not possible to use โ;โ characters inย anyย injection, as it will prematurely terminateย theย injection string. In pseudocode, it looks like this:
if (HTTP_COOKIE != (char *)0x0) { StrBufSetStr(cookie,HTTP_COOKIE); cookie = StrBufToStr(cookie); cookie = strtok_r(cookie, ";", &saveptr); while (cookie != 0x0) { cookie = strstr(cookie, "sessionid="); if (cookie != 0x0) { sessionid_cookie_value = pathparam_ + 10; } } }
Because ourย HTTP request is made to theย /upload
ย URI, theย main()
ย function inย upload.cgi
ย calls a function atย 000124a4
, which Iโve namedย handle_upload()
. This function takes a pointer to theย sessionid
ย cookie value as its first argument.
voidย handle_upload(char *sessionId, char *destination, charย *option, char *pathparam, char *fileparam, char *cert_name, char *cert_type, char *password)ย
It also takesย severalย other arguments,ย each of whichย are populated by the multipart request parsingย that takes placeย in theย main()
ย function. The names Iโve given these arguments roughly align with the names of the parameters thatย thisย multipartย ingesting logicย looks for.
Depending on what string is passed as theย
pathparam
ย parameter, slightly different code paths will be taken, which means that slightly different checks must be bypassed to be able to reach the vulnerable code. In this example, I am using a request with theยpathparam
set to โConfigurationโ, so the pseudocode I’m showing reflects this.
Withinย handle_upload()
, aย curl
ย command is constructed with a call toย sprintf
, the resulting buffer of which is then passed directly toย popen
:
ret = strcmp(pathparam, "Configuration"); if (ret == 0) { config_json = upload_Configuration_json(destination,fileparam); if (config_json != 0) { post_data = json_object_to_json_string(config_json); sprintf(command_buf, "curl %s --cookie \'sessionid=%s\' -X POST -H \'Content-Type: application/json\' -d\'%s\' ", jsonrpc_cgi, sessionId , post_data); debug("curl_cmd=%s",command_buf); __stream = popen(command_buf, "r"); if (__stream != (FILE *)0x0) { [...snip...] }
Theย sessionid
ย cookie value that we have passed in our request is passed directly into thisย sprintf()
ย call. With a craftedย sessionid
ย value, we would therefore be able to inject arbitrary commands into this command buffer.ย This will run the command with the privileges of theย upload.cgi
ย binaryย which, in thisย case, isย www-data
.
Key Takeaways
Logic bugs can be quite easy to introduce, and sometimes tricky to identify. Authentication can be difficult to implement well, especially when multiple authorization methods might be accepted. As higher-end embedded devices start to use more common server software components (for purposes they were not necessarily intended for), there are often more layers of complexity introduced – thicker web servers requiring more precise configuration, CGI binaries, middleware gluing things together. Each layer introduces opportunities for mis-configuration, which could lead to security issues.
Timeline
2021-01-02: Initial disclosure made to Cisco PSIRT.
2021-01-07: Confirmation of receipt of disclosure from Cisco PSIRT.
2021-01-27: Confirmation that issue is valid from Cisco PSIRT.
2021-02-12: Update from Cisco PSIRT.
2021-03-23: We contact Cisco PSIRT for timeline update and CVE IDs.
2021-03-23: Cisco PSIRT respond giving us timeline and CVE IDs.
2021-04-07: Cisco release advisory.