This is just a small release, only adding a couple of new things and I cleaned up the output a little more. This version add two additional URIs to the unsafe URL check: a check for the ColdFusion administrator URL and a check for the tomcat status page. I also added a small disclaimer to the output of the unsafe URL check because successes could be false positives--especially requests that result in 301 or 302 responses.
For people new to the site, CryptoNark is a perl-based PCI compliance remediation validation checker. It's primary purpose is to allow an administrator to check to see whether configuration changes have been successfully implemented when fixing PCI Compliance related vulnerabilities.
I added the SSLv3 tests back in. Although SSLv3 and TLSv1 in openssl use the same cipher list, some TLSv1 ciphers won't work over an SSLv3 connection.
The only other encryption-related thing added is I'm reading the common name of the cert returned when connections are made over a secured channel. I'm shooting to add some more interesting stuff in a version 0.4 release coming "soon".
The main CryptoNark page has been updated and you can download the script from the downloads page.
#!/usr/bin/env perl
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 = "0.3.6";
my ( $host, $port, $scheme );
my $useragent = "cryptonark-pci-auditor/v" . $version;
sub usage{
say "Usage: cnark.pl [--host HOSTNAME --port PORT]";
exit;
}
usage() if ( ! GetOptions("host=s" => \$host, "port=s" => \$port,) or ( ! defined $host) or ( ! defined $port) );
my $key;
my $value;
my $ssl2client;
my $ssl3client;
if ( $port eq 443) {
$scheme = 'https';
}
else {
$scheme = 'http';
}
# 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: Successes could be false positives";
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_verify_mod => 0,
SSL_version => 'TLSv1',
SSL_cipher_list => 'RC4-MD5',
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
Timeout => '5'
);
my $cn = $certclient->peer_certificate("cn");
say "Certificate Commmon Name: " . $cn;
}
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 $ssl2client = 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 "Testing 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();
}
}
# 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();