2008-09-16 36 views
43

Làm cách nào để có được LWP để xác minh rằng chứng chỉ của máy chủ mà tôi đang kết nối được ký bởi cơ quan đáng tin cậy và được cấp cho đúng máy chủ lưu trữ? Theo như tôi có thể nói, nó thậm chí không kiểm tra xem chứng chỉ yêu cầu là tên máy chủ mà tôi đang kết nối đến. Điều đó có vẻ giống như một lỗ hổng bảo mật lớn (đặc biệt là với các lỗ hổng DNS gần đây).Tôi làm cách nào để LWP xác thực chứng chỉ máy chủ SSL?

Cập nhật: Nó chỉ ra những gì tôi thực sự muốn là HTTPS_CA_DIR, bởi vì tôi không có ca-bundle.crt. Nhưng HTTPS_CA_DIR=/usr/share/ca-certificates/ đã làm các trick. Tôi đánh dấu câu trả lời là chấp nhận, bởi vì nó đã đủ gần.

Cập nhật 2: Nó chỉ ra rằng HTTPS_CA_DIRHTTPS_CA_FILE chỉ áp dụng nếu bạn đang sử dụng Net :: SSL làm thư viện SSL cơ bản. Nhưng LWP cũng làm việc với IO :: Socket :: SSL, nó sẽ bỏ qua các biến môi trường đó và vui vẻ nói chuyện với bất kỳ máy chủ nào, bất kể chứng chỉ nào trình bày. Có giải pháp tổng quát hơn không?

Cập nhật 3: Thật không may, giải pháp vẫn chưa hoàn tất. Cả Net :: SSL và IO :: Socket :: SSL đều không kiểm tra tên máy chủ đối với chứng chỉ. Điều này có nghĩa là ai đó có thể nhận được chứng chỉ hợp pháp cho một số miền và sau đó mạo danh bất kỳ miền nào khác mà không có LWP phàn nàn.

Cập nhật 4:LWP 6.00 cuối cùng cũng giải quyết được sự cố. Xem my answer để biết chi tiết.

Trả lời

36

Lỗ hổng bảo mật lâu đời này cuối cùng đã được sửa trong phiên bản 6.00 của libwww-perl. Bắt đầu với phiên bản đó, theo mặc định, LWP::UserAgent xác minh rằng máy chủ HTTPS hiển thị chứng chỉ hợp lệ phù hợp với tên máy chủ dự kiến ​​(trừ khi $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} được đặt thành giá trị sai hoặc, để tương thích ngược nếu biến đó không được đặt, hoặc $ENV{HTTPS_CA_FILE} hoặc $ENV{HTTPS_CA_DIR} được đặt) .

Điều này có thể được kiểm soát bởi tùy chọn ssl_opts mới của LWP :: UserAgent. Xem liên kết đó để biết chi tiết về cách chứng chỉ của Tổ chức phát hành chứng chỉ. Nhưng hãy cẩn thận , cách LWP :: UserAgent sử dụng để làm việc, nếu bạn cung cấp một hash ssl_opts để các nhà xây dựng, sau đó verify_hostname mặc định để 0 thay vì 1. (This bug đã được cố định trong LWP 6.03). Để được an toàn, luôn chỉ định verify_hostname => 1 trong số ssl_opts của bạn.

Vì vậy, use LWP::UserAgent 6; phải đủ để có chứng chỉ máy chủ được xác thực.

9

Có hai cách để thực hiện việc này tùy thuộc vào mô-đun SSL nào bạn đã cài đặt. LWP docs recommend installing Crypt::SSLeay. Nếu đó là những gì bạn đã làm, hãy đặt biến môi trường HTTPS_CA_FILE để trỏ tới ca-bundle.crt của bạn nên thực hiện thủ thuật. (các Crypt::SSLeay docs đề cập đến điều này nhưng là một chút ánh sáng về chi tiết). Ngoài ra, tùy thuộc vào thiết lập của bạn, bạn có thể cần phải đặt biến môi trường HTTPS_CA_DIR thay thế.

Ví dụ cho 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 
 

Lưu ý rằng có được không làm die, nhưng nó trả về một undef.

Hoặc, bạn có thể sử dụng mô-đun IO::Socket::SSL (cũng có sẵn từ CPAN). Để làm điều này xác minh chứng chỉ máy chủ bạn cần phải sửa đổi giá trị mặc định bối cảnh 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"); 
 

