Permintaan HTTPS Python (urllib2) ke beberapa situs gagal di Ubuntu 12.04 tanpa proxy


23

Saya punya sedikit aplikasi yang saya tulis dengan Python dan dulu berfungsi ... sampai kemarin, ketika tiba-tiba mulai memberi saya kesalahan dalam koneksi HTTPS. Saya tidak ingat apakah ada pembaruan, tetapi kedua Python 2.7.3rc2 dan Python 3.2 gagal sama.

Saya mencari di Google dan menemukan bahwa ini terjadi ketika orang-orang di belakang proxy, tetapi saya tidak (dan tidak ada yang berubah di jaringan saya sejak terakhir kali bekerja). Komputer syster saya yang menjalankan windows dan Python 2.7.2 tidak memiliki masalah (di jaringan yang sama).

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

Apa yang salah? Bantuan apa pun dihargai.

PS: Versi python yang lebih lama juga tidak bekerja, tidak di sistem saya dan tidak di sesi langsung dari USB, tetapi DO bekerja di sesi langsung Ubuntu 11.10.


1
Apakah itu terjadi untuk setiap situs SSL yang Anda coba hubungi, atau hanya satu? Jika tidak terjadi untuk setiap situs, dapatkah Anda memberi tahu kami situs apa yang menyebabkan masalah?
James Henstridge

Yah, saya sendiri bukan programmer, dan saya mencoba membaca halaman dari API situs, dan itu satu-satunya panggilan yang membutuhkan SSL, jadi saya tidak tahu apakah saya melakukannya dengan benar di tempat pertama. . Saya telah menggunakannya seperti panggilan urllib.urlopen (url) .read () yang normal dan berhasil. Bisakah Anda memberi saya alamat situs lain atau skrip python yang akan menjawab pertanyaan ini?
Pablo

Oh, saya lupa menyebutkan: situsnya adalah Mediafire. Panggilan get_session_token-nya yang menyebabkan masalah.
Pablo

Saya dapat mereproduksi ini dengan situs itu. Saya telah memperbarui pertanyaan Anda untuk memasukkan situs yang dimaksud. Saya menduga ini adalah masalah dengan OpenSSL, karena wget juga gagal.
James Henstridge

Ini terjadi dengan stream.twitter.com untuk saya pada saat penulisan.
MarkR

Jawaban:


15

Ini tampaknya terkait dengan penambahan dukungan TLS 1.1 dan 1.2 ke versi OpenSSL yang ditemukan di 12.04. Kegagalan koneksi dapat direproduksi dengan alat baris perintah 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
---

Sambungan berhasil jika saya memaksa koneksi untuk menggunakan TLS 1.0 dengan -tls1argumen baris perintah.

Saya sarankan Anda mengajukan laporan bug tentang masalah ini di sini:

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


2
Terima kasih! Saya melaporkan bug. Silakan, lihat apakah Anda dapat menambahkan info yang relevan ke dalamnya: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo

1
Bagaimana ini membantunya mengatasi masalah dengan Python?
Cerin

2
@ Cerin: itu mengisolasi masalah sebagai bug OpenSSL daripada sesuatu dengan Python, dan mengarahkannya untuk menggunakan pelacak bug. Masalah itu telah diperbaiki.
James Henstridge

12

Untuk pemula python seperti saya, berikut adalah cara untuk mengganti httplib cara termudah. Di bagian atas skrip python Anda, sertakan baris ini:


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

Mulai sekarang, Anda dapat menggunakan urllib atau apa pun yang Anda gunakan seperti biasanya.

Catatan: Ini untuk python 2.7. Untuk solusi python 3.x, Anda perlu mengganti kelas HTTPSConnection yang ditemukan di http.client. Saya meninggalkan itu sebagai latihan untuk pembaca. :-)


2
Saya sangat suka solusi ini, ia menghindari memodifikasi pustaka sistem atau peretasan lainnya.
MarkR

4
Gagal menggunakan Python 2.7.4 di Ubuntu 12.04: NameError: name 'socket' tidak ditentukan. --- Anda harus menambahkan "socket impor" juga.
Ben Walther

Berfungsi bagus di Ubuntu 13.04. Terima kasih!
dharmatech

2
Tidak ada alasan untuk hanya menambal httplib. Orang-orang dapat menggunakan soket SSL lain. Orang bisa menambal sslbukan sebagai jawaban saya di bawah ini.
temoto

Ini memberi saya kesalahanBadStatusLine: ''
Cerin

8

Anda dapat menghindari memodifikasi file httplib.py dengan memodifikasi objek HTTPSConnection Anda:

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)

Metode permintaan membuat soket baru hanya jika connection.sock tidak didefinisikan. Membuat sendiri menambahkan parameter ssl_version akan membuat metode permintaan menggunakannya. Kemudian semuanya berjalan seperti biasa.

Saya mengalami masalah yang sama dan ini bekerja untuk saya.

Salam


7

Masalahnya adalah ssl, tidak ada hubungannya dengan HTTP, jadi mengapa menambal httplibjika Anda bisa menambal ssl. Kode berikut harus memperbaiki semua soket SSL termasuk, tetapi tidak terbatas pada HTTPS, untuk Python 2.6+ (bawaan ssl, tidak mencoba dengan 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

Jawaban yang bagus. Cara yang bagus dan elegan untuk menyelesaikan masalah.
chnrxn

3

EDIT httplib.py (/usr/lib/pythonX.X/httplib.py di Linux)

CARI deklarasi kelas koneksi HTTPS

  class HTTPSConnection(HTTPConnection):
....

Di dalam kode CHANGE baris baris

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

UNTUK

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

Maka permintaan HTTPS httplib harus bekerja

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
Benar-benar bukan hak untuk mengedit file sistem seperti itu. Alih-alih, tetapkan ulang definisi apa pun yang perlu diubah, dengan mendefinisikannya kembali dalam kode Anda .
Reinstate Monica - ζ--

2

Masalah ini kemungkinan karena SSLv2 dinonaktifkan di server web, tetapi Python 2.x mencoba untuk membuat koneksi dengan PROTOCOL_SSLv23 secara default.

Inilah tautan ke jawaban saya untuk masalah serupa di Stack Overflow - /programming//a/24166498/41957

Pembaruan: ini secara fungsional sama dengan jawaban @ temoto di atas.


TypeError: metode tidak terikat __init __ () harus dipanggil dengan instance SSLSocket sebagai argumen pertama (sebagai gantinya _socketobject misalnya)
sureshvv

Hmm, partial () tidak berfungsi untuk metode kelas. Akan memposting solusi yang lebih baik segera.
chnrxn

@ sureshvv, jika Anda dapat membantu memeriksa solusinya, itu akan dihargai.
chnrxn

Jawaban @ temeto berhasil.
sureshvv

1

Perbaikan sederhana yang berhasil bagi saya adalah mengganti protokol default SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

Ini retas, tetapi berfungsi cukup baik dalam konteks saat ini. Sejak kerentanan pudel telah ditemukan, TLSv1 cukup banyak menjadi satu-satunya versi yang dapat diterima di Internet.
chnrxn
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.