web-dev-qa-db-fra.com

Comment obtenir de LWP la validation des certificats de serveur SSL?

Comment obtenir LWP afin de vérifier que le certificat du serveur auquel je me connecte est signé par une autorité de confiance et délivré au bon hôte? Autant que je sache, il ne vérifie même pas que le certificat prétend être pour le nom d'hôte auquel je me connecte. Cela semble être un trou de sécurité majeur (en particulier avec les vulnérabilités DNS récentes).

Update: Il s'est avéré que ce que je voulais vraiment, c'est HTTPS_CA_DIR, car je n'ai pas de fichier ca-bundle.crt. Mais HTTPS_CA_DIR=/usr/share/ca-certificates/ a fait l'affaire. Je marque quand même la réponse comme acceptée, car elle était suffisamment proche.

Mise à jour 2: Il s'avère que HTTPS_CA_DIR et HTTPS_CA_FILE ne s'appliquent que si vous utilisez Net :: SSL comme bibliothèque SSL sous-jacente. Mais LWP fonctionne également avec IO :: Socket :: SSL, qui ignorera ces variables d’environnement et communiquera avec bonheur avec n’importe quel serveur, quel que soit le certificat présenté. Y a-t-il une solution plus générale?

Mise à jour 3: Malheureusement, la solution n'est toujours pas complète. Ni Net :: SSL ni IO :: Socket :: SSL vérifie le nom d'hôte par rapport au certificat. Cela signifie que quelqu'un peut obtenir un certificat légitime pour un domaine, puis emprunter l'identité de tout autre domaine sans que LWP se plaint.

Mise à jour 4:LWP 6.00 résout enfin le problème. Voir ma réponse pour plus de détails.

44
cjm

Cette faille de sécurité de longue date a finalement été corrigée dans la version 6.00 de libwww-Perl . À partir de cette version, par défaut LWP :: UserAgent vérifie que les serveurs HTTPS présentent un certificat valide correspondant au nom d’hôte attendu (à moins que $ENV{Perl_LWP_SSL_VERIFY_HOSTNAME} soit défini sur une valeur false ou, pour des raisons de compatibilité, si cette variable n’est pas définie du tout, $ENV{HTTPS_CA_FILE} ou $ENV{HTTPS_CA_DIR} est défini).

Ceci peut être contrôlé par la nouvelle option ssl_opts de LWP :: UserAgent. Voir ce lien pour plus de détails sur la localisation des certificats de l'autorité de certification. Mais soyez prudent, comme LWP :: UserAgent fonctionnait auparavant, si vous fournissez un hachage ssl_opts au constructeur, alors verify_hostname a pour valeur par défaut 0 au lieu de 1. ( Ce bogue a été corrigé dans LWP 6.03.) Pour plus de sécurité, spécifiez toujours verify_hostname => 1 dans votre ssl_opts.

Donc, use LWP::UserAgent 6; devrait suffire à faire valider les certificats de serveur.

37
cjm

Il existe deux méthodes pour ce faire, selon le module SSL que vous avez installé. La documentation LWP recommande d'installer Crypt :: SSLeay . Si c'est ce que vous avez fait, définir la variable d'environnement HTTPS_CA_FILE pour qu'il pointe vers votre fichier ca-bundle.crt devrait suffire. (Le Crypt :: SSLeay docs mentionne cela mais est un peu léger sur les détails). En outre, en fonction de votre configuration, vous devrez peut-être définir la variable d'environnement HTTPS_CA_DIR à la place.

Exemple pour 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

Notez que get n'a pas die, mais renvoie une undef.

Vous pouvez également utiliser le module IO::Socket::SSL (également disponible à partir du réseau CPAN). Pour que cela vérifie le certificat de serveur, vous devez modifier les paramètres par défaut du contexte 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");

Cette version entraîne également le retour de unfile par get(), mais affiche un avertissement sur STDERR lorsque vous l'exécutez (ainsi qu'un tas de débogage si vous importez les symboles de débogage * depuis 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

J'ai atterri sur cette page à la recherche d'un moyen de contourner la validation SSL, mais toutes les réponses ont été très utiles. Voici mes conclusions. Pour ceux qui cherchent à contourner la validation SSL (non recommandé mais il peut arriver que vous deviez absolument le faire), je suis sur lwp 6.05 et cela a fonctionné pour moi:

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

J'ai également testé sur une page avec POST et cela a également fonctionné. La clé consiste à utiliser Net :: SSL avec verify_hostname = 0.

6
bshok

Toutes les solutions présentées ici contiennent une faille majeure en matière de sécurité: elles vérifient uniquement la validité de la chaîne de confiance du certificat, mais ne comparent pas le nom commun du certificat au nom d'hôte auquel vous vous connectez. Ainsi, un homme au milieu peut vous présenter un certificat arbitraire et LWP l'acceptera avec plaisir tant qu'il est signé par une autorité de certification en laquelle vous avez confiance. Le nom commun du faux certificat n'est pas pertinent car il n'est jamais vérifié par LWP.

Si vous utilisez IO::Socket::SSL comme backend de LWP, vous pouvez activer la vérification du nom commun en définissant le paramètre verifycn_scheme comme suit:

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

Si vous utilisez directement LWP :: UserAgent (et non via LWP :: Simple), vous pouvez valider le nom d'hôte du certificat en ajoutant l'en-tête "If-SSL-Cert-Subject" à votre objet HTTP :: Request. La valeur de l'en-tête est traitée comme une expression régulière à appliquer sur le sujet du certificat. Si elle ne correspond pas, la demande échoue. Par exemple:

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

imprimera

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

Vous avez raison de vous en préoccuper. Malheureusement, je ne pense pas qu'il soit possible de le faire à 100% en toute sécurité avec les liaisons SSL/TLS de bas niveau que j'ai examinées pour Perl.

Vous devez essentiellement indiquer le nom d’hôte du serveur que vous souhaitez connecter à la bibliothèque SSL avant que l’opération de transfert ne commence. Alternativement, vous pouvez organiser un rappel au bon moment et interrompre la négociation depuis le rappel si le test n’exécute pas. Les personnes écrivant des liaisons Perl dans OpenSSL semblaient avoir du mal à rendre l'interface de rappel de manière cohérente.

La méthode pour vérifier le nom d'hôte par rapport au certificat du serveur dépend également du protocole. Cela devrait donc être un paramètre pour toute fonction parfaite.

Vous voudrez peut-être voir s'il existe des liaisons à la bibliothèque Netscape/Mozilla NSS. Cela me semblait très bon de le faire quand je l'ai regardé.

1
Marsh Ray

Vous pouvez également envisager Net :: SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ). Mais attention, cela dépend des entrées/sorties récentes: :: SSL et Net :: Versions SSLeay.

1
goneri

Il suffit d’exécuter la commande suivante dans Terminal: Sudo cpan install Mozilla :: CA

Cela devrait le résoudre.

0
Bojoer