如何获得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值,或者为了完全向后兼容,如果根本没有设置该变量,则< x8>或$ENV{HTTPS_CA_DIR}已设置)。
这可以通过LWP :: UserAgent的新ssl_opts选项进行控制。有关该证书颁发机构证书如何定位的详细信息,请参见该链接。但是请注意,LWP :: UserAgent的工作方式是:如果向构造函数提供ssl_opts哈希,则verify_hostname默认为0而不是1。(此错误已在LWP 6.03中修复。)为了安全起见,请始终在ssl_opts中指定verify_hostname => 1。
因此,use LWP::UserAgent 6;应该足以验证服务器证书。
有两种方法可以执行此操作,具体取决于您安装的SSL模块。 LWP文档建议安装Crypt :: SSLeay。如果已完成操作,则将HTTPS_CA_FILE环境变量设置为指向您的ca-bundle.crt应该可以解决问题。 (Crypt :: SSLeay文档提到了这一点,但在细节上有点轻描淡写)。另外,根据您的设置,您可能需要设置HTTPS_CA_DIR环境变量。
Crypt :: SSLeay的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <wyn>
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
</wyn> |
请注意,get不是die,但确实会返回undef。
或者,您可以使用IO::Socket::SSL模块(也可从CPAN获得)。要验证服务器证书,您需要修改SSL上下文默认值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <wyn>
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");
</wyn> |
此版本还会导致get()返回undef,但是在执行STDERR时会向STDERR打印警告(如果从IO :: Socket :: SSL导入debug *符号,还会进行一系列调试):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <wyn>
% 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)
</wyn> |
我登陆此页面,寻找一种绕过SSL验证的方法,但是所有答案仍然非常有帮助。 这是我的发现。 对于那些希望绕过SSL验证的用户(不建议这样做,但是在某些情况下您绝对必须这样做),我使用的是lwp 6.05,这对我来说很有效:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 ."
";
} |
我还在带有POST的页面上进行了测试,它也可以正常工作。 关键是使用Net :: SSL以及verify_hostname = 0。
此处介绍的所有解决方案都存在一个主要的安全漏洞,因为它们仅验证证书的信任链的有效性,而不将证书的公用名与您要连接的主机名进行比较。因此,中间的人可能会向您出示任意证书,并且LWP会很乐意接受该证书,只要该证书由您信任的CA签署即可。伪证书的公用名无关紧要,因为LWP从未对其进行过检查。
如果您将IO::Socket::SSL用作LWP的后端,则可以通过设置verifycn_scheme参数来启用对公用名的验证,如下所示:
1 2 3 4 5 6 7 8 9
| 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"
);
} |
如果直接使用LWP :: UserAgent(而不是通过LWP :: Simple),则可以通过将" If-SSL-Cert-Subject"标头添加到HTTP :: Request对象来验证证书中的主机名。标头的值被视为要应用于证书主题的正则表达式,如果不匹配,则请求失败。例如:
1 2 3 4 5 6 7 8 9 10
| #!/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 ."
" |
将打印
1
| Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/ |
您对此感到担心是正确的。不幸的是,我认为在我为Perl看过的任何低级SSL / TLS绑定下,都不可能100%安全地做到这一点。
本质上,您需要在握手开始之前传递要连接到SSL库的服务器的主机名。另外,您可以安排在适当的时机进行回调,如果未检出,则中止来自回调内部的握手。编写与OpenSSL的Perl绑定的人似乎很难使回调接口保持一致。
根据服务器的证书检查主机名的方法也取决于协议。因此,这必须是任何完善函数的参数。
您可能想查看是否有到Netscape / Mozilla NSS库的绑定。当我看着它的时候,似乎做得很好。
您也可以考虑使用Net :: SSLGlue(http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm)但是,请注意,这取决于最近的IO :: Socket :: SSL和Net :: SSLeay版本。
只需在终端中执行以下命令即可:
须藤cpan安装Mozilla :: CA
它应该解决它。