Các yêu cầu HTTPS của Python (urllib2) đến một số trang web không thành công trên Ubuntu 12.04 mà không có proxy


23

Tôi có một ứng dụng nhỏ mà tôi đã viết bằng Python và nó đã từng hoạt động ... cho đến ngày hôm qua, khi nó đột nhiên bắt đầu gây ra lỗi cho tôi trong kết nối HTTPS. Tôi không nhớ nếu có bản cập nhật, nhưng cả Python 2.7.3rc2 và Python 3.2 đều không giống nhau.

Tôi đã googled nó và phát hiện ra rằng điều này xảy ra khi mọi người đứng sau một proxy, nhưng tôi thì không (và không có gì thay đổi trong mạng của tôi kể từ lần cuối nó hoạt động). Máy tính của tôi đang chạy windows và Python 2.7.2 không có vấn đề gì (trong cùng một mạng).

>>> url = 'https://www.mediafire.com/api/user/get_session_token.php'
>>> response = urllib2.urlopen(url).read()
  File "/usr/lib/python2.7/urllib2.py", line 126, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.7/urllib2.py", line 400, in open
    response = self._open(req, data)
  File "/usr/lib/python2.7/urllib2.py", line 418, in _open
    '_open', req)
  File "/usr/lib/python2.7/urllib2.py", line 378, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.7/urllib2.py", line 1215, in https_open
    return self.do_open(httplib.HTTPSConnection, req)
  File "/usr/lib/python2.7/urllib2.py", line 1177, in do_open
    raise URLError(err)
urllib2.URLError: <urlopen error [Errno 8] _ssl.c:504: EOF occurred in violation of protocol>

Chuyện gì vậy? Bất kỳ trợ giúp được đánh giá cao.

PS.: Các phiên bản python cũ hơn cũng không hoạt động, không phải trong hệ thống của tôi và không phải trong phiên trực tiếp từ USB, nhưng DO hoạt động trong phiên trực tiếp Ubuntu 11.10.


1
Có xảy ra cho mọi trang web SSL mà bạn cố gắng liên hệ, hoặc chỉ một trang không? Nếu nó không xảy ra cho mọi trang web, thì bạn có thể cho chúng tôi biết trang web nào đang gây ra sự cố không?
James Henstridge

Chà, bản thân tôi không phải là một lập trình viên có kinh nghiệm và tôi đang cố đọc một trang từ API của trang web và đó là cuộc gọi duy nhất yêu cầu SSL, vì vậy tôi không biết liệu tôi có làm đúng ngay từ đầu không . Tôi đã sử dụng nó như một cuộc gọi urllib.urlopen (url) .read () bình thường và nó đã hoạt động. Bạn có thể vui lòng cho tôi địa chỉ của một trang web khác hoặc một tập lệnh python sẽ trả lời câu hỏi này không?
Pablo

Ồ, tôi quên đề cập: trang web là Mediafire. Đó là cuộc gọi get_session_token đang gây ra sự cố.
Pablo

Tôi đã có thể tái tạo điều này với trang web đó. Tôi đã cập nhật câu hỏi của bạn để bao gồm các trang web trong câu hỏi. Tôi nghi ngờ rằng đây là một vấn đề với OpenSSL, vì wget cũng thất bại.
James Henstridge

Điều này xảy ra với stream.twitter.com cho tôi tại thời điểm viết bài.
MarkR

Câu trả lời:


15

Điều này dường như có liên quan đến việc bổ sung hỗ trợ TLS 1.1 và 1.2 cho phiên bản OpenSSL được tìm thấy trong 12.04. Lỗi kết nối có thể được sao chép bằng công cụ dòng lệnh OpenSSL:

$ openssl s_client -connect www.mediafire.com:443
CONNECTED(00000003)
140491065808544:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 320 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---

Kết nối thành công nếu tôi buộc kết nối sử dụng TLS 1.0 với -tls1đối số dòng lệnh.

Tôi sẽ đề nghị bạn nộp một báo cáo lỗi về vấn đề này ở đây:

https://bugs.launchpad.net/ubfox/+filebug


2
Cảm ơn bạn! Tôi đã báo cáo một lỗi. Vui lòng xem xem bạn có thể thêm bất kỳ thông tin liên quan nào vào đó không: bug.launchpad.net/ubfox/+source/openssl/+orms/965371
Pablo

1
Làm thế nào điều này giúp anh ta giải quyết vấn đề trong Python?
Cerin

2
@Cerin: nó đã phân lập vấn đề như một lỗi OpenSSL chứ không phải là một cái gì đó trong Python và hướng dẫn anh ta sử dụng trình theo dõi lỗi. Vấn đề đó đã được khắc phục.
James Henstridge

12

Đối với những người mới làm trăn như tôi, đây là cách để ghi đè omeplib cách dễ nhất. Ở đầu tập lệnh python của bạn, bao gồm các dòng sau:


import httplib
from httplib import HTTPConnection, HTTPS_PORT
import ssl

class HTTPSConnection(HTTPConnection):
    "This class allows communication via SSL."
    default_port = HTTPS_PORT

    def __init__(self, host, port=None, key_file=None, cert_file=None,
            strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
            source_address=None):
        HTTPConnection.__init__(self, host, port, strict, timeout,
                source_address)
        self.key_file = key_file
        self.cert_file = cert_file

    def connect(self):
        "Connect to a host on a given (SSL) port."
        sock = socket.create_connection((self.host, self.port),
                self.timeout, self.source_address)
        if self._tunnel_host:
            self.sock = sock
            self._tunnel()
        # this is the only line we modified from the httplib.py file
        # we added the ssl_version variable
        self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)

