Как я могу заставить LWP проверять сертификаты сервера SSL?

StackOverflow https://stackoverflow.com/questions/74358

  •  09-06-2019
  •  | 
  •  

Вопрос

Как я могу получить ЛВП проверить, что сертификат сервера, к которому я подключаюсь, подписан доверенным органом и выдан правильному хосту?Насколько я могу судить, он даже не проверяет, действительно ли сертификат принадлежит имени хоста, к которому я подключаюсь.Это похоже на серьезную дыру в безопасности (особенно с учетом недавних уязвимостей 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: ЛВП 6.00 наконец решает проблему.Видеть мой ответ для получения подробной информации.

Это было полезно?

Решение

Эта давняя дыра в безопасности наконец-то исправлена ​​в версии 6.00. libwww-perl.Начиная с этой версии, по умолчанию LWP::UserAgent проверяет, что серверы HTTPS предоставляют действительный сертификат, соответствующий ожидаемому имени хоста (если только $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} установлено ложное значение или, для обратной совместимости, если эта переменная вообще не установлена, либо $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; должно быть достаточно для проверки сертификатов сервера.

Другие советы

Есть два способа сделать это в зависимости от того, какой модуль 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 не делает 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) 

Я зашел на эту страницу в поисках способа обойти проверку 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.

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

Все представленные здесь решения содержат серьезный недостаток безопасности: они только проверяют достоверность цепочки доверия сертификата, но не сравнивают общее имя сертификата с именем хоста, к которому вы подключаетесь.Таким образом, человек посередине может предоставить вам произвольный сертификат, и 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"
    );
}

Вы также можете рассмотреть Net::SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) Но будьте осторожны, это зависит от последних версий IO::Socket::SSL и Net::SSLeay.

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top