TL;DR: NETGEAR just patched 3 reported vulnerabilities (Demon's Cries, Draconian Fear and Seventh Inferno) in some managed (smart) switches. If you or your company owns any of these devices, please patch now.
Note: Details on Seventh Inferno will be publish on or after 13th September.
Affected devices:
NETGEAR's advisory can be found here: Security Advisory for Multiple Vulnerabilities on Some Smart Switches, PSV-2021-0140, PSV-2021-0144, PSV-2021-0145.
Some human readable details are in the next section.
1 NETGEAR on the advisory page says it's 7.4 (High), since we rated Attack Complexity and User Interaction differently (in my case I think the attack complexity is low, but it does require user interaction - admin needs to be in the process of logging in; NETGEAR rated the opposite).
Published on September 6th, 2021.
Draconian Fear
*** Summary:
Affected Model: NETGEAR GS110TPV3 Smart Managed Pro Switch (and some other)
Firmware Version: V7.0.6.3 (from 2021-05-07)
NETGEAR GS110TPV3 Smart Managed Pro Switch is vulnerable to authentication
hijacking (for lack of a better term) that allows an attacker with the same IP
as a logging in admin to hijack the session bootstrapping information, giving
the attacker full admin access to the device web UI and resulting in a full
compromise of the device.
The obvious limiting factor here is the requirement for the attacker to either
have the same IP as the admin (foothold on the same machine with limited
privileges, same source NAT IP, etc) or being able to spoof the IP with various
low-level network shenanigans, as well winning a race condition with a 1-second
window (pretty easy actually).
Attached PoC will attempt to win the race and hijack session bootstrap
information.
IMPORTANT: This vulnerability is reported under the 90-day policy, i.e. this
report will be shared publicly with the defensive community on 6th September
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 other NETGEAR devices reuse the same code.
*** More details:
Web UI authentication logic on this device goes like this:
1. Admin opens the website and enters the password.
2. The password is obfuscated and sent to /cgi/set.cgi?cmd=home_loginAuth.
3. The set.cgi handler (cgi_home_loginAuth_set) creates an authing session file
(libcgiutil.so's cgi_util_authingSession_create) named:
/tmp/sess/guiAuth_info_{handlerPid}
This file contains:
* username
* password
* name of the result file /tmp/sess/guiAuth_{http}_{clientIP}_{userAgent}
* {clientIP}
* {http}
* {userAgent}
(where {http} is either "http" or "https" string, and {userAgent} is
an integer between 1 and 5 denoting a type of browser)
4. The same handler creates another file named /tmp/_polld_act_web_login and
fills it with a command to be executed:
/home/web/cgi/login.cgi {handlerPid} &
5. Then sends a SIGUSR1 signal to polld daemon and returns a generic HTTP
response.
6. The polld daemon upon receiving the SIGUSR1 opens the created
/tmp/_polld_act_web_login file and executes the command within it.
7. The login.cgi program uses the data inside the
/tmp/sess/guiAuth_info_{handlerPid} file to authenticate the user, and
writes the result in the /tmp/sess/guiAuth_{http}_{clientIP}_{userAgent}
file.
8. At the same time the browser is instructed to poll the
/cgi/get.cgi?cmd=home_loginStatus endpoint every second to receive
information whether the authentication is still in progress, or whether it
failed or succeeded.
9. The get.cgi handler (cgi_home_loginStatus_get) takes the client IP, http or
https schema, and user agent type, and opens the status file
/tmp/sess/guiAuth_{http}_{clientIP}_{userAgent} to check the status.
In case the status is 1 (followed by session id), the handler returns the
session bootstrapping material (session id and RSA public key).
To put it another way, the browser first sends the login information using
set.cgi, and then polls get.cgi to get the session id.
The problem is that get.cgi relies only on the IP and a guessable 1-5 browser
type number for verification whether the polling party is actually the same as
the party that sent in the login information. I.e. there is no e.g. session
cookie that ties the subsequent set.cgi and get.cgi together.
Given this, an attacker on the same IP as the admin can just flood the get.cgi
with requests and snatch the session information as soon as it appears. Since
the window between get.cgi requests on the browser is 1 second, that allows an
attacker to send multiple requests effectively greatly increasing the odds of
getting the session information before admin's browser gets it (in my tests the
attached PoC wins the race 9 out of 10 times).
*** Proposed fix:
The name of the result file should be a cryptographically secure random value
(e.g. 32 lower-case hexadecimal characters), e.g. following this pattern:
/tmp/sess/guiAuth_result_{random32lowerCaseHexChars}
The {random} value should then be passed when returning from the set.cgi request
to the browser and used in subsequent get.cgi requests.
Since the value has the charset limited to hexadecimal characters, and has a set
length, it's quite easy to verify that the get.cgi parameter meets these
criteria (and therefore eliminate any possibility of a path traversal / pointing
to an arbitrary file by the attacker).
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 time
SWITCH_ADDR = '192.168.2.14' # Address of the target switch.
headers = {
'User-Agent': 'Chrome', # Change the user agent if needed.
}
while True:
r = requests.get(
f"http://{SWITCH_ADDR}/cgi/get.cgi?cmd=home_loginStatus&token=bla",
verify=False,
headers=headers,
)
#print(r.status_code)
d = r.json()
print(d["data"]["status"])
if d["data"]["status"] != "authing":
print(d)
break