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

发布于 2024-07-05 11:41:09 字数 949 浏览 6 评论 0原文

如何获取 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终于解决了这个问题。 有关详细信息,请参阅我的回答

How can I get LWP to verify that the certificate of the server I'm connecting to is signed by a trusted authority and issued to the correct host? As far as I can tell, it doesn't even check that the certificate claims to be for the hostname I'm connecting to. That seems like a major security hole (especially with the recent DNS vulnerabilities).

Update: It turns out what I really wanted was HTTPS_CA_DIR, because I don't have a ca-bundle.crt. But HTTPS_CA_DIR=/usr/share/ca-certificates/ did the trick. I'm marking the answer as accepted anyway, because it was close enough.

Update 2: It turns out that HTTPS_CA_DIR and HTTPS_CA_FILE only apply if you're using Net::SSL as the underlying SSL library. But LWP also works with IO::Socket::SSL, which will ignore those environment variables and happily talk to any server, no matter what certificate it presents. Is there a more general solution?

Update 3: Unfortunately, the solution still isn't complete. Neither Net::SSL nor IO::Socket::SSL is checking the host name against the certificate. This means that someone can get a legitimate certificate for some domain, and then impersonate any other domain without LWP complaining.

Update 4: LWP 6.00 finally solves the problem. See my answer for details.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

横笛休吹塞上声 2024-07-12 11:41:09

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

应该可以解决这个问题。

Just perform execute the following command in Terminal:
sudo cpan install Mozilla::CA

It should solve it.

花开柳相依 2024-07-12 11:41:09

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

You may also consider Net::SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) But, take care, it depends on recent IO::Socket::SSL and Net::SSLeay versions.

北音执念 2024-07-12 11:41:09

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

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

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

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

You are right to be concerned about this. Unfortunately, I don't think it's possible to do it 100% securely under any of the low-level SSL/TLS bindings I looked at for Perl.

Essentially you need to pass in the hostname of the server you want to connect to the SSL library before the handshaking gets underway. Alternatively, you could arrange for a callback to occur at the right moment and abort the handshake from inside the callback if it doesn't check out. People writing Perl bindings to OpenSSL seemed to have troubles making the callback interface consistently.

The method to check the hostname against the server's cert is dependent on the protocol, too. So that would have to be a parameter to any perfect function.

You might want to see if there are any bindings to the Netscape/Mozilla NSS library. It seemed pretty good at doing this when I looked at it.

萌无敌 2024-07-12 11:41:09

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

I landed on this page looking for a way to bypass SSL validation but all answers were still very helpful. Here are my findings. For those looking to bypass SSL validation (not recommended but there may be cases where you will absolutely have to), I'm on lwp 6.05 and this worked for me:

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";
}

I also tested on a page with POST and it also worked. The key is to use Net::SSL along with verify_hostname = 0.

拔了角的鹿 2024-07-12 11:41:09

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

If you use LWP::UserAgent directly (not via LWP::Simple) you can validate the hostname in the certificate by adding the "If-SSL-Cert-Subject" header to your HTTP::Request object. The value of the header is treated as a regular expression to be applied on the certificate subject, and if it does not match, the request fails. For example:

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

will print

Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/
折戟 2024-07-12 11:41:09

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

All the solutions presented here contain a major security flaw in that they only verify the validity of the certificate's trust chain, but don't compare the certificate's Common Name to the hostname you're connecting to. Thus, a man in the middle may present an arbitrary certificate to you and LWP will happily accept it as long as it's signed by a CA you trust. The bogus certificate's Common Name is irrelevant because it's never checked by LWP.

If you're using IO::Socket::SSL as LWP's backend, you can enable verification of the Common Name by setting the verifycn_scheme parameter like this:

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"
    );
}
柒夜笙歌凉 2024-07-12 11:41:09

这个长期存在的安全漏洞终于在 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; 应该足以验证服务器证书。

This long-standing security hole has finally been fixed in version 6.00 of libwww-perl. Starting with that version, by default LWP::UserAgent verifies that HTTPS servers present a valid certificate matching the expected hostname (unless $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} is set to a false value or, for backwards compatibility if that variable is not set at all, either $ENV{HTTPS_CA_FILE} or $ENV{HTTPS_CA_DIR} is set).

This can be controlled by the new ssl_opts option of LWP::UserAgent. See that link for details on how the Certificate Authority certificates are located. But be careful, the way LWP::UserAgent used to work, if you provide a ssl_opts hash to the constructor, then verify_hostname defaulted to 0 instead of 1. (This bug was fixed in LWP 6.03.) To be safe, always specify verify_hostname => 1 in your ssl_opts.

So use LWP::UserAgent 6; should be sufficient to have server certificates validated.

饮湿 2024-07-12 11:41:09

有两种方法可以执行此操作,具体取决于您安装的 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 不会消亡,但它会返回一个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) 

There are two means of doing this depending on which SSL module you have installed. The LWP docs recommend installing Crypt::SSLeay. If that's what you've done, setting the HTTPS_CA_FILE environment variable to point to your ca-bundle.crt should do the trick. (the Crypt::SSLeay docs mentions this but is a bit light on details). Also, depending on your setup, you may need to set the HTTPS_CA_DIR environment variable instead.

Example for 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

Note that get doesn't die, but it does return an undef.

Alternatively, you can use the IO::Socket::SSL module (also available from the CPAN). To make this verify the server certificate you need to modify the SSL context defaults:


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");

This version also causes get() to return undef but prints a warning to STDERR when you execute it (as well as a bunch of debugging if you import the debug* symbols from 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) 

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文