Как я могу получить LWP для проверки сертификатов сервера SSL? - PullRequest
44 голосов
/ 16 сентября 2008

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

Ответы [ 8 ]

37 голосов
/ 16 марта 2011

Эта давняя дыра в безопасности наконец исправлена ​​в версии 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; должно быть достаточно для проверки сертификатов сервера.

9 голосов
/ 16 сентября 2008

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

6 голосов
/ 09 мая 2014

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

2 голосов
/ 11 октября 2011

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

Если вы используете 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/
1 голос
/ 24 февраля 2010

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

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

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

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

1 голос
/ 23 февраля 2010

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

0 голосов
/ 06 февраля 2016

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

Это должно решить.

...