« October 2009 | Main | December 2009 »

6 posts from November 2009

11/21/2009

New Release: CryptoNark v0.3 - Less Crypto, More Nark!

CryptoNark 0.4.1 is the most recent version. See the Release Announcement for more details.

OK, not really with less crypto but this is the first release where cryptonark becomes a little something more than a script that will tell you what SSL ciphers are enabled on a site.  Some common vulnerabilities that innocent web masters like myself get hit with contain the rather ominous titles like "SSL Server Allows Anonymous Authentication Vulnerability", "SSL Server Has SSLv2 Enabled Vulnerability" or "SSL Sever Supports Weak Encryption Vulnerability".  CryptoNark has been reporting cipher strength levels allowable on a site for some time now, which has allowed administrators to verify that PCI remediation changes have been effectively implemented.  

Another popular vulnerability that crops up from time to time is "Web Server HTTP Trace/Track Method Support Cross-Site Tracing Vulnerability".  I have written several HOWTO documents on disabling TRACE and TRACK on various platforms so I will not rehash what needs to be done to address this particular item here but cryptonark now tests a site to see if TRACE and/or TRACK are enabled on a site.  

Other changes to the script is use of Perl 5.10-specific functions, so Perl 5.10 or higher is now required. 

CryptoNark now uses command line arguments, taking advantage of the built in core module, Getopt::Long.  Right now, there are only two (--host and --port) and both are required.  SSL Cipher scanning will only occur if the port is set to 443.  Future changes might include a --notrace or --nocipher option to disable certain checks. 

Finally, and much to my surprise, I learned recently that some admins disable HEAD requests on their sites.  Therefore, I've modified the script to error out a little more gracefully in the even this condition is met. 

In future versions I will be expanding the types of tests performed.  I may also be including additional ciphers since more TLS-specific ciphers have been included in later openssl distributions, (which means the SSL3 tests may be coming back).  Grab the latest version from the download page!

#!/usr/bin/env perl
use strict;
use warnings;
use feature ':5.10';

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 $host;
my $port;
my $scheme;
my $help;

sub usage{
    print "Usage: cnark.pl [--host HOSTNAME --port PORT]\n";
    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 hash 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'
);

sub is_weak{
  if ($key =~ /^EXP-|^NULL|^ADH-|DES-CBC-/) {
    print RED, "    " . $key . " -- " . $value . "\n", RESET;
  }
  else {
    print GREEN, "    " . $key . " -- " . $value . "\n", RESET;
  }
}

