如何让LWP验证我连接的服务器的证书是否由受信任的机构签名并发送给正确的主机?据我所知,它甚至没有检查证书声称是否为我正在连接的主机名.这似乎是一个主要的安全漏洞(特别是最近的DNS漏洞).
更新: 事实证明我真正想要的是HTTPS_CA_DIR
,因为我没有ca-bundle.crt.但是HTTPS_CA_DIR=/usr/share/ca-certificates/
诀窍呢.无论如何,我将答案标记为已被接受,因为它足够接近.
更新2:事实证明,HTTPS_CA_DIR
并且HTTPS_CA_FILE
仅当您正在使用的Net :: SSL作为底层的SSL库申请.但LWP也适用于IO :: Socket :: SSL,它将忽略这些环境变量并愉快地与任何服务器通信,无论它呈现什么证书.有更通用的解决方案吗?
更新3:不幸的是,解决方案仍然不完整.Net :: SSL和IO :: Socket :: SSL都没有根据证书检查主机名.这意味着某人可以获得某个域的合法证书,然后在没有LWP抱怨的情况下冒充任何其他域.
更新4: LWP 6.00终于解决了这个问题.详情请见我的回答.
这个长期存在的安全漏洞终于在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;
应该足以验证服务器证书.
根据您安装的SSL模块,有两种方法可以执行此操作.该LWP文档建议安装地穴:: 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导入调试*符号,还会进行一系列调试):
% 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.