Comment faire pour que LWP valide les certificats du serveur SSL?
Comment puis-je obtenir LWP pour 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? Pour 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 ressemble à un grand trou de sécurité (en particulier avec les récentes vulnérabilités DNS).
mise à Jour: Il s'avère que ce que je voulais, c'était HTTPS_CA_DIR
, parce que je n'ai pas de paquet.CRT. Mais HTTPS_CA_DIR=/usr/share/ca-certificates/
a fait l'affaire. Je note la réponse comme acceptée de toute façon, parce qu'elle était assez proche.
mise à Jour 2: Il s'avère que HTTPS_CA_DIR
et HTTPS_CA_FILE
ne s'applique que si vous utilisez Net::SSL sous-jacents de la bibliothèque SSL. Mais LWP fonctionne aussi avec IO:: Socket:: SSL, qui ignorera ces variables d'environnement et parlera avec plaisir à n'importe quel serveur, quel que soit le certificat présent. Y a-t-il une solution plus générale?
Maj 3: malheureusement, la solution n'est toujours pas complète. Ni Net:: SSL ni IO::Socket::SSL vérifient le nom d'hôte par rapport au certificat. Cela signifie que quelqu'un peut obtenir un certificat légitime pour un certain domaine, puis se faire passer pour tout autre domaine sans se plaindre LWP.
mise à jour 4: LWP 6.00 enfin résout le problème. Voir ma réponse pour plus de détails.
8 réponses
ce trou de sécurité de longue date a finalement été corrigé dans la version 6.00 de libwww-perl . A 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}
ne soit défini à une valeur fausse ou, pour une compatibilité inversée si cette variable n'est pas définie du tout, $ENV{HTTPS_CA_FILE}
ou $ENV{HTTPS_CA_DIR}
soit défini).
cela peut être contrôlé par la nouvelle option ssl_opts de LWP::UserAgent. Consultez ce lien pour obtenir des détails sur la façon dont les certificats D'Autorité de certification sont situés. Mais soyez prudent , la façon dont LWP:: UserAgent utilisé pour travailler, si vous fournissez un ssl_opts
hachage au constructeur, puis verify_hostname
par défaut à 0 au lieu de 1. ( Ce bug a été corrigé dans LWP 6.03.) Pour être sûr, toujours spécifier verify_hostname => 1
votre ssl_opts
.
So use LWP::UserAgent 6;
devrait être suffisant pour que les certificats de serveur soient validés.
il y a deux moyens de le faire selon le module SSL que vous avez installé. Le LWP docs recommande l'installation de Crypt:: SSLeay . Si c'est ce que vous avez fait, Paramétrez la variable d'environnement HTTPS_CA_FILE
pour pointer votre ca-bundle.le crt devrait faire l'affaire. (le Crypt:: SSLeay docs mentionne ceci mais est un peu léger sur les détails). En outre, selon votre configuration, vous pouvez avoir besoin de définir la variable d'environnement HTTPS_CA_DIR
plutôt.
exemple de 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 ne fait pas die
, mais il renvoie un undef
.
alternativement, vous pouvez utiliser le module IO::Socket::SSL
(également disponible dans le CPAN). Pour ce faire, vérifiez le certificat du serveur, vous devez modifier le contexte SSL par défaut:
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 provoque aussi get()
à retourner undef mais imprime un avertissement à STDERR
lorsque vous l'exécutez (ainsi qu'un paquet de débogage si vous importez les symboles de débogage* 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)
j'ai atterri sur cette page à la recherche d'un moyen de contourner la validation SSL, mais toutes les réponses étaient toujours très utiles. Voici mes conclusions. Pour ceux qui cherchent à contourner la validation SSL (pas recommandé mais il peut y avoir des cas où vous devrez 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 aussi testé sur une page avec POST et cela a aussi fonctionné. La clé est D'utiliser Net:: SSL avec verify_hostname = 0.
si vous utilisez LWP:: UserAgent directement (pas via LWP::Simple) vous pouvez valider le nom d'hôte dans le certificat en ajoutant l'en-tête "If-SSL-Cert-Subject" à votre HTTP::Request objet. La valeur de l'en-tête est traitée comme une expression régulière à appliquer sur le sujet du certificat, et si elle ne correspond pas, la requête é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"
imprime
Status: 500 Bad SSL certificate subject: '/C=CA/ST=Ontario/L=Ottawa/O=Your Org/CN=yourdomain.tld' !~ //CN=make-it-fail.tld/
toutes les solutions présentées ici contiennent un défaut de sécurité majeur en ce qu'elles ne vérifient que la validité de la chaîne de confiance du certificat, mais ne comparent pas le nom usuel 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 volontiers tant qu'il est signé par une AC en qui vous avez confiance. Le nom usuel du faux certificat n'est pas pertinent parce qu'il n'a jamais été 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 ceci:
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"
);
}
vous pouvez également considérer Net:: SSLGlue ( http://search.cpan.org/dist/Net-SSLGlue/lib/Net/SSLGlue.pm ) mais, attention, cela dépend des versions récentes de IO::Socket::SSL et Net::SSLeay.
vous avez raison de vous en inquiéter. Malheureusement, je ne pense pas qu'il soit possible de le faire 100% en toute sécurité sous l'une des fixations SSL/TLS de bas niveau que J'ai regardé pour Perl.
essentiellement, vous devez passer dans le nom d'hôte du serveur que vous voulez connecter à la bibliothèque SSL avant que la handshaking commence. Alternativement, vous pouvez prendre des dispositions pour qu'un rappel se produise au bon moment et annuler la poignée de main de l'intérieur du rappel si elle ne vérifie pas hors. Les personnes qui écrivent des fixations Perl vers OpenSSL semblent avoir des problèmes à rendre l'interface de rappel cohérente.
la méthode pour vérifier le nom d'hôte par rapport au cert du serveur dépend aussi du protocole. De sorte que devrait être un paramètre d'une fonction parfaite.
vous pourriez vouloir voir s'il y a des reliures à la bibliothèque Netscape/Mozilla NSS. Il semblait assez bon pour faire ça quand je l'ai regardé.
il suffit d'exécuter la commande suivante dans le Terminal: sudo cpan install Mozilla:: CA
ça devrait le résoudre.