9 posts categorized "tools"

03/18/2010

3 Common Causes of Unknown SSL Protocol Errors with cURL

I see a lot of people hitting this site looking for reasons as to why curl is logging the following message when an SSL connection is attempted:

curl: (35) Unknown SSL protocol error in connection to ${some_server}

So, I thought it would be helpful to publish my 3 most common reasons why I've experienced this error during my web mastering career. It should not serve as an end-all list but it should provide some quick pointers.

  1. The Destination Site Does Not Like the Protocol

    Let's take my Techstacks Tools site as an example. Firing off a request like the following, results in the Unknown SSL Protocol error:

    curl --sslv2 https://techstacks-tools.appspot.com/

    Why? Well, in this case it is because the techstacks tools site does not support SSLv2, thus, generating the curl (35) error.

  2. The Destination Site Does Not Like the Cipher

    You could be trying to connect to the site using an ssl cipher that the site is configured to reject. For example, anonymous ciphers are typically disabled on ssl-encrypted sites that are customer-facing. (Many of us set a blanket rejection policy on any SSL-encrypted web site—regardless of it's purpose.) The following command string "can" also result in the curl (35) error:

    curl --ciphers ADH-RC4-MD5 https://some_web_site.some_domain.com/

    Unfortunately, the type of error response you can get from curl depends largely upon the ssl server. On some sites, you'll receive the Unknown SSL Protocol error but on my techstacks-tools site, I get:

    curl: (35) error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

    Kudos to Google because this particular error is a bit more descriptive than the one my websites at work generate because this at least tells you that a ssl socket was started but because of handshake failures, the socket was never able to complete.

    Try connecting to the site with a cipher that the site supports. Not sure which cipher to use? Well, let me introduce my cryptonark ssl cipher tester... Version 0.3.5 does a bit more than just echo back allowable ciphers on the site but there is also an older version on the downloads page that only echoes back allowable ciphers.

  3. The SSL Private Key Has Expired

    I came across this one earlier today working with an old WebSeAL site. In IBM GSKit, you can specify how long the private key password is valid. After reaching a certain date, you will still be able to get webseal started and listening on port 443 (or whatever you set your https-port value to) but you will not be able to successfully negotiate an SSL session. In today's case, the old WebSEAL instance was using long-expired kdb file with a long expired private key password. Once replaced with the correct, more-up-to-date version, everything worked again.

03/15/2010

CryptoNark v0.3.5 Released

Please let me know what you think, if something isn't working, etc.  New in this version is scanning for a small number of unsafe URLs, (i.e URLs that you don't necessarily want to expose to the outside world like IIS's "IIS Admin" site or the Apache mod_info server info page).  

This script is getting kind of long so the next version will mostly be a cleanup.

An updated version of this script is now available on the downloads page.  Source code is below:

#!/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 ( $host, $port, $scheme );
my $useragent = 'cryptonark-pci-auditor/v0.3';

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',
 'IIS Samples' => 'IISsamples',
 'IIS Scripts' => 'Scripts',
 'IIS MSADC Directory' => 'MSADC',
 'IIS Help' => 'IISHelp',
 'IIS Admin' => 'IISAdmin',
 'Tomcat Manager' => 'manager/html',
 '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....";
 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 . " unsafe url found or false positive" . "\n", RESET;
 }
 else {
  print GREEN, "  " . $key . " -- /" . $value . " unsafe 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";
 }

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();


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

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_tls_ciphers{
print "Testing SSLv3/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 ($port eq 443) {
 test_ssl2_ciphers();
 test_tls_ciphers();
 }

shameless_plug();

Advertisement: Webroot $10,000 Security Sweepstakes - Click here for up to 4 Entries!

12/22/2009

Bling v0.6 Released

A few weeks ago, Feedburner pings began throwing an exception:  "Request Throttled. Try again later" so this new release of Bling removes Feedburner from the list of XMLRPC services.

