2008-09-16 73 views
43

如何获得LWP以验证我要连接的服务器的证书是否由受信任的机构签署并发​​布给正确的主机?据我所知,它甚至不检查证书是否声明为我要连接的主机名。这似乎是一个主要的安全漏洞(尤其是最近的DNS漏洞)。如何让LWP验证SSL服务器证书?

更新:原来我真正想要的是HTTPS_CA_DIR,因为我没有ca-bundle.crt。但HTTPS_CA_DIR=/usr/share/ca-certificates/做到了。无论如何,我将答案标记为接受,因为它足够接近。

更新2:事实证明,HTTPS_CA_DIRHTTPS_CA_FILE如果你使用的Net :: SSL作为底层的SSL库才适用。但LWP也适用于IO :: Socket :: SSL,它将忽略这些环境变量,并且很乐意与任何服务器交谈,无论它提供什么证书。有更通用的解决方案吗?

更新3:不幸的是,解决方案仍然不完整。 Net :: SSL和IO :: Socket :: SSL都没有根据证书检查主机名。这意味着某人可以获得某个域的合法证书,然后在没有LWP投诉的情况下冒充任何其他域。

更新4:LWP 6.00终于解决了这个问题。有关详细信息,请参阅my answer

回答

36

这个长期存在的安全漏洞最终在libwww-perl版本6.00中得到了修复。从该版本开始,默认情况下,LWP::UserAgent验证HTTPS服务器是否提供了与预期主机名相匹配的有效证书(除非$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}设置为假值,或者为了向后兼容(如果完全没有设置该变量,则设置$ENV{HTTPS_CA_FILE}$ENV{HTTPS_CA_DIR}) 。

这可以通过LWP :: UserAgent的新ssl_opts选项来控制。有关证书颁发机构证书的位置的详细信息,请参阅该链接。但小心,LWP的方式:: UserAgent的用于工作,如果你提供一个ssl_opts哈希构造函数,然后verify_hostname默认为0而不是1(This bug固定在LWP 6.03)。为了安全起见,总是在您的ssl_opts中指定verify_hostname => 1

因此use LWP::UserAgent 6;应该足以验证服务器证书。

9

根据您安装的SSL模块,有两种方法可以做到这一点。 LWP docs recommend installing Crypt::SSLeay。如果这就是你所做的,将HTTPS_CA_FILE环境变量设置为指向你的ca-bundle.crt应该可以做到。 (Crypt::SSLeay docs提到这一点,但在细节上略显点点)。另外,根据您的设置,您可能需要设置HTTPS_CA_DIR环境变量。

的地穴:: SSLeay的

例子:

 

use LWP::Simple qw(get); 
$ENV{HTTPS_CA_FILE} = "/path/to/your/ca/file/ca-bundle"; 
$ENV{HTTPS_DEBUG} = 1; 

print get("https://some-server-with-bad-certificate.com"); 

__END__ 
SSL_connect:before/connect initialization 
SSL_connect:SSLv2/v3 write client hello A 
SSL_connect:SSLv3 read server hello A 
SSL3 alert write:fatal:unknown CA 
SSL_connect:error in SSLv3 read server certificate B 
SSL_connect:error in SSLv3 read server certificate B 
SSL_connect:before/connect initialization 
SSL_connect:SSLv3 write client hello A 
SSL_connect:SSLv3 read server hello A 
SSL3 alert write:fatal:bad certificate 
SSL_connect:error in SSLv3 read server certificate B 
SSL_connect:before/connect initialization 
SSL_connect:SSLv2 write client hello A 
SSL_connect:error in SSLv2 read server hello B 
 

注意,得到不die,但它确实返回undef

或者,您可以使用IO::Socket::SSL模块(也可从CPAN获得)。为了使这个验证您需要修改SSL上下文默认的服务器证书:

 

use IO::Socket::SSL qw(debug3); 
use Net::SSLeay; 
BEGIN { 
    IO::Socket::SSL::set_ctx_defaults(
     verify_mode => Net::SSLeay->VERIFY_PEER(), 
     ca_file => "/path/to/ca-bundle.crt", 
     # ca_path => "/alternate/path/to/cert/authority/directory" 
    ); 
} 
use LWP::Simple qw(get); 

warn get("https:://some-server-with-bad-certificate.com"); 
 

这个版本也导致get()返回民主基金,但打印警告STDERR当你执行它(以及如果一堆调试导入调试*符号从IO ::插座:: SSL):

 

