Подтвердить что ты не робот

Как я могу получить LWP для проверки сертификатов SSL-сервера?

Как я могу получить 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 окончательно решает проблему. Подробнее см. мой ответ.

4b9b3361

Ответ 1

Эта долговременная дыра безопасности, наконец, была исправлена ​​в версии 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. (Эта ошибка была исправлена ​​в LWP 6.03.) Чтобы быть в безопасности, всегда указывайте verify_hostname => 1 в ssl_opts.

Так что use LWP::UserAgent 6; должно быть достаточным для проверки сертификатов сервера.

Ответ 2

Есть два способа сделать это в зависимости от того, какой модуль SSL вы установили. LWP docs рекомендует установить Crypt:: SSLeay. Если это то, что вы сделали, установив переменную среды HTTPS_CA_FILE, чтобы указать на ваш ca-bundle.crt, нужно сделать трюк. (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):


% 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

Я приземлился на этой странице, ища способ обойти проверку 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

Если вы используете 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/

Ответ 5

Все представленные здесь решения содержат серьезный недостаток безопасности, поскольку они проверяют достоверность цепочки доверия сертификатов, но не сравнивают общее имя сертификата с именем хоста, с которым вы подключаетесь. Таким образом, человек в середине может предоставить вам произвольный сертификат, и LWP с радостью примет его до тех пор, пока он будет подписан CA, которому вы доверяете. Подтверждение общего фиктивного сертификата не имеет значения, поскольку оно никогда не проверялось LWP.

Если вы используете IO::Socket::SSL в качестве LWP-сервера, вы можете включить проверку Common Name, установив параметр 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"
    );
}

Ответ 7

Вы правы, чтобы беспокоиться об этом. К сожалению, я не думаю, что это можно сделать на 100% безопасно под любым из низкоуровневых привязок SSL/TLS, на которые я смотрел Perl.

По существу вам необходимо передать имя хоста сервера, который вы хотите подключиться к библиотеке SSL, до того, как начнется процесс установления связи. В качестве альтернативы вы можете организовать обратный вызов в нужный момент и прервать рукопожатие внутри обратного вызова, если он не проверяется. Люди, записывающие привязки Perl к OpenSSL, как представляется, сталкиваются с проблемами, делающими интерфейс обратного вызова последовательно.

Метод проверки имени хоста в отношении сертификата сервера также зависит от протокола. Таким образом, это должно быть параметром любой совершенной функции.

Возможно, вам захочется увидеть, есть ли привязки к NSS-библиотеке Netscape/Mozilla. Это выглядело довольно хорошо, когда я смотрел на него.

Ответ 8

Просто выполните следующую команду в терминале: sudo cpan установить Mozilla:: CA

Он должен решить эту проблему.