Pregunta

Como puedo conseguir LWP ¿Para verificar que el certificado del servidor al que me estoy conectando esté firmado por una autoridad confiable y emitido al host correcto?Por lo que puedo decir, ni siquiera verifica que el certificado indique ser para el nombre de host al que me estoy conectando.Eso parece un agujero de seguridad importante (especialmente con las recientes vulnerabilidades de DNS).

Actualizar: Resulta que lo que realmente quería era HTTPS_CA_DIR, porque no tengo ca-bundle.crt.Pero HTTPS_CA_DIR=/usr/share/ca-certificates/ Hizo el truco.Estoy marcando la respuesta como aceptada de todos modos, porque estaba lo suficientemente cerca.

Actualización 2: Resulta que HTTPS_CA_DIR y HTTPS_CA_FILE solo se aplica si está utilizando Net::SSL como biblioteca SSL subyacente.Pero LWP también funciona con IO::Socket::SSL, que ignorará esas variables de entorno y hablará felizmente con cualquier servidor, sin importar qué certificado presente.¿Existe una solución más general?

Actualización 3: Lamentablemente, la solución aún no está completa.Ni Net::SSL ni IO::Socket::SSL verifican el nombre de host con el certificado.Esto significa que alguien puede obtener un certificado legítimo para algún dominio y luego hacerse pasar por cualquier otro dominio sin que LWP se queje.

Actualización 4: LWP6.00 finalmente resuelve el problema.Ver mi respuesta para detalles.

¿Fue útil?

Solución

Este antiguo agujero de seguridad finalmente se solucionó en la versión 6.00 de libwww-perl.A partir de esa versión, por defecto LWP::Agente de usuario verifica que los servidores HTTPS presenten un certificado válido que coincida con el nombre de host esperado (a menos que $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} se establece en un valor falso o, para compatibilidad con versiones anteriores, si esa variable no está configurada en absoluto, ya sea $ENV{HTTPS_CA_FILE} o $ENV{HTTPS_CA_DIR} Está establecido).

Esto puede ser controlado por el nuevo ssl_opts opción de LWP::UserAgent.Consulte ese enlace para obtener detalles sobre cómo se ubican los certificados de la Autoridad de certificación.Pero ten cuidado, la forma en que solía funcionar LWP::UserAgent, si proporciona un ssl_opts hash al constructor, entonces verify_hostname predeterminado a 0 en lugar de 1.(este error se corrigió en LWP 6.03.) Para estar seguro, especifique siempre verify_hostname => 1 en tus ssl_opts.

Entonces use LWP::UserAgent 6; debería ser suficiente para validar los certificados del servidor.

Otros consejos

Hay dos formas de hacer esto dependiendo del módulo SSL que haya instalado.El Los documentos de LWP recomiendan instalar Crypt::SSLeay.Si eso es lo que has hecho, configurar el HTTPS_CA_FILE La variable de entorno para apuntar a su ca-bundle.crt debería funcionar.(el Cripta::Documentos SSLeay menciona esto pero es un poco ligero en detalles).Además, dependiendo de su configuración, es posible que necesite configurar el HTTPS_CA_DIR variable de entorno en su lugar.

Ejemplo de Cripta::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

Tenga en cuenta que get no die, pero devuelve un undef.

Alternativamente, puede utilizar el IO::Socket::SSL módulo (también disponible en el CPAN).Para que esto verifique el certificado del servidor, debe modificar los valores predeterminados del 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 versión también provoca get() para devolver undef pero imprime una advertencia para STDERR cuando lo ejecuta (así como un montón de depuración si importa los símbolos de depuración* desde 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) 

Llegué a esta página buscando una forma de evitar la validación SSL, pero todas las respuestas fueron muy útiles.Aquí están mis hallazgos.Para aquellos que buscan omitir la validación SSL (no recomendado, pero puede haber casos en los que sea absolutamente necesario), estoy en lwp 6.05 y esto funcionó para mí:

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

También probé en una página con POST y también funcionó.La clave es usar Net::SSL junto con verificar_hostname = 0.

Si usa LWP::UserAgent directamente (no a través de LWP::Simple), puede validar el nombre de host en el certificado agregando el encabezado "If-SSL-Cert-Subject" a su objeto HTTP::Request.El valor del encabezado se trata como una expresión regular que se aplicará al asunto del certificado y, si no coincide, la solicitud falla.Por ejemplo:

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

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 las soluciones presentadas aquí contienen una falla de seguridad importante, ya que solo verifican la validez de la cadena de confianza del certificado, pero no comparan el nombre común del certificado con el nombre de host al que se está conectando.Por lo tanto, un intermediario puede presentarle un certificado arbitrario y LWP lo aceptará felizmente siempre que esté firmado por una CA en la que confíe.El nombre común del certificado falso es irrelevante porque LWP nunca lo verifica.

Si estas usando IO::Socket::SSL como backend de LWP, puede habilitar la verificación del nombre común configurando el 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"
    );
}

También puede considerar Net::SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) Pero tenga cuidado, depende de las versiones recientes de IO::Socket::SSL y Net::SSLeay.

Tienes razón en preocuparte por esto.Desafortunadamente, no creo que sea posible hacerlo de forma 100% segura bajo ninguno de los enlaces SSL/TLS de bajo nivel que miré para Perl.

Básicamente, debe pasar el nombre de host del servidor que desea conectar a la biblioteca SSL antes de que comience el protocolo de enlace.Alternativamente, puede programar una devolución de llamada en el momento adecuado y cancelar el apretón de manos desde dentro de la devolución de llamada si no se verifica.Las personas que escribían enlaces de Perl a OpenSSL parecían tener problemas para hacer que la interfaz de devolución de llamada fuera consistente.

El método para comparar el nombre de host con el certificado del servidor también depende del protocolo.Entonces ese tendría que ser un parámetro para cualquier función perfecta.

Es posible que desee ver si hay enlaces a la biblioteca NSS de Netscape/Mozilla.Parecía bastante bueno haciendo esto cuando lo miré.

Simplemente ejecute el siguiente comando en la Terminal:sudo cpan instalar Mozilla::CA

Debería solucionarlo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top