48

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

更新: 原来我真正想要的是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终于解决了这个问题。有关详细信息,请参阅我的答案

4

8 回答 8

40

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

这可以通过LWP::UserAgent的新ssl_opts选项来控制。有关如何定位证书颁发机构证书的详细信息,请参阅该链接。但要小心,LWP::UserAgent 过去的工作方式,如果你向ssl_opts构造函数提供哈希,则verify_hostname默认为 0而不是 1。(此错误已在 LWP 6.03 中修复。)为安全起见,请始终verify_hostname => 1在您的ssl_opts.

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

于 2011-03-16T17:20:31.593 回答
9

根据您安装的 SSL 模块,有两种方法可以做到这一点。LWP 文档建议安装 Crypt:: SSLeay 。如果这就是您所做的,将HTTPS_CA_FILE环境变量设置为指向您的 ca-bundle.crt 应该可以解决问题。(Crypt::SSLeay 文档提到了这一点,但对细节有点轻描淡写)。此外,根据您的设置,您可能需要设置HTTPS_CA_DIR环境变量。

Crypt::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

请注意, get 不是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()返回 undef,但会STDERR在您执行它时打印一个警告(如果您从 IO::Socket::SSL 导入 debug* 符号,还会显示一堆调试):


% 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) 

于 2008-09-16T16:49:41.060 回答
7

我登陆此页面寻找绕过 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。

于 2014-05-09T17:32:54.347 回答
2

如果您直接使用 LWP::UserAgent(不是通过 LWP::Simple),您可以通过将“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/
于 2009-03-04T20:16:13.013 回答
2

此处介绍的所有解决方案都包含一个主要的安全漏洞,因为它们仅验证证书信任链的有效性,但不会将证书的通用名称与您要连接的主机名进行比较。因此,中间人可能会向您出示任意证书,只要它是由您信任的 CA 签名的,LWP 就会很乐意接受它。伪造证书的公用名无关紧要,因为 LWP 从未检查过它。

如果您用作 LWP 的后端,您可以通过设置如下参数IO::Socket::SSL来启用公用名验证: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"
    );
}
于 2011-10-11T06:11:04.287 回答
1

您也可以考虑 Net::SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) 但是,请注意,这取决于最近的 IO::Socket::SSL 和Net::SSLeay 版本。

于 2010-02-23T13:14:28.167 回答
1

你担心这一点是对的。不幸的是,我认为在我为 Perl 研究的任何低级 SSL/TLS 绑定下不可能 100% 安全地做到这一点。

本质上,您需要在握手开始之前传入要连接到 SSL 库的服务器的主机名。或者,您可以安排在正确的时刻发生回调,如果未签出,则从回调内部中止握手。编写 Perl 绑定到 OpenSSL 的人似乎很难使回调接口保持一致。

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

您可能想查看是否有任何绑定到 Netscape/Mozilla NSS 库。当我看着它时,它似乎很擅长这样做。

于 2010-02-23T23:47:29.493 回答
0

只需在终端中执行以下命令: sudo cpan install Mozilla::CA

它应该解决它。

于 2016-02-06T16:07:18.960 回答