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/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java b/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java index 68fe9d23..ad6d630c 100644 --- a/nogotofail/clients/android/app/src/main/java/net/nogotofail/AttacksPreferenceFragment.java +++ b/nogotofail/clients/android/app/src/main/java/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/clients/android/app/src/main/res/values/strings.xml b/nogotofail/clients/android/app/src/main/res/values/strings.xml index 11057cf8..67948c4f 100644 --- a/nogotofail/clients/android/app/src/main/res/values/strings.xml +++ b/nogotofail/clients/android/app/src/main/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/mitm/connection/handlers/data/ssl.py b/nogotofail/mitm/connection/handlers/data/ssl.py index 30e3a74e..20f0bb19 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 +from datetime import datetime +import OpenSSL.crypto + class _TlsRecordHandler(DataHandler): """Base class for a handler that acts on TlsRecords in a Tls connection. @@ -172,3 +177,110 @@ 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(_TlsRecordHandler): + name = "sunsetsha1" + description = ( + "Detects TLS certificates using the SHA-1 signature algorithm that " + "expire during of after the Google Chrome sunset period.") + + # Certificate type constants + LEAF_CERT = 1 + INTERMEDIATE_CERT = 2 + + def on_tls_response(self, record): + CRT_DATE_FORMAT = "%Y%m%d%H%M%SZ" + try: + 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 + crt_chain_length = len(certificate.certificates) + crt_chain_index = 0 + # Loop through certificate chain starting from the leaf + # cert to the last intermediate cert (before the root). + for crt_byte_string in certificate.certificates[ + :crt_chain_length-1]: + if (crt_chain_index == 0): + crt_type = self.LEAF_CERT + else: + crt_type = self.INTERMEDIATE_CERT + a_cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_ASN1, crt_byte_string) + # Check certificates in chain for SHA-1 sunset issue + crt_signature_algorithm = \ + a_cert.get_signature_algorithm() + if ("sha1" in crt_signature_algorithm): + crt_CN = a_cert.get_subject().CN + crt_not_before = a_cert.get_notBefore() + crt_not_after = a_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, + crt_type) + crt_chain_index += 1 + except AttributeError: + pass + return record.to_bytes() + + def _alert_on_sunset_sha1(self, crt_not_after, crt_CN, crt_type): + """ 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 + logging_level = logging.WARNING + CRT_DATE_FORMAT = "%d-%m-%Y" + sunset_critical_date = datetime.strptime("31-12-2016", CRT_DATE_FORMAT) + sunset_error_date = datetime.strptime("30-06-2016", CRT_DATE_FORMAT) + sunset_warning_date = datetime.strptime("31-12-2015", CRT_DATE_FORMAT) + + if (crt_type == self.LEAF_CERT): + crt_type_name = "Leaf" + elif (crt_type == self.INTERMEDIATE_CERT): + crt_type_name = "Intermediate" + + if (crt_not_after > sunset_critical_date): + if (crt_type == self.LEAF_CERT): + logging_level = logging.CRITICAL + elif (crt_type == self.INTERMEDIATE_CERT): + logging_level = logging.WARNING + log_message = \ + [crt_type_name, " certificate with CN ", crt_CN, + " uses SHA-1 and expires after 31 Dec 2016"] + self.log(logging_level, "".join(log_message)) + self.log_event(logging_level, connection.AttackEvent( + self.connection, self.name, True, "")) + self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1) + elif (crt_not_after > sunset_error_date): + if (crt_type == self.LEAF_CERT): + logging_level = logging.ERROR + elif (crt_type == self.INTERMEDIATE_CERT): + logging_level = logging.WARNING + log_message = \ + [crt_type_name, " certificate with CN ", crt_CN, + " uses SHA-1 and expires after 30 Jun 2016"] + self.log(logging_level, "".join(log_message)) + self.log_event(logging_level, connection.AttackEvent( + self.connection, self.name, True, "")) + self.connection.vuln_notify(util.vuln.VULN_SUNSET_SHA1) + elif (crt_not_after > sunset_warning_date): + logging_level = logging.WARNING + log_message = \ + [crt_type_name, " certificate with CN ", crt_CN, + " uses SHA-1 and expires after 31 Dec 2015"] + self.log(logging_level, "".join(log_message)) + self.log_event(logging_level, 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"