代码之家  ›  专栏  ›  技术社区  ›  cjm

我如何获得LWP来验证SSL服务器证书?

  •  49
  • cjm  · 技术社区  · 17 年前

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

    更新: 原来我真正想要的是 HTTPS_CA_DIR ,因为我没有信用卡。但是 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 最终解决了这个问题。看见 my answer 了解详情。

    8 回复  |  直到 9 年前
        1
  •  39
  •   cjm    8 年前

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

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

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

        2
  •  9
  •   Ether    13 年前

    根据您安装的SSL模块,有两种方法可以做到这一点。这个 LWP docs recommend installing Crypt::SSLeay .如果这就是你所做的,设置 HTTPS_CA_FILE 指向ca-bundle.crt的环境变量应该能解决问题。(the Crypt::SSLeay docs 提到了这一点,但细节有点少)。此外,根据您的设置,您可能需要设置 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) 
    
    
        3
  •  6
  •   bshok    12 年前

    我来到这个页面是为了寻找绕过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。

        4
  •  2
  •   dave0    17 年前

    如果您直接使用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/
    
        5
  •  2
  •   blumentopf    14 年前

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

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

        7
  •  1
  •   Marsh Ray    16 年前

    你担心这件事是对的。不幸的是,我认为在我为Perl研究的任何低级SSL/TLS绑定下都不可能做到100%安全。

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

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

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

        8
  •  0
  •   Bojoer    10 年前

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

    它应该能解决这个问题。