Path: blob/master/lib/msf/core/exploit/pgadmin.rb
19611 views
# -*- coding: binary -*-12#3# This mixin provides helpers to interact with pgAdmin. It provides methods to:4# - authenticate5# - obtain the CSRF token,6# - check the version of pgAdmin.7#8module Msf9module Exploit::PgAdmin10include Msf::Exploit::Remote::HttpClient1112def auth_required?13res = send_request_cgi('uri' => normalize_uri(target_uri.path), 'keep_cookies' => true)14if res&.code == 302 && res.headers['Location']['login']15return true16end17false18end1920def get_version21if auth_required?22res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)23else24res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'browser/'), 'keep_cookies' => true)25end26html_document = res&.get_html_document27return unless html_document&.xpath('//title').text == 'pgAdmin 4'2829# there's multiple links in the HTML that expose the version number in the [X]XYYZZ,30# see: https://github.com/pgadmin-org/pgadmin4/blob/053b1e3d693db987d1c947e1cb34daf842e387b7/web/version.py#L2731versioned_link = html_document.xpath('//link').find { |link| link['href'] =~ /\?ver=(\d?\d)(\d\d)(\d\d)/ }32return unless versioned_link3334Rex::Version.new("#{Regexp.last_match(1).to_i}.#{Regexp.last_match(2).to_i}.#{Regexp.last_match(3).to_i}")35end3637def check_version(patched_version, low_bound = 0)38version = get_version39return Msf::Exploit::CheckCode::Unknown('Unable to determine the target version') unless version40return Msf::Exploit::CheckCode::Safe("pgAdmin version #{version} is not affected") if version >= Rex::Version.new(patched_version) || version < Rex::Version.new(low_bound)4142Msf::Exploit::CheckCode::Appears("pgAdmin version #{version} is affected")43end4445def csrf_token46return @csrf_token if @csrf_token4748if auth_required?49res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)50set_csrf_token_from_login_page(res)51else52res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'browser/js/utils.js'), 'keep_cookies' => true)53set_csrf_token_from_config(res)54end55fail_with(Failure::UnexpectedReply, 'Failed to obtain the CSRF token') unless @csrf_token56@csrf_token57end5859def set_csrf_token_from_config(res)6061# The CSRF token should be inside a java script tag, inside a function called window.renderSecurityPage and should look like:62# ImQzYTQ0YzAzOGMyY2YwZWNkMWRkY2Q4ODdhMTA5MGM3YzI5ZTYzY2Ii.Z_6Kdw.XP2eOIJ26MikqG5J8J8W1bDPMpQ63if res&.code == 200 && res.body =~ /csrfToken": "([\w+.-]+)"/64@csrf_token = Regexp.last_match(1)65# at some point between v7.0 and 7.7 the token format changed66else67@csrf_token = res&.body.scan(/pgAdmin\['csrf_token'\]\s*=\s*'([^']+)'/)&.flatten&.first68end69end7071def set_csrf_token_from_login_page(res)72if res&.code == 200 && res.body =~ /csrfToken": "([\w+.-]+)"/73@csrf_token = Regexp.last_match(1)74# at some point between v7.0 and 7.7 the token format changed75elsif (element = res.get_html_document.xpath("//input[@id='csrf_token']")&.first)76@csrf_token = element['value']77end78end7980def authenticate(username, password)81res = send_request_cgi({82'uri' => normalize_uri(target_uri.path, 'authenticate/login'),83'method' => 'POST',84'keep_cookies' => true,85'vars_post' => {86'csrf_token' => csrf_token,87'email' => username,88'password' => password,89'language' => 'en',90'internal_button' => 'Login'91}92})9394unless res&.code == 302 && res&.headers&.[]('Location') != normalize_uri(target_uri.path, 'login')95fail_with(Msf::Exploit::Failure::NoAccess, 'Failed to authenticate to pgAdmin')96end9798print_good('Successfully authenticated to pgAdmin')99res100end101end102end103104105