Tuesday, December 28, 2010

Man-in-the-middle fun with Perl LWP

Note: If you don't care about Perl anymore, and you never have to audit projects that have PERL in them, just skip this entirely.  This is basically about a perl gotcha that seems to have hit every project I've come across that uses LWP.  Nothing more, nothing less.


When you use perl and you want to access HTTP/HTTPS/FTP content, you'll typically use a pre-made module to accomplish the task.  One of the most common modules to do this is LWP.  LWP is not only used directly, but by several other popular modules to handle network communications.  And this scares me...


Why? Because perl programmers often use LWP to handle "web stuff".  Code I have encountered that uses LWP may disallow certain protocols that can be accessed, such as "file:" because they know that they only want to access remote URLs, but does not often do anything special to handle HTTPS securely.  This may be because the entire notion of validating certificates the LWP-way is so counterintuitive that even the most experience perl folks I know have never encountered this.


Here's the issue with LWP and HTTPS:


Unless you define a custom HTTP request header called "If-SSL-Cert-Subject" and set it to a regular expression that properly matches the subject field of an SSL certificate, you won't be doing proper hostname validation.  Without hostname validation, you might as well not be using SSL.


I have not come across a single project that uses LWP and has hostname validation enabled.  There's a lot of code out there and I'm sure someone has done it right somewhere.  I have a feeling this lack of validation is because (a) programmers expect HTTPS libraries to do this by default, and (b) the interface basically makes no sense. 


Here's a snippet from the LWP perldoc that describes this functionality:


       The request can contain the header "If‐SSL‐Cert‐Subject" in order to make the request conditional on the content of the server certificate.  If the certificate subject does not match, no request is sent to the server and an internally generated error response is returned.  The value of the "If‐SSL‐Cert‐Subject" header is interpreted as a Perl regular expression.

Remember that a certificate's subject may look like this:


https://www.google.com:

subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com

or like this:

https://www.godaddy.com:
subject=/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc/OU=MIS Department/CN=www.GoDaddy.com/serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)

As the value of this header is a regex, one cannot simply use a hostname.  Remember that /domain.com/ will match "someotherdomain.com" or even "hacker.anotherdomain.computerparts.topleveldomain".  Your code will still be vulnerable to attackers unless your regular expression is properly anchored and you take the time to properly parse the subject line which is a chore to say the least.  Additionally, subjects are not guaranteed to all follow the same order or format, which makes writing a general purpose check rather difficult.

And remember, if you don't do this (correctly), you might as well not be using SSL.

I have filed a couple of bugs against the larger perl projects I am aware of that use LWP.  I would strongly suggest that any perl programmers reading this check to see if they have used LWP and if so, if they have actually enabled correct hostname validation. 

Here's a snippet of perl blatantly ripped out of the perldoc for LWP::UserAgent that demonstrates setting the header so you can play around with it:

#!/usr/bin/perl
require LWP::UserAgent;
my $ua = LWP::UserAgent->new;
# If no If-SSL-Cert-Subject header exists, no hostname validation is on
# Here's a certificate check that can by bypassed by anyone. Think attacker.domain.combinationlockfactory.com
#$ua->default_header("If-SSL-Cert-Subject"=>'domain.com');
# Any of the following lines will validate https://www.godaddy.com...
#$ua->default_header("If-SSL-Cert-Subject"=>'Domain Control Validated');
#$ua->default_header("If-SSL-Cert-Subject"=>'www.Go');
#$ua->default_header("If-SSL-Cert-Subject"=>'www.GoDaddy.com');
$ua->timeout(10);
my $response = $ua->get('https://www.godaddy.com/');
if ($response->is_success) { print $response->content; } else { die $response->status_line; }

This seems like a pretty dangerous, hard to use interface.  I've reached out to some people in the perl community about trying to fix this.   I'm interested in your comments.  Hopefully reading this has not been a waste of your time.

1 comment:

  1. A helpful guy on IRC pointed me at Net::SSLGlue, which claims to properly validate hosts. Nice -- so you can actually use validation if you go this route. Unfortunately, I don't think most people have any idea that this needs to be done.

    ReplyDelete