Phiên bản này cũng gây get() trở undef nhưng in một cảnh báo cho STDERR khi bạn thực hiện nó (cũng như một loạt các sửa lỗi nếu bạn nhập debug * biểu tượng từ IO :: Ổ cắm :: 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

Nếu bạn sử dụng LWP :: UserAgent trực tiếp (không qua LWP :: Simple), bạn có thể xác nhận tên máy trong giấy chứng nhận bằng cách thêm Tiêu đề "If-SSL-Cert-Subject" cho đối tượng HTTP :: Request của bạn. Giá trị của tiêu đề được coi là biểu thức chính quy được áp dụng trên chủ đề chứng chỉ và nếu nó không khớp, yêu cầu không thành công.Ví dụ:

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

sẽ in

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

Bạn có quyền quan tâm về điều này. Thật không may, tôi không nghĩ rằng nó có thể làm điều đó một cách an toàn 100% dưới bất kỳ ràng buộc SSL/TLS cấp thấp nào mà tôi đã xem xét cho Perl.

Về cơ bản, bạn cần phải chuyển vào tên máy chủ của máy chủ mà bạn muốn kết nối với thư viện SSL trước khi bắt tay được thực hiện. Ngoài ra, bạn có thể sắp xếp cho một cuộc gọi lại xảy ra vào đúng thời điểm và hủy bỏ cái bắt tay từ bên trong cuộc gọi lại nếu nó không kiểm tra. Mọi người viết liên kết Perl với OpenSSL dường như gặp khó khăn khi thực hiện giao diện gọi lại một cách nhất quán.

Phương pháp kiểm tra tên máy chủ đối với chứng chỉ của máy chủ cũng phụ thuộc vào giao thức. Vì vậy, đó sẽ phải là một tham số cho bất kỳ chức năng hoàn hảo nào.

Bạn có thể muốn xem liệu có bất kỳ ràng buộc nào với thư viện Netscape/Mozilla NSS hay không. Nó có vẻ khá tốt khi làm điều này khi tôi nhìn nó.

2

Tất cả các giải pháp được trình bày ở đây đều có lỗ hổng bảo mật lớn ở chỗ chúng chỉ xác minh tính hợp lệ của chuỗi tin cậy của chứng chỉ, nhưng không so sánh tên chung của chứng chỉ với tên máy chủ bạn đang kết nối. Vì vậy, một người đàn ông ở giữa có thể trình bày một giấy chứng nhận tùy ý cho bạn và LWP sẽ vui vẻ chấp nhận nó miễn là nó được ký bởi một CA bạn tin tưởng. Tên chung của chứng chỉ không có thật là không liên quan vì nó không bao giờ được kiểm tra bởi LWP.

Nếu bạn đang sử dụng IO::Socket::SSL như backend LWP, bạn có thể kích hoạt tính năng xác minh của Common Name bằng cách thiết lập các thông số verifycn_scheme như thế này:

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

Không, giải pháp được chấp nhận không gặp phải vấn đề này. (Ok, tôi đã tự viết nó.) LWP 6 không so sánh Common Name với tên máy chủ theo mặc định và hủy bỏ nếu chúng không khớp. (Bạn đúng rằng các phiên bản trước của LWP không.) – cjm

+1

Điều đó không đúng, tôi đang sử dụng phiên bản LWP :: UserAgent (phiên bản 6.04) mới nhất làm phụ trợ cho SOAP :: Lite (phiên bản 0.714). Phần phụ trợ của LWP :: UserAgent là IO :: Socket :: SSL trên máy này. Tôi đã tìm thấy rằng không có mã được bao gồm ở trên, không phải CN được kiểm tra cũng như chuỗi chứng chỉ đã được xác minh. Sử dụng ssl_opts() để đặt "verify_hostname" và "SSL_ca_path" không có hiệu lực. – blumentopf

+1

Tôi sẽ đặt cược bạn có tập hợp '$ ENV {PERL_LWP_SSL_VERIFY_HOSTNAME}', '$ ENV {HTTPS_CA_FILE}' hoặc '$ ENV {HTTPS_CA_DIR} ', bất kỳ cài đặt nào trong số đó đều có thể vô hiệu hóa kiểm tra tên máy chủ. – cjm

6

tôi hạ cánh trên trang này tìm kiếm một cách để vượt qua xác nhận SSL nhưng tất cả các câu trả lời vẫn rất hữu ích. Đây là những phát hiện của tôi. Đối với những người tìm cách bỏ qua xác thực SSL (không được khuyến nghị nhưng có thể có trường hợp bạn hoàn toàn sẽ phải), tôi đang ở trên lwp 6.05 và điều này làm việc cho tôi:

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

Tôi cũng đã thử nghiệm trên một trang có POST và nó cũng hoạt động. Điều quan trọng là sử dụng Net :: SSL cùng với verify_hostname = 0.

+0

Dành cho The Win trên phần cứng mạng nội bộ cũ, không được hỗ trợ. – Stickley

0

Chỉ cần thực hiện thực hiện lệnh sau trong Terminal: sudo CPAN cài đặt Mozilla :: CA

Nó sẽ giải quyết nó.

Các vấn đề liên quan