it-swarm-pt.tech

Como posso obter o LWP para validar certificados de servidor SSL?

Como posso obter LWP para verificar se o certificado do servidor ao qual estou me conectando está assinado por uma autoridade confiável e emitido para o host correto? Tanto quanto eu posso dizer, nem sequer verifica que o certificado afirma ser para o nome do host que eu estou conectando. Isso parece ser uma grande falha de segurança (especialmente com as vulnerabilidades recentes do DNS).

Update: Acontece que o que eu realmente queria era HTTPS_CA_DIR, porque eu não tenho um ca-bundle.crt. Mas HTTPS_CA_DIR=/usr/share/ca-certificates/ fez o truque. Estou marcando a resposta como aceita de qualquer maneira, porque estava perto o suficiente.

Atualização 2: Acontece que HTTPS_CA_DIR e HTTPS_CA_FILE só se aplicam se você estiver usando Net :: SSL como a biblioteca SSL subjacente. Mas o LWP também funciona com o IO :: Socket :: SSL, que irá ignorar essas variáveis ​​de ambiente e, felizmente, conversar com qualquer servidor, não importa qual certificado ele apresente. Existe uma solução mais geral?

Atualização 3: Infelizmente, a solução ainda não está completa. Nem Net :: SSL nem IO :: Socket :: SSL está verificando o nome do host em relação ao certificado. Isso significa que alguém pode obter um certificado legítimo para algum domínio e, em seguida, se passar por outro domínio sem queixar-se do LWP.

Atualização 4:LWP 6.00 finalmente resolve o problema. Veja minha resposta para detalhes.

44
cjm

Essa falha de segurança de longa data foi finalmente corrigida na versão 6.00 do libwww-Perl . Começando com essa versão, por padrão LWP :: UserAgent verifica se os servidores HTTPS apresentam um certificado válido correspondente ao nome de host esperado (a menos que $ENV{Perl_LWP_SSL_VERIFY_HOSTNAME} seja definido como um valor falso ou, para compatibilidade com versões anteriores, se essa variável não for definida) $ENV{HTTPS_CA_FILE} ou $ENV{HTTPS_CA_DIR} está definido).

Isto pode ser controlado pela nova opção ssl_opts do LWP :: UserAgent. Veja este link para detalhes sobre como os certificados da Autoridade de Certificação estão localizados. Mas be careful, o modo como LWP :: UserAgent costumava funcionar, se você fornecer um hash ssl_opts para o construtor, então verify_hostname assumido como 0 ao invés de 1. ( Esse bug foi corrigido no LWP 6.03.) Para ser seguro, sempre especifique verify_hostname => 1 no seu ssl_opts.

Portanto, use LWP::UserAgent 6; deve ser suficiente para ter certificados de servidor validados.

37
cjm

Existem duas maneiras de fazer isso, dependendo de qual módulo SSL você instalou. Os documentos LWP recomendam instalar o Crypt :: SSLeay . Se foi isso que você fez, definir a variável de ambiente HTTPS_CA_FILE para apontar para o seu ca-bundle.crt deve resolver o problema. (o Crypt :: SSLeay docs menciona isso, mas é um pouco leve em detalhes). Além disso, dependendo da configuração, talvez seja necessário definir a variável de ambiente HTTPS_CA_DIR.

Exemplo para 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 que get não é die, mas retorna um undef.

Como alternativa, você pode usar o módulo IO::Socket::SSL (também disponível no CPAN). Para fazer isso, verifique o certificado do servidor para modificar os padrões de contexto 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");

Esta versão também faz com que get() retorne undef mas imprime um aviso para STDERR quando você o executa (assim como um monte de depuração se você importar os símbolos de depuração * de 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) 
9
Brian Phillips

Cheguei a esta página procurando uma maneira de contornar a validação de SSL, mas todas as respostas ainda foram muito úteis. Aqui estão minhas descobertas. Para aqueles que procuram contornar a validação do SSL (não recomendado, mas pode haver casos em que você absolutamente terá que), eu estou no lwp 6.05 e isso funcionou para mim:

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

Eu também testei em uma página com POST e também funcionou. A chave é usar o Net :: SSL juntamente com verify_hostname = 0.

6
bshok

Todas as soluções apresentadas aqui contêm uma grande falha de segurança, pois apenas verificam a validade da cadeia de confiança do certificado, mas não comparam o Nome Comum do certificado com o nome do host ao qual você está se conectando. Assim, um homem no meio pode apresentar um certificado arbitrário para você e a LWP aceitará com satisfação, desde que seja assinado por uma CA em quem você confia. O nome comum do certificado falso é irrelevante porque nunca é verificado pelo LWP.

Se você estiver usando IO::Socket::SSL como backend do LWP, você pode habilitar a verificação do Common Name definindo o parâmetro verifycn_scheme desta forma:

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
blumentopf

Se você usa LWP :: UserAgent diretamente (não via LWP :: Simple) você pode validar o hostname no certificado adicionando o cabeçalho "If-SSL-Cert-Subject" ao seu objeto HTTP :: Request. O valor do cabeçalho é tratado como uma expressão regular a ser aplicada no assunto do certificado e, se não corresponder, a solicitação falhará. Por exemplo:

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

vai imprimir

Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/
2
dave0

Você está certo em se preocupar com isso. Infelizmente, eu não acho que é possível fazer isso 100% com segurança sob qualquer uma das ligações SSL/TLS de baixo nível que eu observei para o Perl.

Essencialmente, você precisa passar o nome do host do servidor que deseja conectar à biblioteca SSL antes que o handshaking seja iniciado. Como alternativa, você pode solicitar que um retorno de chamada ocorra no momento certo e abortar o handshake de dentro do retorno de chamada, se ele não fizer o check-out. As pessoas que escreviam ligações Perl para o OpenSSL pareciam ter problemas para fazer a interface de retorno de chamada consistentemente.

O método para verificar o nome do host em relação ao certificado do servidor também depende do protocolo. Então isso teria que ser um parâmetro para qualquer função perfeita.

Você pode querer ver se há alguma ligação com a biblioteca do Netscape/Mozilla NSS. Parecia muito bom fazer isso quando eu olhei para ele.

1
Marsh Ray

Você também pode considerar Net :: SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) Mas, tome cuidado, isso depende de recente IO :: Socket :: Versões SSL e Net :: SSLeay.

1
goneri

Basta executar o seguinte comando no Terminal: Sudo cpan install Mozilla :: CA

Deve resolver isso.

0
Bojoer