sub get_server_type{
  my $url = "$scheme://$host/";
  my $ua = LWP::UserAgent->new;
    $ua->timeout(10);
    $ua->env_proxy;
    $ua->agent('cryptonark-pci-auditor/v0.3');
      my $header = $ua->head( $url );
      say "\nWeb Server Type: " . $header->server . "\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('cryptonark-pci-auditor/v0.3');
    
    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('cryptonark-pci-auditor/v0.3');
    
    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 shameless_plug{
    say "\n---------------Shameless Plug---------------";
    say "For tips on PCI Remediation, visit my blog at:";
    say "   http://blog.techstacks.com/pci-compliance/";
    say "Thanks for using cnark!";
    say "---------------------------------------------";
  }

get_server_type();

say "Testing for HTTP TRACE...";
sleep 2;
test_for_trace();

say "\nTesting for HTTP TRACK...";
sleep 2;
test_for_track();

sub test_ssl2_ciphers{

say "\nTesting SSLv2 Ciphers...";
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_tls_ciphers{
say "Testing SSLv3/TLSv1 Ciphers...";
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 ($port eq 443) {
 test_ssl2_ciphers();
 test_tls_ciphers();
 }
shameless_plug();

11/07/2009

Another Bling Release: v0.4.1 - So Long, Technorati

I'm releasing a new version of Bling.  The only change is the removal of the Technorati ping, since this doesn't work any more.  

According to their developer page, all (all?) previously published developer APIs ceased being available on October 25th, 2009.  Now I am sure that many of you running blogs out there were able to get some useful and interesting data from these APIs and maybe some of you are even bummed that they are gone but there is at least one bitter semi-professional blogger contributing posts on this site who still hasn't been able to attract Technorat's spider, let alone the attention of their support staff.  

Back when this site first went live, the first thing I learned as a budding new blogger was to get listed on Technorati.  **Everyone** who had a blog needed to be listed on Technorati.  Tagging your pages with Technorati-specific links was one of the first things you were supposed to do.  Heck, people even developed lots of cool widgets and applications that would create those tags for you.  Technorati had ping buttons available everywhere.  Then something happened.  Sometime within the last 18 months or so, Technorati started experiencing lots and lots of problems.  Blog claims wouldn't work.  Searching would fail.  You couldn't even load your profile at times.  What I suspect that happened was that instead of confessing to twitter-like outages and documenting every little thing on some Technorati status site, Technorati stayed online by ignoring the small sites.  Support requests would go unanswered.  Pings, although accepted, would never result in an actual crawl by their searchbot.  Then a couple months ago, a new Technorati launches. Pings don't work.  APIs are deprecated and then retired wholesale.  The new site does...something.

If anyone hits this page via search engine query and you are trying to find out if Technorati works any more, the answer from me is "No.  It doesn't work.  It never really worked for me.  My site profile never updated.  I never saw their crawler unless I deleted and recreated my blog claim.  I would almost state that for all I know, my Technorati listing is actually hurting me but I won't because I really have no clue what they do.  I'm not even sure why I'm still tagging my pages with technorati tags.  Why am I doing this?"

WasaLive and BlogBuzzMachine may have to go to in the near future.  The two of them worked great until I released v0.3 and I guess they weren't ready for the 10's (yes, I meant "tens") of new bloggers running xmlrpc pings with this script.  Although truthfully, I need to add a debug option to the script in order to display the raw output for failed pings to really see why they are failing.

The new version is available on the downloads page.  The main bling page has been updated as well.   The full source is available in the download (which mostly includes lots and lots of comments).  The source, without the 60+ lines of comments documenting change history at the beginning of the script, is below:

#!/usr/bin/env groovy

import groovy.net.xmlrpc.*
import groovy.util.slurpersupport.GPathResult

// You'll want to make this next section your own
def blogTitle = "YOUR_BLOG_TITLE_HERE"
def blogURL = "YOUR_BLOG_URL_HERE"

// Set up a map (hash) of popular rpc endpoints
// It is too bad for my syntax highlighter, but some of 
// the blogs containing periods in their names required
// placing them in quotes. 

def trackbacks = [
 Google:'http://blogsearch.google.com/ping/RPC2',
 Weblogs:'http://rpc.weblogs.com/RPC2',
 FeedBurner:'http://ping.feedburner.com/',
 Moreover:'http://api.moreover.com/RPC2',
 Syndic8:'http://ping.syndic8.com/xmlrpc.php' ,
 BlogRolling:'http://rpc.blogrolling.com/pinger/',
 NewsGator:'http://services.newsgator.com/ngws/xmlrpcping.aspx',
 Bloglines:'http://www.bloglines.com/ping',
 'Blo.gs':'http://ping.blo.gs/',
 BlogCatalog:'http://rpc.blogcatalog.com/',
 PubSub:'http://xping.pubsub.com/ping/',
 'MyBlog.jp':'http://ping.myblog.jp/',
 Goo:'http://blog.goo.ne.jp/XMLRPC',
 BlogPeople:'http://www.blogpeople.net/servlet/weblogUpdates',
 Twingly:'http://rpc.twingly.com/',
 Spinn3r:'http://rpc.spinn3r.com/open/RPC2',
 PostRank:'http://api.postrank.com/v2/ping',
 WasaLive:'http://www.wasalive.com/ping/',
 BlogBuzzMachine:'http://rpc.blogbuzzmachine.com/RPC2',
 IceRocket:'http://rpc.icerocket.com:10080/',
 FeedBlitz:'http://www.feedblitz.com/f/f.fbz?XmlPing',
 ]

// Set up canned responses to make the outputted responses nicer.
// Previously, the output used the literal response from the endpoint
// which did not look all that nice in a terminal window.
def weal = "Thanks for the ping!"
def woe = "PING ATTEMPT FAILED."

// IceRocket is separate because their ping method call
// is different from everyone else's--go figure...


// Here is the section responsible for iterating through each ping
// url in the trackbacks map.

println "====PING RESULTS===="

trackbacks.each {
 try{
  def url = it.value
  def proxy = new XMLRPCServerProxy(url)
  response = proxy.weblogUpdates.ping(blogTitle, blogURL)
   response.data instanceof GPathResult

  if (!response.flerror)
    println " ${it.key}".padRight(25) + "${weal}"
   else
    println " ${it.key}".padRight(25) + "${woe}"

  }catch(ConnectException ex) {
   println " ${it.key}".padRight(25) + "${woe}"   
  }catch(IOException ex){
   println " ${it.key}".padRight(25) + "${woe}"
  }
}
println "===================="

11/04/2009

Testing for Trace and Track

I am modifying cryptonark to report on more commonly-reported PCI findings than just SSL cipher strength. Another popular issue reported during PCI Scans is the "Web Server HTTP Trace/Track Method Support Cross-Site Tracing Vulnerability". The wording of this particular error has been a small thorn in my side mainly because "Trace/Track" can, and has, been interpreted as meaning "trace and track", "trace or track", or "trace and/or track".  The modifications I've made to this script proved to be so useful, I thought breaking it out and sharing it might be useful to others as well.  

The truth is, with most modern web servers and application servers, this vulnerability notification really means:  "trace is enabled unless you're running a fairly old version of IIS—like IIS4—then it is **probably** track".  

The script below uses Perl 5.10 and LWP::UserAgent.  I've already got Net::SSLeay and Crypt::SSLeay installed, so I did not need to declare or install them in order for LWP to be able to connect to ssl-encrypted web sites.  The script is a little bit cooler than others that I have written because not only does it attempt an HTTP Trace request and report back success, failure, or other information but it also tests for HTTP Track.  The script right now only assumes that you'll be testing against a well known ssl port, unfortunately, so your web server will need to be running ssl on port 443.  Otherwise, the port is assumed to be an HTTP port.   It should help you satisfy those security and compliance administrators who demand proof that both methods are disabled, (even though TRACK does not really exist anywhere any more).

This script is now available on the Downloads page and test4trac.pl has it's own information page now.

#!/usr/bin/perl 
#===============================================================================
#
#         FILE:  test4trace.pl
#
#        USAGE:  ./test4trace.pl  
#
#  DESCRIPTION:  The "UI" for this script may not
#                make sense but this is a sub
#                routine of a future version of my
#                cryptonark script that tests for
#                the existence of the TRACE method
#                on a web site.  That's why,
#                syntactically, this script follows
#                cryptonark's syntax and version
#                numbering.
#
#      OPTIONS:  ---
# REQUIREMENTS:  Perl 5.10 & LWP::UserAgent
#         BUGS:  None Found Yet
#        NOTES:  ---
#       AUTHOR:  Chris Mahns
#      COMPANY:  Blogging Techstacks
#      VERSION:  0.3
#      CREATED:  11/03/2009 21:45:30
#     REVISION:  ---
#===============================================================================

use strict;
use warnings;
use feature ':5.10';

use LWP::UserAgent;

my $host = $ARGV[0];
my $port = $ARGV[1];
my $scheme;

my $help = "Usage:  $0  ";

if ( !@ARGV ) {
    print $help . "\n";
    exit 0;
}

if ( $port == 443 ) {
    $scheme = 'https';
}
else {
    $scheme = 'http';
}

sub test_for_trace {
    my $url = "$scheme://$host/";

    my $method = "TRACE";
    my $ua     = LWP::UserAgent->new;
    $ua->timeout(10);
    $ua->env_proxy;
    $ua->agent('test4trace-pci-auditor/v0.3');
    
    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('test4trace-pci-auditor/v0.3');
    
    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;
        }
    }
}
say "First we test for Trace...";
sleep 2;
test_for_trace();

say "\nNow we test for Track...";
sleep 2;
test_for_track();