我怎样才能得到 轻量级水压 验证我连接的服务器的证书是否由受信任的机构签名并颁发给正确的主机?据我所知,它甚至不检查证书是否声称适用于我要连接的主机名。这似乎是一个主要的安全漏洞(尤其是最近的 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: 轻量级WP 6.00 终于解决了问题。看 我的答案 了解详情。

有帮助吗?

解决方案

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

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

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

其他提示

有两种方法可以执行此操作,具体取决于您安装的 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) 

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

如果直接使用 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/

此处提供的所有解决方案都包含一个主要的安全缺陷,因为它们仅验证证书信任链的有效性,但不将证书的公用名与您要连接的主机名进行比较。因此,中间人可能会向您提供任意证书,只要它是由您信任的 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"
    );
}

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

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

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

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

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

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

应该解决它。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top