Merge remote-tracking branch 'origin/develop' into feature/refactor-monkey-island
This commit is contained in:
commit
243a255833
|
@ -1,13 +1,14 @@
|
|||
import os
|
||||
import sys
|
||||
from network.range import FixedRange, RelativeRange, ClassCRange
|
||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\
|
||||
SambaCryExploiter
|
||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
|
||||
import types
|
||||
import uuid
|
||||
from abc import ABCMeta
|
||||
from itertools import product
|
||||
import uuid
|
||||
import types
|
||||
|
||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
|
||||
SambaCryExploiter
|
||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
|
||||
from network.range import FixedRange
|
||||
|
||||
__author__ = 'itamar'
|
||||
|
||||
|
@ -15,6 +16,7 @@ GUID = str(uuid.getnode())
|
|||
|
||||
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
||||
|
||||
|
||||
def _cast_by_example(value, example):
|
||||
"""
|
||||
a method that casts a value to the type of the parameter given as example
|
||||
|
@ -140,9 +142,9 @@ class Configuration(object):
|
|||
max_iterations = 1
|
||||
|
||||
scanner_class = TcpScanner
|
||||
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger]
|
||||
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger]
|
||||
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
|
||||
SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux
|
||||
SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux
|
||||
]
|
||||
|
||||
# how many victims to look for in a single scan iteration
|
||||
|
@ -178,7 +180,7 @@ class Configuration(object):
|
|||
|
||||
range_class = FixedRange
|
||||
range_size = 1
|
||||
range_fixed = ['',]
|
||||
range_fixed = ['', ]
|
||||
|
||||
blocked_ips = ['', ]
|
||||
|
||||
|
@ -186,7 +188,17 @@ class Configuration(object):
|
|||
HTTP_PORTS = [80, 8080, 443,
|
||||
8008, # HTTP alternate
|
||||
]
|
||||
tcp_target_ports = [22, 2222, 445, 135, 3389]
|
||||
tcp_target_ports = [22,
|
||||
2222,
|
||||
445,
|
||||
135,
|
||||
3389,
|
||||
80,
|
||||
8080,
|
||||
443,
|
||||
8008,
|
||||
3306,
|
||||
9200]
|
||||
tcp_target_ports.extend(HTTP_PORTS)
|
||||
tcp_scan_timeout = 3000 # 3000 Milliseconds
|
||||
tcp_scan_interval = 200
|
||||
|
@ -211,13 +223,17 @@ class Configuration(object):
|
|||
# User and password dictionaries for exploits.
|
||||
|
||||
def get_exploit_user_password_pairs(self):
|
||||
"""
|
||||
Returns all combinations of the configurations users and passwords
|
||||
:return:
|
||||
"""
|
||||
return product(self.exploit_user_list, self.exploit_password_list)
|
||||
|
||||
exploit_user_list = ['Administrator', 'root', 'user']
|
||||
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
|
||||
|
||||
# smb/wmi exploiter
|
||||
smb_download_timeout = 300 # timeout in seconds
|
||||
smb_download_timeout = 300 # timeout in seconds
|
||||
smb_service_name = "InfectionMonkey"
|
||||
|
||||
# Timeout (in seconds) for sambacry's trigger to yield results.
|
||||
|
@ -243,7 +259,6 @@ class Configuration(object):
|
|||
# Monkey copy filename on share (64 bit)
|
||||
sambacry_monkey_copy_filename_64 = "monkey64_2"
|
||||
|
||||
|
||||
# system info collection
|
||||
collect_system_info = True
|
||||
|
||||
|
@ -253,4 +268,5 @@ class Configuration(object):
|
|||
|
||||
mimikatz_dll_name = "mk.dll"
|
||||
|
||||
|
||||
WormConfiguration = Configuration()
|
||||
|
|
|
@ -39,7 +39,9 @@
|
|||
"SSHFinger",
|
||||
"PingScanner",
|
||||
"HTTPFinger",
|
||||
"SMBFinger"
|
||||
"SMBFinger",
|
||||
"MySQLFinger"
|
||||
"ElasticFinger",
|
||||
],
|
||||
"max_iterations": 3,
|
||||
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
|
||||
|
@ -83,7 +85,9 @@
|
|||
80,
|
||||
8080,
|
||||
443,
|
||||
8008
|
||||
3306,
|
||||
8008,
|
||||
9200
|
||||
],
|
||||
"timeout_between_iterations": 10,
|
||||
"use_file_logging": true,
|
||||
|
|
|
@ -23,5 +23,7 @@ from tcp_scanner import TcpScanner
|
|||
from smbfinger import SMBFinger
|
||||
from sshfinger import SSHFinger
|
||||
from httpfinger import HTTPFinger
|
||||
from elasticfinger import ElasticFinger
|
||||
from mysqlfinger import MySQLFinger
|
||||
from info import local_ips
|
||||
from info import get_free_tcp_port
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import json
|
||||
import logging
|
||||
from contextlib import closing
|
||||
|
||||
import requests
|
||||
from requests.exceptions import Timeout, ConnectionError
|
||||
|
||||
from model.host import VictimHost
|
||||
from network import HostFinger
|
||||
|
||||
ES_PORT = 9200
|
||||
ES_SERVICE = 'elastic-search-9200'
|
||||
ES_HTTP_TIMEOUT = 5
|
||||
LOG = logging.getLogger(__name__)
|
||||
__author__ = 'danielg'
|
||||
|
||||
|
||||
class ElasticFinger(HostFinger):
|
||||
"""
|
||||
Fingerprints elastic search clusters, only on port 9200
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
def get_host_fingerprint(self, host):
|
||||
"""
|
||||
Returns elasticsearch metadata
|
||||
:param host:
|
||||
:return: Success/failure, data is saved in the host struct
|
||||
"""
|
||||
assert isinstance(host, VictimHost)
|
||||
try:
|
||||
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
|
||||
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
|
||||
data = json.loads(req.text)
|
||||
host.services[ES_SERVICE] = {}
|
||||
host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
|
||||
host.services[ES_SERVICE]['name'] = data['name']
|
||||
host.services[ES_SERVICE]['version'] = data['version']['number']
|
||||
return True
|
||||
except Timeout:
|
||||
LOG.debug("Got timeout while trying to read header information")
|
||||
except ConnectionError: # Someone doesn't like us
|
||||
LOG.debug("Unknown connection error")
|
||||
except KeyError:
|
||||
LOG.debug("Failed parsing the ElasticSearch JSOn response")
|
||||
return False
|
|
@ -0,0 +1,85 @@
|
|||
import logging
|
||||
import socket
|
||||
|
||||
from model.host import VictimHost
|
||||
from network import HostFinger
|
||||
from .tools import struct_unpack_tracker, struct_unpack_tracker_string
|
||||
|
||||
MYSQL_PORT = 3306
|
||||
SQL_SERVICE = 'mysqld-3306'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MySQLFinger(HostFinger):
|
||||
"""
|
||||
Fingerprints mysql databases, only on port 3306
|
||||
"""
|
||||
|
||||
SOCKET_TIMEOUT = 0.5
|
||||
HEADER_SIZE = 4 # in bytes
|
||||
|
||||
def __init__(self):
|
||||
self._config = __import__('config').WormConfiguration
|
||||
|
||||
def get_host_fingerprint(self, host):
|
||||
"""
|
||||
Returns mySQLd data using the host header
|
||||
:param host:
|
||||
:return: Success/failure, data is saved in the host struct
|
||||
"""
|
||||
assert isinstance(host, VictimHost)
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(self.SOCKET_TIMEOUT)
|
||||
|
||||
try:
|
||||
s.connect((host.ip_addr, MYSQL_PORT))
|
||||
header = s.recv(self.HEADER_SIZE) # max header size?
|
||||
|
||||
response, curpos = struct_unpack_tracker(header, 0, "I")
|
||||
response = response[0]
|
||||
response_length = response & 0xff # first byte is significant
|
||||
data = s.recv(response_length)
|
||||
# now we can start parsing
|
||||
protocol, curpos = struct_unpack_tracker(data, 0, "B")
|
||||
protocol = protocol[0]
|
||||
|
||||
if protocol == 0xFF:
|
||||
# error code, bug out
|
||||
LOG.debug("Mysql server returned error")
|
||||
return False
|
||||
|
||||
version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
|
||||
version = version[0]
|
||||
host.services[SQL_SERVICE] = {}
|
||||
host.services[SQL_SERVICE]['version'] = version
|
||||
version = version.split('-')[0].split('.')
|
||||
host.services[SQL_SERVICE]['major_version'] = version[0]
|
||||
host.services[SQL_SERVICE]['minor_version'] = version[1]
|
||||
host.services[SQL_SERVICE]['build_version'] = version[2]
|
||||
thread_id, curpos = struct_unpack_tracker(data, curpos, "<I") # ignore thread id
|
||||
|
||||
# protocol parsing taken from
|
||||
# https://nmap.org/nsedoc/scripts/mysql-info.html
|
||||
if protocol == 10:
|
||||
# new protocol
|
||||
self._parse_protocol_10(curpos, data, host)
|
||||
return True
|
||||
if protocol == 9:
|
||||
return True
|
||||
s.close()
|
||||
|
||||
except Exception as err:
|
||||
LOG.debug("Error getting mysql fingerprint: %s", err)
|
||||
|
||||
return False
|
||||
|
||||
def _parse_protocol_10(self, curpos, data, host):
|
||||
salt, curpos = struct_unpack_tracker(data, curpos, "s8B")
|
||||
capabilities, curpos = struct_unpack_tracker(data, curpos, "<H")
|
||||
host.services[SQL_SERVICE]['capabilities'] = capabilities[0]
|
||||
charset, curpos = struct_unpack_tracker(data, curpos, "B")
|
||||
status, curpos = struct_unpack_tracker(data, curpos, "<H")
|
||||
extcapabilities, curpos = struct_unpack_tracker(data, curpos, "<H")
|
||||
host.services[SQL_SERVICE]['extcapabilities'] = extcapabilities[0]
|
||||
# there's more data but it doesn't matter
|
|
@ -1,6 +1,7 @@
|
|||
import socket
|
||||
import select
|
||||
import logging
|
||||
import struct
|
||||
|
||||
DEFAULT_TIMEOUT = 10
|
||||
BANNER_READ = 1024
|
||||
|
@ -8,6 +9,32 @@ BANNER_READ = 1024
|
|||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def struct_unpack_tracker(data, index, fmt):
|
||||
"""
|
||||
Unpacks a struct from the specified index according to specified format.
|
||||
Returns the data and the next index
|
||||
:param data: Buffer
|
||||
:param index: Position index
|
||||
:param fmt: Struct format
|
||||
:return: (Data, new index)
|
||||
"""
|
||||
unpacked = struct.unpack_from(fmt, data, index)
|
||||
return unpacked, struct.calcsize(fmt)
|
||||
|
||||
|
||||
def struct_unpack_tracker_string(data, index):
|
||||
"""
|
||||
Unpacks a null terminated string from the specified index
|
||||
Returns the data and the next index
|
||||
:param data: Buffer
|
||||
:param index: Position index
|
||||
:return: (Data, new index)
|
||||
"""
|
||||
ascii_len = data[index:].find('\0')
|
||||
fmt = "%ds" % ascii_len
|
||||
return struct_unpack_tracker(data,index,fmt)
|
||||
|
||||
|
||||
def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import sys
|
||||
import socket
|
||||
import sys
|
||||
|
||||
import psutil
|
||||
from enum import IntEnum
|
||||
from network.info import get_host_subnets, local_ips
|
||||
|
||||
from network.info import get_host_subnets
|
||||
|
||||
__author__ = 'uri'
|
||||
|
||||
|
@ -68,7 +70,7 @@ class InfoCollector(object):
|
|||
"cmdline": " ".join(process.cmdline()),
|
||||
"full_image_path": process.exe(),
|
||||
}
|
||||
except psutil.AccessDenied:
|
||||
except (psutil.AccessDenied, WindowsError):
|
||||
# we may be running as non root
|
||||
# and some processes are impossible to acquire in Windows/Linux
|
||||
# in this case we'll just add what we can
|
||||
|
@ -78,7 +80,8 @@ class InfoCollector(object):
|
|||
"cmdline": "ACCESS DENIED",
|
||||
"full_image_path": "null",
|
||||
}
|
||||
pass
|
||||
continue
|
||||
|
||||
self.info['process_list'] = processes
|
||||
|
||||
def get_network_info(self):
|
||||
|
|
Loading…
Reference in New Issue