% perl ssl_test.pl 
DEBUG: .../IO/Socket/SSL.pm:1387: new ctx 139403496 
DEBUG: .../IO/Socket/SSL.pm:269: socket not yet connected 
DEBUG: .../IO/Socket/SSL.pm:271: socket connected 
DEBUG: .../IO/Socket/SSL.pm:284: ssl handshake not started 
DEBUG: .../IO/Socket/SSL.pm:327: Net::SSLeay::connect -> -1 
DEBUG: .../IO/Socket/SSL.pm:1135: SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed 

DEBUG: .../IO/Socket/SSL.pm:333: fatal SSL error: SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed 
DEBUG: .../IO/Socket/SSL.pm:1422: free ctx 139403496 open=139403496 
DEBUG: .../IO/Socket/SSL.pm:1425: OK free ctx 139403496 
DEBUG: .../IO/Socket/SSL.pm:1135: IO::Socket::INET configuration failederror:00000000:lib(0):func(0):reason(0) 
500 Can't connect to some-server-with-bad-certificate.com:443 (SSL connect attempt failed with unknown errorerror:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed) 
 
2

如果直接使用LWP :: UserAgent的(不是通过LWP ::简单),你可以通过添加验证证书中的主机“If-SSL-Cert-Subject”头部到您的HTTP :: Request对象。标题的值被视为要应用于证书主题的正则表达式,如果不匹配,请求将失败。例如:

#!/usr/bin/perl 
use LWP::UserAgent; 
my $ua = LWP::UserAgent->new(); 
my $req = HTTP::Request->new(GET => 'https://yourdomain.tld/whatever'); 
$req->header('If-SSL-Cert-Subject' => '/CN=make-it-fail.tld'); 

my $res = $ua->request($req); 

print "Status: " . $res->status_line . "\n" 

将打印

Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/ 
1

你对此有所担忧。不幸的是,我认为不可能在我为Perl查看的任何低级SSL/TLS绑定中100%安全地执行此操作。

本质上,您需要在握手进行之前传递要连接到SSL库的服务器的主机名。或者,您可以安排在适当的时候进行回调,并在回调内部放弃握手(如果不检出)。编写Perl绑定到OpenSSL的人似乎遇到了一致的回调接口问题。

检查主机名与服务器证书的方法也取决于协议。所以这必须是任何完美功能的参数。

您可能想要查看是否存在与Netscape/Mozilla NSS库的任何绑定。当我看着它时,看起来很好。

2

这里介绍的所有解决方案都包含一个主要的安全漏洞,它们只验证证书信任链的有效性,但不会将证书的公用名与您要连接的主机名进行比较。因此,中间男性可以向您提供任意证书,只要LWP由您信任的CA签名,LWP就会高兴地接受。伪造证书的通用名称是不相关的,因为它从来没有被LWP检查过。

如果您使用IO::Socket::SSL作为LWP的后台,你可以通过设置verifycn_scheme参数这样使通用名的验证:

use IO::Socket::SSL; 
use Net::SSLeay; 
BEGIN { 
    IO::Socket::SSL::set_ctx_defaults(
     verify_mode => Net::SSLeay->VERIFY_PEER(), 
     verifycn_scheme => 'http', 
     ca_path => "/etc/ssl/certs" 
    ); 
} 
+2

不,接受的解决方案不会遇到这个问题。 (好吧,我自己写了。)LWP 6在默认情况下会将通用名与主机名进行比较,如果不匹配则会中止。 (你是对的,以前版本的LWP没有。) – cjm 2011-10-11 07:15:37

+1

这是不正确的,我使用最新版本的LWP :: UserAgent(版本6.04)作为SOAP :: Lite(版本0.714)的后端。 LWP :: UserAgent的后端在这台机器上是IO :: Socket :: SSL。我发现如果没有上面的代码,CN既不会被检查,也不会验证证书链。使用ssl_opts()设置“verify_hostname”和“SSL_ca_path”不起作用。 – blumentopf 2013-02-04 16:45:29

6

我登陆这个页面寻找一种方式来绕过SSL验证上但所有的答案仍然非常有帮助。这是我的发现。对于那些希望绕过SSL验证的人(不推荐,但可能有些情况下,你绝对必须),我在LWP 6。05和这对我有效:

use strict; 
use warnings; 
use LWP::UserAgent; 
use HTTP::Request::Common qw(GET); 
use Net::SSL; 

my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 },); 
my $req = GET 'https://github.com'; 
my $res = $ua->request($req); 
if ($res->is_success) { 
    print $res->content; 
} else { 
    print $res->status_line . "\n"; 
} 

我也在POST页面上测试,它也工作。关键是要使用Net :: SSL与verify_hostname沿= 0

0

只是执行执行在终端中输入以下命令: 须藤CPAN安装Mozilla :: CA

应该解决这个问题。