Perl 如何让LWP验证SSL服务器证书?

Perl 如何让LWP验证SSL服务器证书?,perl,ssl,https,lwp,Perl,Ssl,Https,Lwp,如何验证我连接的服务器的证书是否由受信任的机构签名并颁发给正确的主机?据我所知,它甚至没有检查证书是否声明是我连接到的主机名。这似乎是一个重大的安全漏洞(尤其是最近的DNS漏洞) 更新:原来我真正想要的是HTTPS\u CA\u DIR,因为我没有CA-bundle.crt。但是HTTPS\u CA\u DIR=/usr/share/CA certificates/成功了。无论如何,我都会将答案标记为已接受,因为它已经足够接近了 更新2:结果是HTTPS\u CA\u DIR和HTTPS\u C

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

更新:原来我真正想要的是
HTTPS\u CA\u DIR
,因为我没有CA-bundle.crt。但是
HTTPS\u CA\u DIR=/usr/share/CA certificates/
成功了。无论如何,我都会将答案标记为已接受,因为它已经足够接近了

更新2:结果是
HTTPS\u CA\u DIR
HTTPS\u CA\u文件
仅在使用Net::SSL作为底层SSL库时适用。但是LWP也可以与IO::Socket::SSL一起工作,它将忽略这些环境变量,并愉快地与任何服务器通信,不管它提供什么证书。有更普遍的解决办法吗

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


更新4:最终解决了问题。有关详细信息,请参阅。

根据您安装的SSL模块,有两种方法可以执行此操作。这个如果您已经这样做了,那么将
HTTPS\u CA\u文件
环境变量设置为指向您的CA-bundle.crt应该可以做到这一点。(报告中提到了这一点,但在细节上有点轻描淡写)。此外,根据您的设置,您可能需要设置
HTTPS\u CA\u 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不会死,但它会返回一个
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导入调试*符号时的一系列调试):


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

#!/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/

您也可以考虑NET::SSLGlue(),但要注意,这取决于最近的IO::Socket::SSL和NET:sSayay.版本:

< P>您对此是正确的。不幸的是,我认为在我为Perl研究的任何低级SSL/TLS绑定下都不可能100%安全地完成这项工作

实际上,在握手开始之前,您需要传入要连接到SSL库的服务器的主机名。或者,您可以安排在适当的时候进行回调,如果没有签出,则从回调内部中止握手。编写OpenSSL的Perl绑定的人似乎很难使回调接口保持一致

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


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

这个长期存在的安全漏洞终于在6.00版的中得到了修复。从该版本开始,默认情况下,验证HTTPS服务器是否提供与预期主机名匹配的有效证书(除非
$ENV{PERL\u LWP\u SSL\u VERIFY\u hostname}
设置为假值,或者如果根本没有设置该变量,为了向后兼容,
$ENV{HTTPS\u CA\u FILE}
$ENV{HTTPS\u CA\u DIR
已设置)

这可以由LWP::UserAgent的新选项控制。有关如何定位证书颁发机构证书的详细信息,请参阅该链接。但是要小心,LWP::UserAgent过去的工作方式是,如果向构造函数提供
ssl\u opts
散列,则
验证\u主机名
默认为0而不是1。(已在LWP 6.03中修复。)为了安全起见,请始终在
ssl\u选项中指定
verify\u hostname=>1


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

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

如果您使用
IO::Socket::SSL
作为LWP的后端,则可以通过如下设置
verifycn\u 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"
    );
}

我登上这一页是为了寻找一种绕过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。

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

它应该解决这个问题。
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";
}