Merge pull request #47 from guardicore/feature/mysql-finger

Feature/mysql fingerprint
This commit is contained in:
itaymmguardicore 2017-09-25 18:09:44 +03:00 committed by GitHub
commit da0abc6a64
5 changed files with 129 additions and 12 deletions

View File

@ -1,13 +1,14 @@
import os import os
import sys import sys
from network.range import FixedRange, RelativeRange, ClassCRange import types
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\ import uuid
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
from abc import ABCMeta from abc import ABCMeta
from itertools import product 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
from network.range import FixedRange
__author__ = 'itamar' __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') EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
def _cast_by_example(value, example): def _cast_by_example(value, example):
""" """
a method that casts a value to the type of the parameter given as example a method that casts a value to the type of the parameter given as example
@ -140,7 +142,7 @@ class Configuration(object):
max_iterations = 1 max_iterations = 1
scanner_class = TcpScanner scanner_class = TcpScanner
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger] finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux
] ]
@ -178,7 +180,7 @@ class Configuration(object):
range_class = FixedRange range_class = FixedRange
range_size = 1 range_size = 1
range_fixed = ['',] range_fixed = ['', ]
blocked_ips = ['', ] blocked_ips = ['', ]
@ -186,7 +188,7 @@ class Configuration(object):
HTTP_PORTS = [80, 8080, 443, HTTP_PORTS = [80, 8080, 443,
8008, # HTTP alternate 8008, # HTTP alternate
] ]
tcp_target_ports = [22, 2222, 445, 135, 3389] tcp_target_ports = [22, 2222, 445, 135, 3389, 3306, ]
tcp_target_ports.extend(HTTP_PORTS) tcp_target_ports.extend(HTTP_PORTS)
tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_timeout = 3000 # 3000 Milliseconds
tcp_scan_interval = 200 tcp_scan_interval = 200
@ -243,7 +245,6 @@ class Configuration(object):
# Monkey copy filename on share (64 bit) # Monkey copy filename on share (64 bit)
sambacry_monkey_copy_filename_64 = "monkey64_2" sambacry_monkey_copy_filename_64 = "monkey64_2"
# system info collection # system info collection
collect_system_info = True collect_system_info = True
@ -253,4 +254,5 @@ class Configuration(object):
mimikatz_dll_name = "mk.dll" mimikatz_dll_name = "mk.dll"
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -39,7 +39,8 @@
"SSHFinger", "SSHFinger",
"PingScanner", "PingScanner",
"HTTPFinger", "HTTPFinger",
"SMBFinger" "SMBFinger",
"MySQLFinger"
], ],
"max_iterations": 3, "max_iterations": 3,
"monkey_log_path_windows": "%temp%\\~df1563.tmp", "monkey_log_path_windows": "%temp%\\~df1563.tmp",
@ -83,6 +84,7 @@
80, 80,
8080, 8080,
443, 443,
3306,
8008 8008
], ],
"timeout_between_iterations": 10, "timeout_between_iterations": 10,

View File

@ -23,5 +23,6 @@ from tcp_scanner import TcpScanner
from smbfinger import SMBFinger from smbfinger import SMBFinger
from sshfinger import SSHFinger from sshfinger import SSHFinger
from httpfinger import HTTPFinger from httpfinger import HTTPFinger
from mysqlfinger import MySQLFinger
from info import local_ips from info import local_ips
from info import get_free_tcp_port from info import get_free_tcp_port

View File

@ -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

View File

@ -1,6 +1,7 @@
import socket import socket
import select import select
import logging import logging
import struct
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
BANNER_READ = 1024 BANNER_READ = 1024
@ -8,6 +9,32 @@ BANNER_READ = 1024
LOG = logging.getLogger(__name__) 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): def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout) sock.settimeout(timeout)