Pergunta

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

Atualizar: Acontece que o que eu realmente queria era HTTPS_CA_DIR, porque não tenho um ca-bundle.crt.Mas HTTPS_CA_DIR=/usr/share/ca-certificates/ fez o truque.De qualquer forma, estou marcando a resposta como aceita, porque estava próxima o suficiente.

Atualização 2: Acontece que HTTPS_CA_DIR e HTTPS_CA_FILE aplica-se apenas se você estiver usando Net::SSL como a biblioteca SSL subjacente.Mas o LWP também funciona com IO::Socket::SSL, que irá ignorar essas variáveis ​​de ambiente e conversar alegremente 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ão 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 então personificar qualquer outro domínio sem reclamar do LWP.

Atualização 4: LWP 6,00 finalmente resolve o problema.Ver minha resposta para detalhes.

Foi útil?

Solução

Esta 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 que corresponde ao nome de host esperado (a menos que $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} está definido como um valor falso ou, para compatibilidade com versões anteriores, se essa variável não estiver definida, $ENV{HTTPS_CA_FILE} ou $ENV{HTTPS_CA_DIR} está definido).

Isto pode ser controlado pelo novo ssl_opts opção de LWP::UserAgent.Consulte esse link para obter detalhes sobre como os certificados da Autoridade de Certificação estão localizados.Mas tome cuidado, a maneira como LWP::UserAgent costumava funcionar, se você fornecer um ssl_opts hash para o construtor, então verify_hostname padrão para 0 em vez de 1.(Este bug foi corrigido no LWP 6.03.) Por segurança, sempre especifique verify_hostname => 1 na tua ssl_opts.

Então use LWP::UserAgent 6; deve ser suficiente para validar os certificados do servidor.

Outras dicas

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

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

Observe que get não die, mas retorna um undef.

Alternativamente, você pode usar o IO::Socket::SSL módulo (também disponível no CPAN).Para fazer isso, verifique o certificado do servidor, você precisa 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 causa get() para retornar undef, mas imprime um aviso para STDERR quando você executá-lo (bem como um monte de depuração se você importar os símbolos debug* 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) 

Cheguei a esta página procurando uma maneira de ignorar a validação SSL, mas todas as respostas ainda foram muito úteis.Aqui estão minhas descobertas.Para aqueles que desejam ignorar a validação SSL (não recomendado, mas pode haver casos em que será absolutamente necessário), estou no lwp 6.05 e 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";
}

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

Se você usar LWP::UserAgent diretamente (não via LWP::Simple), poderá validar o nome do host 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 ao 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"

irá imprimir

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

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

Se você estiver usando IO::Socket::SSL como back-end do LWP, você pode ativar a verificação do Nome Comum definindo o verifycn_scheme parâmetro como este:

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

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

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

Essencialmente, você precisa passar o nome do host do servidor que deseja conectar à biblioteca SSL antes do início do handshake.Como alternativa, você pode fazer com que um retorno de chamada ocorra no momento certo e abortar o handshake de dentro do retorno de chamada, caso não seja verificado.As pessoas que escrevem ligações Perl para OpenSSL parecem ter problemas para tornar a interface de retorno de chamada consistente.

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 Netscape/Mozilla NSS.Parecia muito bom fazer isso quando olhei para ele.

Basta executar o seguinte comando no Terminal:sudo cpan instalar Mozilla::CA

Deveria resolver isso.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top