The last release was back in September and the changes that I've made to cryptonark seemed like it was enough to warrant a new version number update. This release of CryptoNark changes the default behavior of the tool so that when run, only the SSL-specific tests are run. A new option, -xl (or --kitchen-sink) will execute all the tests in full betrayal mode.
Another addition to the tool is that certificate validation is now enabled by default. There are now two files bundled in the downloadable archives, cnark.pl and ca-bundle.crt. OpenSSL does not provide any root certificates in a standard install but in order to enable certificate verification, root CA certificates were required. Thanks to the mod_ssl team, who, in turn, create their certificate bundle based off of the work provided by the Mozilla team, for providing a fairly comprehensive root certificate bundle. Now, cryptonark should exit gracefully in the event of an incomplete ssl certificate chain, an expired cert, a self-signed certificate, etc. If it does but you really, really want to run the tests anyway, a new option (--insecure) will disable certificate verification.
Finally, cryptonark now supports both short and long options. Use -h or --host for the hostname to be scanned, -p or --port for the port number, -i or --insecure to disable certificate verification, and -xl or --kitchen-sink to run the full set of tests. Downloads are on the Techstacks Downloads page. Thanks for checking it out!
#!/usr/bin/env perl
# Usage: ./cryptonark.pl host port
# based on sslthing.sh by blh [at] blh.se
# ported to perl by Chris Mahns - techstacks.com
#
# cryptonark:
# version 0.1 - Initial Version
#
# almost a direct port, this version also tests
# null and anonymous ssl ciphers and reports
# accordingly. A little more information is provided
# in the output. Works best if used to validate PCI-DSS
# compliance--to check that null, anonymous and weak ciphers
# are disabled.
#
# It probably will not run right "out of the box"--it requires
# IO::Socket::SSL. Tie::Hash::Indexed, although not strictly required
# is nice to have in order to order the hash lists from strongest to
# weakest. Otherwise, the order could be random making the results a
# bit harder to read.
#
# version 0.2 -
# + Added Color Coded output. Good ciphers are green, bad ones are red.
#
# version 0.2.1 -
# + Removed the SSLv3 Tests. (Actually, just commented
# them out for now.) SSLv3 and TLSv1 utilize the same ciphers so
# the tests are redundant.
# + Added a message after the display of cipher levels providing links to
# my site so that you can get information on disabling ciphers
#
# version 0.2.5 -
# + Added a HEAD request to display web server type. Uses some addition
# modules: LWP::UserAgent and HTTP::Headers
#
# version 0.3 -
# + "Upgraded" cnark to use perl 5.10 features. Perl 5.10
# is now required.
#
# + Modified script so that it uses command line options
# using Core Module Getopt::Long. --host and --port
# should now be used.
#
# + Removed exception catch on check_server_type() function.
# I don't think HEAD requests should ever fail but some
# sites restrict that method too for some reason.
#
# + cnark now tests for existence of HTTP Methods:
# TRACE and TRACK.
#
# + cnark will skip ssl scans if a non ssl port
# is used.
#
# version 0.3.1 -
# + Clean up
#
# + Switched to use Modern::Perl
#
# version 0.3.5
# + Now scans for commonly used "unsafe" URLs.
# More to come.
#
# version 0.3.6
#
# + Moved some stuff around.
#
# + Added SSLv3 tests back in. This functionality was
# useful to some folks who were looking for output
# from specific protocols.
#
# + Added ColdFusion Administrator to unsafe URL
# check
#
# + Added Tomcat Status URL to unsafe URL Check
#
# + Tweaked the output for the unsafe URL Check
# to reduce some redundancy and add a
# false positive disclaimer
#
# Should be able to get rid of the disclaimer
# when better false positive checks are added
#
# + Added some basic certificate parsing
#
# version 0.4
# + Added option -xl|--kitchen-sink By default, cnark.pl
# now performs ssl-only tests. The -xl|kitchen-sink will
# put cryptonark in full betrayal mode.
#
# + Added Certificate Peer Verification (enabled by default)
# This causes cryptonark to fail on expired certs, missing
# root certificates, self-signed certs, and (I hope)
# invalid certificate chains.
#
#
# Certificate Validation is due to the inclusion of
# the ca-bundle.crt CA certificate bundle from mod_ssl.
#
# + Added --insecure option to disable peer verification
# + Removed shamesless plug
#
use Modern::Perl;
use Term::ANSIColor qw(:constants);
use Tie::Hash::Indexed;
use IO::Socket::SSL;
# The following three modules provide support for
# displaying server type and TRACE and TRACK requests.
use LWP::UserAgent;
use HTTP::Headers;
use HTTP::Request;
# Core Module introducing command line options
use Getopt::Long;
my $version = "v0.4";
my ( $host, $port, $scheme );
my $insecure = '';
my $xl = '';
my $verifymode = 1;
my $useragent = "cryptonark-pci-auditor/" . $version;
sub usage{
say "Usage: cnark.pl -h|--host -p|--port \n\t\t[-i|--insecure] [-xl|--kitchen-sink]";
exit;
}
usage() if ( ! GetOptions("h|host=s" => \$host, "p|port=s" => \$port, "i|insecure" => \$insecure, "xl|kitchen-sink" => \$xl ) or ( ! defined $host) or ( ! defined $port) );
my $key;
my $value;
my $ssl2client;
my $ssl3client;
if ( $port eq 443) {
$scheme = 'https';
}
else {
$scheme = 'http';
}
if ($insecure eq 1 ) {
$verifymode = 0;
}
# Populate arrays with OpenSSL ciphers
# Note: TLSv1 ciphers and SSLv3 ciphers are
# identical in OpenSSL
tie my %ssl2_ciphers, 'Tie::Hash::Indexed';
tie my %tls1_ciphers, 'Tie::Hash::Indexed';
%ssl2_ciphers = (
'DES-CBC3-MD5' => '168 bits, High Encryption',
'RC2-CBC-MD5' => '128 bits, Medium Encryption',
'RC4-MD5' => '128 bits, Medium Encryption',
'DES-CBC-MD5' => '56 bits, Low Encryption',
'EXP-RC2-CBC-MD5' => '40 bits, Export-Grade Encryption',
'EXP-RC4-MD5' => '40 bits, Export-Grade Encryption'
);
%tls1_ciphers = (
'ADH-AES256-SHA' => '256 bits, High Encryption, Anonymous Auth',
'DHE-RSA-AES256-SHA' => '256 bits, High Encryption',
'DHE-DSS-AES256-SHA' => '256 bits, High Encryption',
'AES256-SHA' => '256 bits, High Encryption',
'ADH-DES-CBC3-SHA' => '168 bits, High Encryption, Anonymous Auth',
'EDH-RSA-DES-CBC3-SHA' => '168 bits, High Encryption',
'EDH-DSS-DES-CBC3-SHA' => '168 bits, High Encryption',
'DES-CBC3-SHA' => '168 bits, High Encryption',
'ADH-AES128-SHA' => '128 bits, High Encryption, Anonymous Auth',
'DHE-RSA-AES128-SHA' => '128 bits, High Encryption',
'DHE-DSS-AES128-SHA' => '128 bits, High Encryption',
'AES128-SHA' => '128 bits, High Encryption',
'RC4-SHA' => '128 bits, Medium Encryption',
'RC4-MD5' => '128 bits, Medium Encryption',
'ADH-RC4-MD5' => '128 bits, Medium Encryption, Anonymous Auth',
'EDH-RSA-DES-CBC-SHA' => '56 bits, Low Encryption',
'EDH-DSS-DES-CBC-SHA' => '56 bits, Low Encryption',
'DES-CBC-SHA' => '56 bits, Low Encryption',
'ADH-DES-CBC-SHA' => '56 bits, Low Encryption, Anonymous Auth',
'EXP-ADH-DES-CBC-SHA' => '40 bits, Export-Grade Encryption',
'EXP-ADH-RC4-MD5' => '40 bits, Export-Grade Encryption',
'EXP-EDH-RSA-DES-CBC-SHA' => '40 bits, Export-Grade Encryption',
'EXP-EDH-DSS-DES-CBC-SHA' => '40 bits, Export-Grade Encryption',
'EXP-DES-CBC-SHA' => '40 bits, Export-Grade Encryption',
'EXP-RC2-CBC-MD5' => '40 bits, Export-Grade Encryption',
'EXP-RC4-MD5' => '40 bits, Export-Grade Encryption',
'NULL-SHA' => 'Null cipher, No Encryption',
'NULL-MD5' => 'Null cipher, No Encryption'
);
# Populate a hash of unsafe URLs
# i.e. URLs you would not want exposed
# to the Internet
tie my %bad_urls, 'Tie::Hash::Indexed';
%bad_urls = (
'Apache mod_status page' => 'server-status',
'Apache mod_info page' => 'server-info',
'Apache mod_jk status page' => 'jk-status',
'Apache mod_proxy_balancer' => 'balancer-manager',
'ColdFusion Administrator' => 'CFIDE/administrator/index.cfm',
'IIS Samples' => 'IISsamples',
'IIS Scripts' => 'Scripts',
'IIS MSADC Directory' => 'MSADC',
'IIS Help' => 'IISHelp',
'IIS Admin' => 'IISAdmin',
'Tomcat Manager' => 'manager/html',
'Tomcat Status Page' => 'manager/status',
'Tomcat JSP Examples' => 'jsp-examples/index.html',
'Tomcat Servlet Examples' => 'servlets-examples/index.html',
'JBoss JMX Console' => 'jmx-console',
'JBoss Tomcat Status Page' => 'status',
'JBoss Web Console' => 'web-console',
'JBoss 5.x Admin Console' => 'admin-console',
);
sub get_server_type{
my $url = "$scheme://$host/";
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->agent( $useragent );
my $header = $ua->head( $url );
print "\nWeb Server Type: " . $header->server . "\n\n";
}
sub test_for_trace {
my $url = "$scheme://$host/";
my $method = "TRACE";
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->agent( $useragent );
my $request = HTTP::Request->new( $method => $url );
$request->header(Header0 => "TRACE");
$request->header(Header1 => "Test");
my $response = $ua->request($request);
given ( $response->code ) {
when (200) {
say "======this is what you sent======";
say $response->content;
say "=================================";
say $method, " is enabled and working.";
}
when (301) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (302) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (307) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (403) {
say $response->status_line;
say $method, " is forbidden.";
}
when (404) {
say $response->status_line;
say "This is an unexpected response";
}
when (405) {
say $response->status_line;
say $method, " is not permitted.";
}
when (501) {
say $response->status_line;
say $method, " is not implemented.";
}
default {
say $response->status_line;
}
}
}
sub test_for_track {
my $test = 'This is an HTTP TRACE test.';
my $url = "$scheme://$host/";
my $method = "TRACK";
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->agent( $useragent );
my $request = HTTP::Request->new( $method => $url );
$request->header(Header0 => "TRACK");
$request->header(Header1 => "Test");
my $response = $ua->request($request);
given ( $response->code ) {
when (200) {
say "======this is what you sent======";
say $response->content;
say "=================================";
say $method, " is enabled and working.";
}
when (301) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (302) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (307) {
say "Redirect present. Retry request against ",
$response->header('Location');
}
when (403) {
say $response->status_line;
say $method, " is forbidden.";
}
when (404) {
say $response->status_line;
say "This is an unexpected response";
}
when (405) {
say $response->status_line;
say $method, " is not permitted.";
}
when (501) {
say $response->status_line;
say $method, " is not implemented.";
}
default {
say $response->status_line;
}
}
}
sub scan_for_unsafe_urls{
print "\n";
say "Scanning for 'Unsafe' URLs...";
say "NOTE: This isn't perfect yet.\nSuccesses could be false positives\nMany are caused by redirection";
sleep 2;
my $response;
my $request;
while (($key,$value) = each(%bad_urls)) {
my $url = "$scheme://$host/$value";
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
$ua->env_proxy;
$ua->agent( $useragent );
my $request = HTTP::Request->new( GET => $url );
my $response = $ua->request($request);
if ($response->code == '200') {
print RED, " " . $key . " -- /". $value . " url found" . "\n", RESET;
}
else {
print GREEN, " " . $key . " -- /" . $value . " url not found." . "\n", RESET;
}
}
}
sub shameless_plug{
print "\n";
print "For tips on PCI Remediation, visit my blog at: \n";
print " http://blog.techstacks.com/pci-compliance/\n";
print "Thanks for using cnark!\n";
}
sub is_weak{
if ($key =~ /^EXP-|^NULL|^ADH-|DES-CBC-/) {
print RED, " " . $key . " -- " . $value . "\n", RESET;
}
else {
print GREEN, " " . $key . " -- " . $value . "\n", RESET;
}
}
# Basic certificate checking
sub cert_info{
print "\nSSL Certificate Information...\n";
sleep 2;
my $certclient = IO::Socket::SSL->new(
SSL_ca_file => 'ca-bundle.crt',
SSL_verify_mode => $verifymode,
SSL_version => 'TLSv1',
SSL_cipher_list => 'RC4-MD5',
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
) || die("Certificate Peer Verification Failed.\nYou can try running with the --insecure switch\n\n");
my $cn = $certclient->peer_certificate("cn");
say "Certificate Commmon Name: " . $cn . "\n";
}
sub test_ssl2_ciphers{
print "\nTesting SSLv2 Ciphers...\n";
sleep 2;
while (($key,$value) = each(%ssl2_ciphers)) {
my $ssl2client = IO::Socket::SSL->new(
SSL_verify_mode => 0,
SSL_version => 'SSLv2',
SSL_cipher_list => $key,
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
)
&& is_weak();
}
}
sub test_ssl3_ciphers{
print "\nTesting SSLv3 Ciphers...\n";
sleep 2;
while (($key,$value) = each(%tls1_ciphers)) {
my $ssl3client = IO::Socket::SSL->new(
SSL_verify_mode => 0,
SSL_version => 'SSLv3',
SSL_cipher_list => $key,
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
)
&& is_weak();
}
}
sub test_tls_ciphers{
print "\nTesting TLSv1 Ciphers...\n";
sleep 2;
while (($key,$value) = each(%tls1_ciphers)) {
my $tls1client = IO::Socket::SSL->new(
SSL_verify_mode => 0,
SSL_version => 'TLSv1',
SSL_cipher_list => $key,
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
)
&& is_weak();
}
}
if ($xl eq 1 ) {
# Run through the subroutines
get_server_type();
say "Testing for HTTP TRACE...";
sleep 2;
test_for_trace();
say "\nTesting for HTTP TRACK...";
sleep 2;
test_for_track();
scan_for_unsafe_urls();
if ($port eq 443) {
cert_info();
test_ssl2_ciphers();
test_ssl3_ciphers();
test_tls_ciphers();
}
#shameless_plug();
}
else {
cert_info();
test_ssl2_ciphers();
test_ssl3_ciphers();
test_tls_ciphers();
}