From acca25f8b2b1a72ccf592298edee210e3bdd77d0 Mon Sep 17 00:00:00 2001 From: yzninja Date: Fri, 24 Jul 2015 15:35:23 +0800 Subject: [PATCH] Added a new connection handler "sunsetsha1" to test for TLS certificates using the SHA-1 signature algorithm which expire during or after the Google Chrome SHA-1 sunset period. This handler was implemented as a passive data handler. --- docs/gce/_update_dev.sh | 66 +++++++++++ .../clients/android/res/values/strings.xml | 4 + .../nogotofail/AttacksPreferenceFragment.java | 1 + .../handlers/connection/__init__.py | 1 + .../mitm/connection/handlers/data/ssl.py | 104 ++++++++++++++++++ nogotofail/mitm/util/vuln.py | 1 + 6 files changed, 177 insertions(+) create mode 100644 docs/gce/_update_dev.sh 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/clients/android/res/values/strings.xml b/nogotofail/clients/android/res/values/strings.xml index 11057cf8..67948c4f 100644 --- a/nogotofail/clients/android/res/values/strings.xml +++ b/nogotofail/clients/android/res/values/strings.xml @@ -94,6 +94,8 @@ Downgrade of HTTPS to HTTP Downgrade of STARTTLS-protected XMPP to cleartext + + Certificate uses SHA-1 and expires after a Chrome sunset date Notifications Notifications @@ -159,6 +161,8 @@ XMPP credentials/auth token compromise XMPP STARTTLS strip Downgrade of STARTTLS-protected XMPP to cleartext + Certificate uses SHA-1 and expires after sunset date + Certificate uses SHA-1 and expires after a Chrome sunset date Advanced diff --git a/nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java b/nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java index 68fe9d23..ad6d630c 100644 --- a/nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java +++ b/nogotofail/clients/android/src/net/nogotofail/AttacksPreferenceFragment.java @@ -49,6 +49,7 @@ public class AttacksPreferenceFragment extends PreferenceFragment { BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("httpdetection"); BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("imagereplace"); BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("sslstrip"); + BUNDLED_SUPPORTED_DATA_ATTACK_IDS.add("sunsetsha1"); } private static final String ATTACK_ENABLED_PREF_KEY_PREFIX = "attack_enabled_"; diff --git a/nogotofail/mitm/connection/handlers/connection/__init__.py b/nogotofail/mitm/connection/handlers/connection/__init__.py index e52130a8..6310393b 100644 --- a/nogotofail/mitm/connection/handlers/connection/__init__.py +++ b/nogotofail/mitm/connection/handlers/connection/__init__.py @@ -26,3 +26,4 @@ from droptls import DropTLS from ccs import EarlyCCS from serverkeyreplace import ServerKeyReplacementMITM +# from sunsetsha1 import SunsetSHA1 diff --git a/nogotofail/mitm/connection/handlers/data/ssl.py b/nogotofail/mitm/connection/handlers/data/ssl.py index b3df9dcc..169dd977 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,102 @@ 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 + if certificate.certificates: + # Check leaf certificate in chain for SHA-1 sunset issue + cert_byte_string = certificate.certificates[0] + leaf_cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_ASN1, cert_byte_string) + crt_signature_algorithm = \ + leaf_cert.get_signature_algorithm() + if ("sha1" in crt_signature_algorithm): + crt_CN = leaf_cert.get_subject().CN + crt_not_before = leaf_cert.get_notBefore() + crt_not_after = leaf_cert.get_notAfter() + debug_message = \ + ["Certicate using SHA-1 with attributes - CN \"", + crt_CN, "\", notBefore \"", str(crt_not_before or ''), + "\", notAfter \"", str(crt_not_after or ''), + "\", signature_algorithm \"", + str(crt_signature_algorithm or ''), "\""] + 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) + 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) diff --git a/nogotofail/mitm/util/vuln.py b/nogotofail/mitm/util/vuln.py index 2de2f0f4..4be47a0a 100644 --- a/nogotofail/mitm/util/vuln.py +++ b/nogotofail/mitm/util/vuln.py @@ -30,3 +30,4 @@ VULN_WEAK_TLS_VERSION = "weaktlsversion" VULN_TLS_SERVER_KEY_REPLACEMENT = "serverkeyreplace" VULN_TLS_SUPERFISH_TRUSTED = "superfishca" +VULN_SUNSET_SHA1 = "sunsetsha1"