#now we override the one in httplib
httplib.HTTPSConnection = HTTPSConnection
# ssl_version corrections are done

Từ đây trở đi, bạn có thể sử dụng urllib hoặc bất cứ thứ gì bạn sử dụng giống như bạn thường làm.

Lưu ý: Đây là cho python 2.7. Đối với giải pháp python 3.x, bạn cần ghi đè lớp HTTPSConnection được tìm thấy trong http.client. Tôi để nó như một bài tập cho người đọc. :-)


2
Tôi thực sự thích giải pháp này, nó tránh sửa đổi bất kỳ thư viện hệ thống hoặc tin tặc nào khác.
MarkR

4
Thất bại khi sử dụng Python 2.7.4 trên Ubuntu 12.04: NameError: tên 'socket' không được xác định. --- Bạn cũng cần thêm "ổ cắm nhập khẩu".
Ben Walther

Hoạt động tốt trên Ubuntu 13.04. Cảm ơn!
dharmatech

2
Không có lý do để chỉ vá httplib. Mọi người có thể sử dụng các ổ cắm SSL khác. Người ta có thể vá sslthay vì như trong câu trả lời của tôi dưới đây.
temoto

Điều này mang lại cho tôi lỗiBadStatusLine: ''
Cerin

8

Bạn có thể tránh sửa đổi tệp omeplib.py bằng cách sửa đổi đối tượng HTTPSConnection của mình:

import httplib, ssl, socket

conn = httplib.HTTPSConnection(URL.hostname)
sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address)
conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
conn.request('POST', URL.path + URL.query)

Phương thức yêu cầu chỉ tạo một ổ cắm mới nếu Connection.sock không được xác định. Tạo một cái riêng của bạn thêm tham số ssl_version sẽ làm cho phương thức yêu cầu sử dụng nó. Sau đó, mọi thứ khác hoạt động như bình thường.

Tôi đã có cùng một vấn đề và điều này làm việc cho tôi.

Trân trọng


7

Vấn đề là ở chỗ ssl, nó không liên quan gì đến HTTP, vậy tại sao phải vá httplibnếu bạn có thể vá ssl. Đoạn mã sau sẽ sửa tất cả các socket SSL bao gồm, nhưng không giới hạn ở HTTPS, đối với Python 2.6+ (tích hợp ssl, không thử với pyopenssl).

import functools
import ssl

old_init = ssl.SSLSocket.__init__

@functools.wraps(old_init)
def ubuntu_openssl_bug_965371(self, *args, **kwargs):
  kwargs['ssl_version'] = ssl.PROTOCOL_TLSv1
  old_init(self, *args, **kwargs)

ssl.SSLSocket.__init__ = ubuntu_openssl_bug_965371

Câu trả lời tốt. Cách tốt đẹp, thanh lịch để giải quyết vấn đề.
chnrxn

3

EDIT omeplib.py (/usr/lib/pythonX.X/httplib.py trên Linux)

TÌM khai báo lớp HTTPSConnection

  class HTTPSConnection(HTTPConnection):
....

Bên trong mã lớp THAY ĐỔI

self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)

ĐẾN

self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)

Sau đó, yêu cầu HTTPS củaplplib sẽ hoạt động

import httplib
from urlparse import urlparse
url = XXX
URL = urlparse(url)
connection = httplib.HTTPSConnection(URL.hostname)
connection.request('POST', URL.path + URL.query)
response = connection.getresponse()

3
Thật sự không đúng khi chỉnh sửa một tệp hệ thống như vậy. Thay vào đó, hãy xác định lại bất kỳ định nghĩa nào cần thay đổi, bằng cách xác định lại chúng trong mã của bạn .
ζ--

2

Vấn đề này có thể là do SSLv2 bị vô hiệu hóa trên máy chủ web, nhưng Python 2.x cố gắng thiết lập kết nối với PROTOCOL_SSLv23 theo mặc định.

Đây là liên kết đến câu trả lời của tôi cho một vấn đề tương tự trên Stack Overflow - /programming//a/24166498/41957

Cập nhật: đây là chức năng giống như câu trả lời của @ temoto ở trên.


LoạiError: phương thức không liên kết __init __ () phải được gọi với phiên bản SSLSocket làm đối số đầu tiên (thay vào đó là ví dụ _socketobject)
sureshvv

Hmm, một phần () không hoạt động cho các phương thức lớp. Sẽ đăng một giải pháp tốt hơn trong thời gian ngắn.
chnrxn

@sureshvv, nếu bạn có thể giúp kiểm tra giải pháp thì nó sẽ được đánh giá cao.
chnrxn

@ câu trả lời của temeto đã làm việc.
sureshvv

1

Một sửa chữa đơn giản có hiệu quả với tôi là ghi đè giao thức mặc định của SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

Đó là hackish, nhưng nó hoạt động khá tốt trong bối cảnh ngày nay. Kể từ khi lỗ hổng poodle được phát hiện, TLSv1 đã trở thành phiên bản duy nhất được chấp nhận trên Internet.
chnrxn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.