CVE-2025-54404,CVE-2025-54403
Multiple OS command injection vulnerabilities exist in the swctrl functionality of Planet WGR-500 v1.3411b190912. A specially crafted network request can lead to arbitrary command execution. An attacker can send a network request to trigger these vulnerabilities.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Planet WGR-500 v1.3411b190912
WGR-500 - https://www.planet.com.tw/
8.8 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
The WGR-500 is a high-performance industrial router designed to support VLAN configurations, equipped with a built-in firewall, and offering a robust suite of advanced networking functionalities.
The WGR-500 device features a service named swctrl
that operates over UDP using a custom protocol. This protocol enables a sender to issue commands to the service. The initial portion of the function responsible for managing these requests is shown below:
void manage_request(int32_t socket) __noreturn
{
char recv_buff[0x100];
memset(&recv_buff, 0, 0x100);
while (true)
{
int32_t addrlen = 0x10;
memset(&recv_buff, 0, 0x100);
struct sockaddr_in sockaddr;
int32_t num_of_bytes_recv =
recvfrom(socket, &recv_buff, 0x100, 0, &sockaddr, &addrlen);
int32_t num_of_bytes_recv_1 = num_of_bytes_recv;
if (num_of_bytes_recv >= 0)
{
struct packet_header header;
struct packet_header* var_2c = &header;
memcpy(&header, &recv_buff, 0x10);
[1] if (!strncmp(var_2c, "PLANETut", 8))
The manage_request
function reads from the socket up to 0x100 bytes into the recv_buff
buffer, that is of type packet_header
:
struct packet_header __packed
{
char magic[0x8];
uint16_t command;
char mac_addr_provided[0x6];
};
This packet_header
structure contains:
magic
: An 8-byte field that is compared at [1]
. For execution to proceed, this field must contain the string “PLANETut”.command
: A uint16_t
field, although only a uint8_t
(the lower 8 bits) is used for the actual command.mac_addr_provided
: A 6-byte field intended for the MAC address of the device’s main interface.If the received command is 0x90
(represented by CHANGE_SETTINGS_0x90
), the swctrl
service enters the “change settings” branch. This functionality allows for password modification and configuration of the device’s network settings (DHCP or static IP).
if (command_recv == CHANGE_SETTINGS_0x90)
{
struct change_pwd_struct change_pwd_struct;
memset(&change_pwd_struct, 0, 0x62);
[2] memcpy(&change_pwd_struct, &recv_buff, 0x62);
[3] if (!check_password(&change_pwd_struct.password))
{
[...]
}
else
{
uint32_t use_dhcp = change_pwd_struct.use_dhcp;
if (change_pwd_struct.new_password[0])
{
uint8_t* new_pass_ptr = &change_pwd_struct.new_password;
uint8_t next_new_pass_char = change_pwd_struct.new_password[0];
[4] while (true)
{
uint8_t old_pass_char =
*(uint8_t*)(new_pass_ptr - 0x10);
*new_pass_ptr = (
(uint8_t)(next_new_pass_char >> 4) & 0xf)
| (uint8_t)(next_new_pass_char << 4);
*(uint8_t*)(new_pass_ptr - 0x10) = (
(uint8_t)(old_pass_char >> 4) & 0xf)
| (uint8_t)(old_pass_char << 4);
new_pass_ptr = &new_pass_ptr[1];
// end of the struct
if (new_pass_ptr == (change_pwd_struct + 0x60))
break;
next_new_pass_char = *new_pass_ptr;
}
char* new_pwd_ptr =
&change_pwd_struct.new_password;
[5] sprintf(&string_to_cmd,
"flash set USER_PASSWORD %s",
&change_pwd_struct.new_password);
[6] system(&string_to_cmd);
The code above, at [2]
, loads 0x62 bytes of the received message into the change_pwd_struct
variable, which is of type struct change_pwd_struct
:
struct change_pwd_struct __packed
{
struct packet_header packet_header;
char use_dhcp;
uint8_t field_11;
uint8_t field_12;
uint8_t field_13;
uint8_t ip[0x4];
uint8_t subnet[0x4];
uint8_t gateway[0x4];
char device_name[0x20];
char password[0x10];
char new_password[0x10];
__offset(0x60)
};
This structure contains the current password under the password
field. The provided password is checked at [3]
. If the password is correct, the code at [4]
is executed. Within the loop at [4]
, the password
and new_password
fields of the message are “decoded.” This decoding process involves swapping the nibbles (the first 4 bits and the last 4 bits) of each byte.
At [5]
, the string "flash set USER_PASSWORD <new_password>"
is constructed and then used at [6]
as a command for system
to change the password.
Subsequently, the use_dhcp
field in the provided packet is checked and used to adjust settings accordingly:
uint32_t first_ip_octect = (uint32_t)change_pwd_struct.ip[0];
if (use_dhcp)
strcpy(&return_message_buff, "Set DHCP done.");
else if (first_ip_octect == 127)
{
[... error branch ...]
}
else
{
[... set ip, netmask and gateway in the flash ...]
[7] char* device_name_cursor =
&change_pwd_struct.device_name;
int32_t idx = 0;
char* next_hostname_char =
(uint32_t)change_pwd_struct.device_name[0];
while (true)
{
device_name_cursor = &device_name_cursor[1];
if (next_hostname_char != '\n' && next_hostname_char != ' '
&& next_hostname_char)
{
new_device_name[idx] =
(char)next_hostname_char;
idx += 1;
}
if (device_name_cursor ==
&change_pwd_struct.password)
break;
next_hostname_char =
(uint32_t)*(uint8_t*)device_name_cursor;
}
[8] sprintf(&temp_stack_buff,
"flash set DEVICE_NAME %s",
&new_device_name);
[9] system(&temp_stack_buff);
[...]
}
If use_dhcp
is not set and the first octet of the provided IP address is not 127
, then the code at [7]
is reached. The device_name_cursor
variable is then parsed to skip newlines and spaces, and the resulting hostname is stored in new_device_name
. At [8]
, the string "flash set DEVICE_NAME <new_device_name>"
is created and subsequently used at [9]
with the system
function to modify the DEVICE_NAME
variable in the device’s flash memory.
In both cases, the strings passed to system
are constructed using attacker-controlled input without proper sanitization, allowing arbitrary shell command execution. Each instance is detailed below.
The swctrl
program allows, via command 0x90
, the modification of the administrator password. At [5]
, the string "flash set USER_PASSWORD <new_password>"
is constructed and subsequently used by the system function at [6]
. This creates an OS command injection vulnerability at [6]
through the new_password
string received by swctrl
. An attacker can exploit this vulnerability to execute arbitrary operating system commands.
The swctrl
program allows, via command 0x90
, the modification of the device’s DEVICE_NAME
. At [8]
, the string "flash set DEVICE_NAME <new_device_name>"
is constructed and subsequently used by the system function at [9]
. This creates an OS command injection vulnerability at [9]
through the new_device_name
string received by swctrl
. An attacker can exploit this vulnerability to execute arbitrary operating system commands.
2025-07-30 - Initial Vendor Contact
2025-08-01 - Vendor Disclosure
2025-08-01 - Vendor Confirmed Receipt
2025-09-01 - Status Update Request
2025-09-01 - Vendor Reply
2025-09-24 - Vendor Reply Acknowledged. Release Date Announced.
2025-10-07 - Public Release
Discovered by Francesco Benvenuto of Cisco Talos.