Le richieste HTTPS di Python (urllib2) ad alcuni siti falliscono su Ubuntu 12.04 senza proxy


23

Ho una piccola app che ho scritto in Python e funzionava ... fino a ieri, quando improvvisamente ha iniziato a darmi un errore in una connessione HTTPS. Non ricordo se ci fosse un aggiornamento, ma sia Python 2.7.3rc2 che Python 3.2 non funzionano allo stesso modo.

L'ho cercato su Google e ho scoperto che ciò accade quando le persone sono dietro un proxy, ma non lo sono (e nulla è cambiato nella mia rete dall'ultima volta che ha funzionato). Il mio computer Syster esegue Windows e Python 2.7.2 non ha problemi (nella stessa rete).

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

Cosa c'è che non va? Qualsiasi aiuto è apprezzato.

PS .: Le versioni precedenti di Python non funzionano, non nel mio sistema e non in una sessione live da USB, ma funzionano in una sessione live di Ubuntu 11.10.


1
Succede per ogni sito SSL che provi a contattare o solo per quello? Se non si verifica per ogni sito, potresti dirci quale sito sta causando il problema?
James Henstridge,

Beh, non sono un programmatore esperto e sto provando a leggere una pagina dall'API di un sito, e questa è l'unica chiamata che richiede SSL, quindi non so se lo stavo facendo bene in primo luogo . L'ho usato come una normale chiamata urllib.urlopen (url) .read () e funzionava. Potresti darmi l'indirizzo di un altro sito o uno script Python per rispondere a questa domanda?
Pablo,

Oh, ho dimenticato di menzionare: il sito è Mediafire. È la sua chiamata get_session_token che sta causando il problema.
Pablo,

Sono stato in grado di riprodurre questo con quel sito. Ho aggiornato la tua domanda per includere il sito in questione. Sospetto che questo sia un problema con OpenSSL, poiché anche wget non riesce.
James Henstridge,

Questo succede con stream.twitter.com per me al momento della scrittura.
MarkR

Risposte:


15

Ciò sembra essere correlato all'aggiunta del supporto TLS 1.1 e 1.2 alla versione di OpenSSL disponibile in 12.04. L'errore di connessione può essere riprodotto con lo strumento da riga di comando 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
---

La connessione ha esito positivo se impongo alla connessione di utilizzare TLS 1.0 con l' -tls1argomento della riga di comando.

Vorrei suggerire di presentare una segnalazione di bug su questo problema qui:

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


2
Grazie! Ho segnalato un bug. Per favore, vedi se riesci ad aggiungere informazioni pertinenti: bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo

1
Come può aiutarlo a risolvere il problema in Python?
Cerin,

2
@Cerin: ha isolato il problema come un bug OpenSSL piuttosto che qualcosa in Python, e lo ha indicato per usare il tracker dei bug. Da allora quel problema è stato risolto.
James Henstridge,

12

Per i principianti di Python come me, ecco il modo per ignorare httplib nel modo più semplice. Nella parte superiore dello script Python, includi queste righe:


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

Da qui in poi, puoi usare urllib o qualunque cosa tu usi come faresti normalmente.

Nota: questo è per Python 2.7. Per una soluzione python 3.x, è necessario sovrascrivere la classe HTTPSConnection presente in http.client. Lo lascio come esercizio per il lettore. :-)


2
Mi piace molto questa soluzione, evita di modificare qualsiasi libreria di sistema o altro hacker.
MarkR

4
Errore con Python 2.7.4 su Ubuntu 12.04: NameError: il nome 'socket' non è definito. --- Dovrai aggiungere anche "import socket".
Ben Walther,

Funziona alla grande su Ubuntu 13.04. Grazie!
Dharmatech,

2
Non c'è motivo di applicare solo patch httplib. Le persone possono utilizzare altri socket SSL. Si potrebbe sslinvece applicare la patch come nella mia risposta di seguito.
temoto,

Questo mi dà l'erroreBadStatusLine: ''
Cerin,

8

Puoi evitare di modificare il file httplib.py modificando l'oggetto HTTPSConnection:

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)

Il metodo di richiesta crea un nuovo socket solo se connection.sock non è definito. Creandone uno tuo aggiungendo il parametro ssl_version farà sì che il metodo di richiesta lo usi. Quindi tutto il resto funziona come al solito.

Stavo avendo lo stesso problema e questo funziona per me.

Saluti


7

Il problema è sslche non ha nulla a che fare con HTTP, quindi perché patch httplibse è possibile patch ssl. Il codice seguente dovrebbe correggere tutti i socket SSL incluso, ma non limitato a HTTPS, per Python 2.6+ (integrato ssl, non provato con 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

Buona risposta. Modo piacevole ed elegante per risolvere il problema.
chnrxn,

3

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

TROVA HTTPS Dichiarazione di classe di connessione

  class HTTPSConnection(HTTPConnection):
....

Codice di classe interno riga CHANGE

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

A

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

Quindi la richiesta HTTPS httplib dovrebbe funzionare

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
Non è davvero giusto modificare un file di sistema in questo modo. Invece, ridefinisci le definizioni che devono essere modificate, ridefinendole nel tuo codice.
Ripristina Monica - ζ--

2

Questo problema è probabilmente dovuto alla disabilitazione di SSLv2 sul server Web, ma Python 2.x tenta di stabilire una connessione con PROTOCOL_SSLv23 per impostazione predefinita.

Ecco il link alla mia risposta per un problema simile su StackTranslate.it - /programming//a/24166498/41957

Aggiornamento: questo è funzionalmente uguale alla risposta di @ temoto sopra.


TypeError: il metodo non associato __init __ () deve essere chiamato con l'istanza SSLSocket come primo argomento (ottenuto invece l'istanza _socketobject)
sureshvv

Hmm, partial () non funziona con i metodi di classe. Pubblicheremo una soluzione migliore a breve.
chnrxn,

@sureshvv, se puoi aiutare a verificare la soluzione, sarà apprezzata.
chnrxn,

La risposta di @ temeto ha funzionato.
sureshvv,

1

Una semplice soluzione che ha funzionato per me è stata quella di sostituire il protocollo predefinito SSL:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

È hacker, ma funziona piuttosto bene nel contesto di oggi. Da quando è stata scoperta la vulnerabilità del barboncino, TLSv1 è diventata praticamente l'unica versione accettabile su Internet.
chnrxn,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.