2008-09-16 99 views
43

如何獲得LWP以驗證我要連接的服務器的證書是否由受信任的機構簽署併發​​布給正確的主機?據我所知,它甚至不檢查證書是否聲明爲我要連接的主機名。這似乎是一個主要的安全漏洞(尤其是最近的DNS漏洞)。如何讓LWP驗證SSL服務器證書?

更新:原來我真正想要的是HTTPS_CA_DIR,因爲我沒有ca-bundle.crt。但HTTPS_CA_DIR=/usr/share/ca-certificates/做到了。無論如何,我將答案標記爲接受,因爲它足夠接近。

更新2:事實證明,HTTPS_CA_DIRHTTPS_CA_FILE如果你使用的Net :: SSL作爲底層的SSL庫才適用。但LWP也適用於IO :: Socket :: SSL,它將忽略這些環境變量,並且很樂意與任何服務器交談,無論它提供什麼證書。有更通用的解決方案嗎?

更新3:不幸的是,解決方案仍然不完整。 Net :: SSL和IO :: Socket :: SSL都沒有根據證書檢查主機名。這意味着某人可以獲得某個域的合法證書,然後在沒有LWP投訴的情況下冒充任何其他域。

更新4:LWP 6.00終於解決了這個問題。有關詳細信息,請參閱my answer

回答

36

這個長期存在的安全漏洞最終在libwww-perl版本6.00中得到了修復。從該版本開始,默認情況下,LWP::UserAgent驗證HTTPS服務器是否提供了與預期主機名相匹配的有效證書(除非$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}設置爲假值,或者爲了向後兼容(如果完全沒有設置該變量,則設置$ENV{HTTPS_CA_FILE}$ENV{HTTPS_CA_DIR}) 。

這可以通過LWP :: UserAgent的新ssl_opts選項來控制。有關證書頒發機構證書的位置的詳細信息,請參閱該鏈接。但小心,LWP的方式:: UserAgent的用於工作,如果你提供一個ssl_opts哈希構造函數,然後verify_hostname默認爲0而不是1(This bug固定在LWP 6.03)。爲了安全起見,總是在您的ssl_opts中指定verify_hostname => 1

因此use LWP::UserAgent 6;應該足以驗證服務器證書。

9

根據您安裝的SSL模塊,有兩種方法可以做到這一點。 LWP docs recommend installing Crypt::SSLeay。如果這就是你所做的,將HTTPS_CA_FILE環境變量設置爲指向你的ca-bundle.crt應該可以做到。 (Crypt::SSLeay docs提到這一點,但在細節上略顯點點)。另外,根據您的設置,您可能需要設置HTTPS_CA_DIR環境變量。

的地穴:: 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 
 

注意,得到不die,但它確實返回undef

或者,您可以使用IO::Socket::SSL模塊(也可從CPAN獲得)。爲了使這個驗證您需要修改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"); 
 

這個版本也導致get()返回民主基金,但打印警告STDERR當你執行它(以及如果一堆調試導入調試*符號從IO ::插座:: 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) 
 
2

如果直接使用LWP :: UserAgent的(不是通過LWP ::簡單),你可以通過添加驗證證書中的主機「If-SSL-Cert-Subject」頭部到您的HTTP :: Request對象。標題的值被視爲要應用於證書主題的正則表達式,如果不匹配,請求將失敗。例如:

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

將打印

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

你對此有所擔憂。不幸的是,我認爲不可能在我爲Perl查看的任何低級SSL/TLS綁定中100%安全地執行此操作。

本質上,您需要在握手進行之前傳遞要連接到SSL庫的服務器的主機名。或者,您可以安排在適當的時候進行回調,並在回調內部放棄握手(如果不檢出)。編寫Perl綁定到OpenSSL的人似乎遇到了一致的回調接口問題。

檢查主機名與服務器證書的方法也取決於協議。所以這必須是任何完美功能的參數。

您可能想要查看是否存在與Netscape/Mozilla NSS庫的任何綁定。當我看着它時,看起來很好。

2

這裏介紹的所有解決方案都包含一個主要的安全漏洞,它們只驗證證書信任鏈的有效性,但不會將證書的公用名與您要連接的主機名進行比較。因此,中間男性可以向您提供任意證書,只要LWP由您信任的CA簽名,LWP就會高興地接受。僞造證書的通用名稱是不相關的,因爲它從來沒有被LWP檢查過。

如果您使用IO::Socket::SSL作爲LWP的後臺,你可以通過設置verifycn_scheme參數這樣使通用名的驗證:

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

不,接受的解決方案不會遇到這個問題。 (好吧,我自己寫了。)LWP 6在默認情況下會將通用名與主機名進行比較,如果不匹配則會中止。 (你是對的,以前版本的LWP沒有。) – cjm 2011-10-11 07:15:37

+1

這是不正確的,我使用最新版本的LWP :: UserAgent(版本6.04)作爲SOAP :: Lite(版本0.714)的後端。 LWP :: UserAgent的後端在這臺機器上是IO :: Socket :: SSL。我發現如果沒有上面的代碼,CN既不會被檢查,也不會驗證證書鏈。使用ssl_opts()設置「verify_hostname」和「SSL_ca_path」不起作用。 – blumentopf 2013-02-04 16:45:29

6

我登陸這個頁面尋找一種方式來繞過SSL驗證上但所有的答案仍然非常有幫助。這是我的發現。對於那些希望繞過SSL驗證的人(不推薦,但可能有些情況下,你絕對必須),我在LWP 6。05和這對我有效:

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

我也在POST頁面上測試,它也工作。關鍵是要使用Net :: SSL與verify_hostname沿= 0

0

只是執行執行在終端中輸入以下命令: 須藤CPAN安裝Mozilla :: CA

應該解決這個問題。