Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/exploit/pgadmin.rb
19611 views
1
# -*- coding: binary -*-
2
3
#
4
# This mixin provides helpers to interact with pgAdmin. It provides methods to:
5
# - authenticate
6
# - obtain the CSRF token,
7
# - check the version of pgAdmin.
8
#
9
module Msf
10
module Exploit::PgAdmin
11
include Msf::Exploit::Remote::HttpClient
12
13
def auth_required?
14
res = send_request_cgi('uri' => normalize_uri(target_uri.path), 'keep_cookies' => true)
15
if res&.code == 302 && res.headers['Location']['login']
16
return true
17
end
18
false
19
end
20
21
def get_version
22
if auth_required?
23
res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)
24
else
25
res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'browser/'), 'keep_cookies' => true)
26
end
27
html_document = res&.get_html_document
28
return unless html_document&.xpath('//title').text == 'pgAdmin 4'
29
30
# there's multiple links in the HTML that expose the version number in the [X]XYYZZ,
31
# see: https://github.com/pgadmin-org/pgadmin4/blob/053b1e3d693db987d1c947e1cb34daf842e387b7/web/version.py#L27
32
versioned_link = html_document.xpath('//link').find { |link| link['href'] =~ /\?ver=(\d?\d)(\d\d)(\d\d)/ }
33
return unless versioned_link
34
35
Rex::Version.new("#{Regexp.last_match(1).to_i}.#{Regexp.last_match(2).to_i}.#{Regexp.last_match(3).to_i}")
36
end
37
38
def check_version(patched_version, low_bound = 0)
39
version = get_version
40
return Msf::Exploit::CheckCode::Unknown('Unable to determine the target version') unless version
41
return Msf::Exploit::CheckCode::Safe("pgAdmin version #{version} is not affected") if version >= Rex::Version.new(patched_version) || version < Rex::Version.new(low_bound)
42
43
Msf::Exploit::CheckCode::Appears("pgAdmin version #{version} is affected")
44
end
45
46
def csrf_token
47
return @csrf_token if @csrf_token
48
49
if auth_required?
50
res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'login'), 'keep_cookies' => true)
51
set_csrf_token_from_login_page(res)
52
else
53
res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'browser/js/utils.js'), 'keep_cookies' => true)
54
set_csrf_token_from_config(res)
55
end
56
fail_with(Failure::UnexpectedReply, 'Failed to obtain the CSRF token') unless @csrf_token
57
@csrf_token
58
end
59
60
def set_csrf_token_from_config(res)
61
62
# The CSRF token should be inside a java script tag, inside a function called window.renderSecurityPage and should look like:
63
# ImQzYTQ0YzAzOGMyY2YwZWNkMWRkY2Q4ODdhMTA5MGM3YzI5ZTYzY2Ii.Z_6Kdw.XP2eOIJ26MikqG5J8J8W1bDPMpQ
64
if res&.code == 200 && res.body =~ /csrfToken": "([\w+.-]+)"/
65
@csrf_token = Regexp.last_match(1)
66
# at some point between v7.0 and 7.7 the token format changed
67
else
68
@csrf_token = res&.body.scan(/pgAdmin\['csrf_token'\]\s*=\s*'([^']+)'/)&.flatten&.first
69
end
70
end
71
72
def set_csrf_token_from_login_page(res)
73
if res&.code == 200 && res.body =~ /csrfToken": "([\w+.-]+)"/
74
@csrf_token = Regexp.last_match(1)
75
# at some point between v7.0 and 7.7 the token format changed
76
elsif (element = res.get_html_document.xpath("//input[@id='csrf_token']")&.first)
77
@csrf_token = element['value']
78
end
79
end
80
81
def authenticate(username, password)
82
res = send_request_cgi({
83
'uri' => normalize_uri(target_uri.path, 'authenticate/login'),
84
'method' => 'POST',
85
'keep_cookies' => true,
86
'vars_post' => {
87
'csrf_token' => csrf_token,
88
'email' => username,
89
'password' => password,
90
'language' => 'en',
91
'internal_button' => 'Login'
92
}
93
})
94
95
unless res&.code == 302 && res&.headers&.[]('Location') != normalize_uri(target_uri.path, 'login')
96
fail_with(Msf::Exploit::Failure::NoAccess, 'Failed to authenticate to pgAdmin')
97
end
98
99
print_good('Successfully authenticated to pgAdmin')
100
res
101
end
102
end
103
end
104
105