It also seemed that shortly after introducing BlogBuzzMachine into Bling's list, they decided to throw an exception that they should only be pinged using Feedburner's Pingshot service. So, if you are a user of Feedburner, like I am, and you enable the Pingshot service, you will be sending notifications out to BlogBuzzMachine. Therefore, BlogBuzzMachine has been removed.

So, the number of services currently receiving pings via Bling is at 19 along with the 5 search engines getting sitemap pings.

There is one new feature included in this release. A "debug" command-line option has been added. If you pass a -d or --debug (for POSIX compliance!) argument to bling, failed pings will include the reason for the failure from the xmlrpc service.

The main bling page has been updated and you can grab a copy from the downloads page. Please let me know of any problems in the comments. Thanks for taking a look and using it!

#!/usr/bin/env groovy

import groovy.net.xmlrpc.*
import groovy.util.slurpersupport.GPathResult
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.Method.GET
import static groovyx.net.http.ContentType.URLENC

def cli = new CliBuilder(usage: 'bling [-d,--debug]')
  cli.d(longOpt:'debug', required:false, type: GString, 'Run with verbose error messages')

def opt =  cli.parse(args)
  if (!opt) return

// You'll want to make this next section your own
def blogTitle = "YOUR_BLOG_TITLE_HERE"
def blogURL = "YOUR_BLOG_URL_HERE"
// def feedURL = "YOUR_BLOG_FEED_URL_HERE"
// New in Bling version 0.5:
// URL for Sitemap Pings
def sitemapURL = "YOUR_BLOG_SITEMAP_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 necessitated
// placing them in quotes.  

def trackbacks = [
  Google:'http://blogsearch.google.com/ping/RPC2',
  Weblogs:'http://rpc.weblogs.com/RPC2',
  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/',
  IceRocket:'http://rpc.icerocket.com:10080/',
  FeedBlitz:'http://www.feedblitz.com/f/f.fbz?XmlPing'
  ]

def searchengines = [
  Ask:'http://submissions.ask.com/ping?sitemap=',
  Bing:'http://www.bing.com/webmaster/ping.aspx?siteMap=',
  Google:'http://www.google.com/webmasters/tools/ping?sitemap=',
  Yahoo:'http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=Bling&url=',
  Moreover:'http://api.moreover.com/ping?u='
  ]

// 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 Failed"

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

println "====XMLRPC 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(20) + "${weal}"
      else if (!opt.d)
        println "  ${it.key}".padRight(20) + "${woe}"
      else 
        println "  ${it.key}".padRight(20) + "${woe} - Reason: ${response.message}"

    }catch(ConnectException ex) {
      println "  ${it.key}".padRight(20) + "${woe}"     
    }catch(IOException ex){
      println "  ${it.key}".padRight(20) + "${woe}"
    }
}
println "==========================="
println "====SITEMAP PING RESULTS==="

searchengines.each {
    def url = it.value + sitemapURL
    def http = new HTTPBuilder( url )
      http.request( GET, URLENC ) { req ->
        headers.'User-Agent' = 'Bling/0.6'
        headers.'Referer' = 'http://blog.techstacks.com/bling.html'

      response.success = { resp, html ->
        println " ${it.key}".padRight(20) + "${weal}"
      }
      response.failure = { resp, html ->
        println " ${it.key}".padRight(20) + "${woe}"
      }
  }
}

println "==========================="
Webroot Software Inc.

30% off select Xbox games from the Microsoft Store with promo code: CLR-MSFT-Games-30%. Offer valid through March 31st.


Follow techstacks on Twitter


Add to favourite links

Add to Google Reader or Homepage

Subscribe in NewsGator Online

Add to netvibes

Subscribe in Bloglines

Subscribe to Blogging Techstacks in Rojo

Add Blogging Techstacks to Pageflakes

Add Blogging Techstacks to fwicki

Tip Jar

For Donations

Tip Jar
Creative Commons Attribution-ShareAlike 3.0 Unported
Powered by WebRing.