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:

  • GC108P
  • GC108PP
  • GS108Tv3
  • GS110TPP
  • GS110TPv3
  • GS110TUP
  • GS308T
  • GS310TP
  • GS710TUP
  • GS716TP
  • GS716TPP
  • GS724TPP
  • GS724TPv2
  • GS728TPPv2
  • GS728TPv2
  • GS750E
  • GS752TPP
  • GS752TPv2
  • MS510TXM
  • MS510TXUP

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.

CVSS, CVE, etc

Some human readable details are in the next section.

  • Vulnerability Codename: Draconian Fear
  • Vendor-specific ID: PSV-2021-0144
  • CVE: CVE-2021-40867
  • CVSS: 7.8 (High)1, CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
  • Patch Diff Risk: Medium

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).

Detailed Report

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

Add a comment:

Nick:
URL (optional):
Math captcha: 7 ∗ 3 + 9 =