Add mysql fingerprinting and improve struct parsing

This commit is contained in:
Daniel Goldberg 2017-09-25 12:01:48 +03:00
parent cb7bb56588
commit 10c9648854
5 changed files with 111 additions and 3 deletions

View File

@ -3,7 +3,7 @@ import sys
from network.range import FixedRange, RelativeRange, ClassCRange 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 SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger
from abc import ABCMeta from abc import ABCMeta
from itertools import product from itertools import product
import uuid import uuid
@ -140,7 +140,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
] ]

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",

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,78 @@
import socket
import logging
from network import HostFinger
from .tools import struct_unpack_tracker, struct_unpack_tracker_string
from model.host import VictimHost
MYSQL_PORT = 3306
SQL_SERVICE = 'mysqld-3306'
LOG = logging.getLogger(__name__)
class MySQLFinger(HostFinger):
"""
Fingerprints mysql databases, only on port 3306
"""
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(0.5)
try:
s.connect((host.ip_addr, MYSQL_PORT))
header = s.recv(4) # max header size?
tmp, curpos = struct_unpack_tracker(header, 0, "I")
tmp = tmp[0]
response_length = tmp & 0xff
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]['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
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,33 @@ 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
unpacked = struct.unpack_from(fmt, data, index)
return unpacked, struct.calcsize(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)