it-swarm.it

Come posso ottenere che LWP convalidi i certificati del server SSL?

Come posso ottenere LWP per verificare che il certificato del server a cui mi connetto sia firmato da un'autorità fidata e rilasciato all'host corretto? Per quello che posso dire, non controlla nemmeno che il certificato affermi di essere per il nome host a cui mi sto connettendo. Sembra un grosso problema di sicurezza (specialmente con le recenti vulnerabilità DNS).

Aggiornamento: Si scopre che quello che volevo veramente era HTTPS_CA_DIR, perché non ho un ca-bundle.crt. Ma HTTPS_CA_DIR=/usr/share/ca-certificates/ ha fatto il trucco. Segnalo comunque la risposta come accettata, perché era abbastanza vicina.

Aggiornamento 2: Si scopre che HTTPS_CA_DIR e HTTPS_CA_FILE si applicano solo se si utilizza Net :: SSL come libreria SSL sottostante. Ma LWP funziona anche con IO :: Socket :: SSL, che ignorerà quelle variabili d'ambiente e parlerà felicemente con qualsiasi server, indipendentemente dal certificato che presenta. C'è una soluzione più generale?

Aggiornamento 3: Sfortunatamente, la soluzione non è ancora completa. Né Net :: SSL né IO :: Socket :: SSL sta controllando il nome dell'host rispetto al certificato. Ciò significa che qualcuno può ottenere un certificato legittimo per alcuni domini e quindi impersonare qualsiasi altro dominio senza lamentarsi di LWP.

Aggiornamento 4:LWP 6.00 finalmente risolve il problema. Vedi la mia risposta per i dettagli.

44
cjm

Questo buco di sicurezza di vecchia data è stato finalmente risolto nella versione 6.00 di libwww-Perl . A partire da tale versione, per impostazione predefinita LWP :: UserAgent verifica che i server HTTPS presentino un certificato valido corrispondente al nome host previsto (a meno che $ENV{Perl_LWP_SSL_VERIFY_HOSTNAME} sia impostato su un valore falso o, per compatibilità all'indietro se tale variabile non è impostata affatto, è impostato $ENV{HTTPS_CA_FILE} o $ENV{HTTPS_CA_DIR}).

Questo può essere controllato dal nuovo ssl_opts option di LWP :: UserAgent. Vedere quel collegamento per i dettagli su come si trovano i certificati dell'Autorità di certificazione. Ma stai attento, il modo in cui LWP :: UserAgent funzionava, se fornisci un hash ssl_opts al costruttore, quindi verify_hostname è impostato su 0 invece di 1. ( Questo bug è stato corretto in LWP 6.03.) Per sicurezza, specificare sempre verify_hostname => 1 nel ssl_opts.

Quindi use LWP::UserAgent 6; dovrebbe essere sufficiente per convalidare i certificati server.

37
cjm

Ci sono due modi per farlo a seconda del modulo SSL che hai installato. I documenti LWP raccomandano l'installazione di Crypt :: SSLeay . Se questo è quello che hai fatto, l'impostazione della variabile d'ambiente HTTPS_CA_FILE in modo che punti al tuo ca-bundle.crt dovrebbe fare il trucco. (i Crypt :: SSLeay docs menzionano questo, ma sono un po 'chiari sui dettagli). Inoltre, a seconda della configurazione, potrebbe essere necessario impostare la variabile d'ambiente HTTPS_CA_DIR.

Esempio per 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

Si noti che get non die, ma restituisce undef.

In alternativa, puoi utilizzare il modulo IO::Socket::SSL (disponibile anche da CPAN). Per verificare se il certificato del server è necessario modificare le impostazioni predefinite del contesto 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");

Questa versione fa sì che get() restituisca undef ma stampa un warning a STDERR quando lo si esegue (oltre a un po 'di debugging se si importano i simboli debug * da 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

Sono atterrato su questa pagina cercando un modo per aggirare la convalida SSL, ma tutte le risposte erano ancora molto utili. Ecco le mie scoperte. Per coloro che cercano di aggirare la convalida SSL (sconsigliato, ma ci possono essere casi in cui è assolutamente necessario), sono su lwp 6.05 e questo ha funzionato per me:

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

Ho anche provato su una pagina con POST e ha funzionato anche. La chiave è usare Net :: SSL insieme a verify_hostname = 0.

6
bshok

Tutte le soluzioni qui presentate contengono un importante difetto di sicurezza in quanto verificano solo la validità della catena di attendibilità del certificato, ma non confrontano il nome comune del certificato con il nome host a cui ci si sta connettendo. Quindi, un uomo nel mezzo può presentare un certificato arbitrario a te e LWP lo accetterà felicemente fintanto che sarà firmato da una CA di cui ti fidi. Il nome comune del certificato fittizio è irrilevante perché non viene mai controllato da LWP.

Se stai utilizzando IO::Socket::SSL come backend di LWP, puoi abilitare la verifica del Common Name impostando il parametro verifycn_scheme in questo modo:

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 si utilizza direttamente LWP :: UserAgent (non tramite LWP :: Simple), è possibile convalidare il nome host nel certificato aggiungendo l'intestazione "If-SSL-Cert-Subject" all'oggetto HTTP :: Request. Il valore dell'intestazione viene considerato come un'espressione regolare da applicare sull'oggetto del certificato e, se non corrisponde, la richiesta non riesce. Per esempio:

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

stamperà

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

Hai ragione a essere preoccupato per questo. Sfortunatamente, non penso che sia possibile farlo al 100% in modo sicuro con nessuno dei binding SSL/TLS di basso livello che ho visto per Perl.

In sostanza è necessario passare il nome host del server che si desidera connettere alla libreria SSL prima che inizi l'handshake. In alternativa, è possibile organizzare una richiamata al momento giusto e interrompere l'handshake dall'interno della richiamata se non si verifica. Le persone che scrivevano binding Perl su OpenSSL sembravano avere problemi a rendere l'interfaccia di callback coerente.

Il metodo per controllare il nome host rispetto al certificato del server dipende anche dal protocollo. Quindi questo dovrebbe essere un parametro per qualsiasi funzione perfetta.

Potresti voler vedere se ci sono collegamenti alla libreria Netscape/Mozilla NSS. Sembrava abbastanza bravo a farlo quando l'ho guardato.

1
Marsh Ray

Si può anche considerare Net :: SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) Ma attenzione, dipende dal recente IO :: Socket :: Versioni SSL e Net :: SSLeay.

1
goneri

Basta eseguire il seguente comando nel terminale: Sudo cpan installa Mozilla :: CA

Dovrebbe risolverlo.

0
Bojoer