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.

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

James Henstridge
sumber
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. :-)

Jeff Mikels
sumber
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

Adrikrun
sumber
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
temoto
sumber
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()
Yagger
sumber
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.

chnrxn
sumber
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
Monis
sumber
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