diff --git a/docs/gce/_update_dev.sh b/docs/gce/_update_dev.sh new file mode 100644 index 00000000..2c2befae --- /dev/null +++ b/docs/gce/_update_dev.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +set -e + +# Directory paths used for nogotofail. +INSTALL_DIR=/opt/nogotofail +CONFIG_DIR=/etc/nogotofail +LOG_DIR=/var/log/nogotofail + +# Stop the nogotofail-mitm and other associated services if they're running. +if (ps ax | grep -v grep | grep nogotofail-mitm > /dev/null) then +sudo /etc/init.d/nogotofail-mitm stop +fi +if (ps ax | grep -v grep | grep dnsmasq > /dev/null) then +sudo /etc/init.d/dnsmasq stop +fi +if (ps ax | grep -v grep | grep openvpn > /dev/null) then +sudo /etc/init.d/openvpn stop +fi +# Remove Python files and compiled versions i.e. *.py and *.pyc files. +# TODO: Find a more elegant method for uninstalling a Python program. +#rm -rf $INSTALL_DIR +#rm -rf $CONFIG_DIR +#rm -rf $LOG_DIR +find $INSTALL_DIR -type f -name '*.py' -delete +find $INSTALL_DIR -type f -name '*.pyc' -delete + +# Install toolchain dependencies +sudo apt-get update +sudo apt-get -y upgrade +#sudo apt-get -y install patch make gcc libssl-dev python-openssl liblzo2-dev libpam-dev + +# Install OpenVPN and dnsmasq +#sudo apt-get -y install openvpn dnsmasq + +# Build and install a patched version of OpenVPN. +# This is needed because the OpenVPN 2.3.x still does not properly handle +# floating clients (those whose source IP address as seen by the server changes +# from time to time) which is a regular occurrence in the mobile world. +# OpenVPN 2.4 might ship with proper support out of the box. In that case, this +# kludge can be removed. +#./build_openvpn.sh + +# Build and install a patched version of dnsmasq. +# This is needed because GCE does not support IPv6. We thus blackhole IPv6 +# traffic from clients so that they are forced to use IPv4. However, default +# DNS servers will still resolve hostnames to IPv6 addresses causing clients to +# attempt IPv6. To avoid clients attempting IPv6, we run a patched dnsmasq DNS +# server which empties AAAA records thus causing clients to go for A records +# which provide IPv4 addresses. +#./build_dnsmasq.sh + +# Set up OpenVPN server +#sudo ./setup_openvpn.sh + +# Set up the MiTM daemons +sudo ./setup_mitm.sh + +# Move dev mitm.conf file into /etc/nogotofail directory +sudo cp /home/michael/noseyp_setup/ngtf_mitm.conf /etc/nogotofail/mitm.conf + +# Restart all the relevant daemons +sudo /etc/init.d/dnsmasq start +sudo /etc/init.d/openvpn start +#sudo /etc/init.d/nogotofail-mitm stop || true +sudo /etc/init.d/nogotofail-mitm start diff --git a/nogotofail/mitm/connection/handlers/connection/__init__.py b/nogotofail/mitm/connection/handlers/connection/__init__.py index 63142b34..6310393b 100644 --- a/nogotofail/mitm/connection/handlers/connection/__init__.py +++ b/nogotofail/mitm/connection/handlers/connection/__init__.py @@ -26,4 +26,4 @@ from droptls import DropTLS from ccs import EarlyCCS from serverkeyreplace import ServerKeyReplacementMITM -from sunsetsha1 import SunsetSHA1 +# from sunsetsha1 import SunsetSHA1 diff --git a/nogotofail/mitm/connection/handlers/data/ssl.py b/nogotofail/mitm/connection/handlers/data/ssl.py index b3df9dcc..5848b634 100644 --- a/nogotofail/mitm/connection/handlers/data/ssl.py +++ b/nogotofail/mitm/connection/handlers/data/ssl.py @@ -19,7 +19,12 @@ from nogotofail.mitm.connection.handlers.data import DataHandler from nogotofail.mitm.connection.handlers.store import handler from nogotofail.mitm.event import connection +from nogotofail.mitm import util from nogotofail.mitm.util import ssl2, tls, vuln +from nogotofail.mitm.util.tls.types import HandshakeMessage, TlsRecord +from datetime import datetime +import OpenSSL.crypto + @handler.passive(handlers) class InsecureCipherDetectionHandler(DataHandler): @@ -90,3 +95,112 @@ def on_ssl(self, client_hello): self.log(logging.ERROR, "Client enabled SSLv3 protocol without TLS_FALLBACK_SCSV") self.log_attack_event(data="SSLv3") + + +@handler.passive(handlers) +class SunsetSHA1(DataHandler): + name = "sunsetsha1" + description = ( + "Detects TLS certificates using the SHA-1 signature algorithm that " + "expire during of after the Google Chrome sunset period.") + + buffer = "" + ssl = False + + def on_ssl(self, client_hello): + self.ssl = True + return True + + def on_response(self, response): + if not self.ssl: + return response + response = self.buffer + response + self.buffer = "" + CRT_DATE_FORMAT = "%Y%m%d%H%M%SZ" + try: + index = 0 + while index < len(response): + record, size = TlsRecord.from_stream(response[index:]) + for i, message in enumerate(record.messages): + # Check for Certificate message + if (isinstance(message, tls.types.HandshakeMessage) and + message.type == HandshakeMessage.TYPE.CERTIFICATE): + certificate = message.obj + # For each certificate in the certificate chain check + # the signature algorithm + count = 0 + for crt_byte_string in certificate.certificates: + a_certificate = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_ASN1, crt_byte_string) + crt_signature_algorithm = \ + a_certificate.get_signature_algorithm() + if ("sha1" in crt_signature_algorithm): + crt_subject = a_certificate.get_subject() + crt_CN = crt_subject.CN + crt_not_before = a_certificate.get_notBefore() + crt_not_after = a_certificate.get_notAfter() + debug_message = \ + ["Certicate using SHA-1 with attributes - CN \"", + self._str_trans(crt_CN), + "\", notBefore \"", self._str_trans(crt_not_before), + "\", notAfter \"", self._str_trans(crt_not_after), + "\", signature_algorithm \"", + crt_signature_algorithm, "\"", + " count=", self._str_trans(count)] + self.log(logging.DEBUG, "".join(debug_message)) + crt_not_after = datetime.strptime(crt_not_after, + CRT_DATE_FORMAT) + self._alert_on_sunset_sha1(crt_not_after, crt_CN) + count += 1 + index += size + except ValueError: + # Failed to parse TLS, this is probably due to a short read of a TLS + # record. Buffer the response to try and get more data. + self.buffer = response + # But don't buffer too much, give up after 16k. + if len(self.buffer) > 2**14: + response = self.buffer + self.buffer = "" + return self.buffer + return "" + return response + + def _alert_on_sunset_sha1(self, crt_not_after, crt_CN): + """ Raises an alert if a certificate signature algorithm uses SHA-1 and + it expires during or after the Google Chrome sunset period. + """ + # SHA-1 sunset dates based on Google Chrome dates published dates. See + # http://googleonlinesecurity.blogspot.com/2014/09/gradually-sunsetting-sha-1.html + sunset_critical_date = datetime.strptime("31-12-2016", "%d-%m-%Y") + sunset_error_date = datetime.strptime("30-06-2016", "%d-%m-%Y") + sunset_warning_date = datetime.strptime("31-12-2015", "%d-%m-%Y") + + if (crt_not_after > sunset_critical_date): + log_message = \ + ["Certificate with CN ", crt_CN, + " uses SHA-1 and expires after 31 Dec 2016"] + self.log(logging.CRITICAL, "".join(log_message)) + self.log_event(logging.CRITICAL, connection.AttackEvent( + self.connection, self.name, True, "")) + self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1) + elif (crt_not_after > sunset_error_date): + log_message = \ + ["Certificate with CN ", crt_CN, + " uses SHA-1 and expires after 30 Jun 2016"] + self.log(logging.ERROR, "".join(log_message)) + self.log_event(logging.ERROR, connection.AttackEvent( + self.connection, self.name, True, "")) + self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1) + elif (crt_not_after > sunset_warning_date): + log_message = \ + ["Certificate with CN ", crt_CN, + " uses SHA-1 and expires after 31 Dec 2015"] + self.log(logging.WARNING, "".join(log_message)) + self.log_event(logging.WARNING, connection.AttackEvent( + self.connection, self.name, True, "")) + self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1) + + def _str_trans(s): + """ Converts objects to strings, and "None" objects to empty strings. + """ + return '' if s is None else str(s)