1"""
2Looks for certificates in SSL/TLS traffic and tries to find any hashes that
3match those in the abuse.ch blacklist.
4(https://sslbl.abuse.ch/blacklist/)
5"""
6
7# handy reference:
8# http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session
9
10import dshell.core
11from dshell.output.alertout import AlertOutput
12
13import hashlib
14import io
15import struct
16
17# SSLv3/TLS version
18SSL3_VERSION = 0x0300
19TLS1_VERSION = 0x0301
20TLS1_1_VERSION = 0x0302
21TLS1_2_VERSION = 0x0303
22
23# Record type
24SSL3_RT_CHANGE_CIPHER_SPEC = 20
25SSL3_RT_ALERT             = 21
26SSL3_RT_HANDSHAKE         = 22
27SSL3_RT_APPLICATION_DATA  = 23
28
29# Handshake message type
30SSL3_MT_HELLO_REQUEST           = 0
31SSL3_MT_CLIENT_HELLO            = 1
32SSL3_MT_SERVER_HELLO            = 2
33SSL3_MT_CERTIFICATE             = 11
34SSL3_MT_SERVER_KEY_EXCHANGE     = 12
35SSL3_MT_CERTIFICATE_REQUEST     = 13
36SSL3_MT_SERVER_DONE             = 14
37SSL3_MT_CERTIFICATE_VERIFY      = 15
38SSL3_MT_CLIENT_KEY_EXCHANGE     = 16
39SSL3_MT_FINISHED                = 20
40
41
42class DshellPlugin(dshell.core.ConnectionPlugin):
43
44    def __init__(self):
45        super().__init__(
46            name="sslblacklist",
47            author="dev195",
48            bpf="tcp and (port 443 or port 993 or port 1443 or port 8531)",
49            description="Looks for certificate SHA1 matches in the abuse.ch blacklist",
50            longdescription="""
51    Looks for certificates in SSL/TLS traffic and tries to find any hashes that
52    match those in the abuse.ch blacklist.
53
54    Requires downloading the blacklist CSV from abuse.ch:
55    https://sslbl.abuse.ch/blacklist/
56
57    If the CSV is not in the current directory, use the --sslblacklist_csv
58    argument to provide a file path.
59""",
60            output=AlertOutput(label=__name__),
61            optiondict={
62                "csv": {
63                    "help": "filepath to the sslblacklist.csv file",
64                    "default": "./sslblacklist.csv",
65                    "metavar": "FILEPATH"
66                },
67            }
68        )
69
70    def premodule(self):
71        self.parse_blacklist_csv(self.csv)
72
73    def parse_blacklist_csv(self, filepath):
74        "parses the SSL blacklist CSV, given the 'filepath'"
75        # Python's standard csv module doesn't seem to handle it properly
76        self.hashes = {}
77        with open(filepath, 'r') as csv:
78            for line in csv:
79                line = line.split('#')[0]  # ignore comments
80                line = line.strip()
81                try:
82                    timestamp, sha1, reason = line.split(',', 3)
83                    self.hashes[sha1] = reason
84                except ValueError:
85                    continue
86
87    def blob_handler(self, conn, blob):
88        if blob.direction == 'cs':
89            return None
90
91        data = io.BytesIO(blob.data)
92
93        # Iterate over each layer of the connection, paying special attention to the certificate
94        while True:
95            try:
96                content_type, proto_version, record_len = struct.unpack("!BHH", data.read(5))
97            except struct.error:
98                break
99            if proto_version not in (SSL3_VERSION, TLS1_VERSION, TLS1_1_VERSION, TLS1_2_VERSION):
100                return None
101            if content_type == SSL3_RT_HANDSHAKE:
102                handshake_type = struct.unpack("!B", data.read(1))[0]
103                handshake_len = struct.unpack("!I", b"\x00"+data.read(3))[0]
104                if handshake_type == SSL3_MT_CERTIFICATE:
105                    # Process the certificate itself
106                    cert_chain_len = struct.unpack("!I", b"\x00"+data.read(3))[0]
107                    bytes_processed = 0
108                    while (bytes_processed < cert_chain_len):
109                        try:
110                            cert_data_len = struct.unpack("!I", b"\x00"+data.read(3))[0]
111                            cert_data = data.read(cert_data_len)
112                            bytes_processed = 3 + cert_data_len
113                            sha1 = hashlib.sha1(cert_data).hexdigest()
114                            if sha1 in self.hashes:
115                                bad_guy = self.hashes[sha1]
116                                self.write("Certificate hash match: {}".format(bad_guy), **conn.info())
117                        except struct.error as e:
118                            break
119                else:
120                    # Ignore any layers that are not a certificate
121                    data.read(handshake_len)
122                    continue
123
124        return conn, blob
125