一部のサイトへのPython HTTPSリクエスト(urllib2)がプロキシなしのUbuntu 12.04で失敗する


23

Pythonで作成した小さなアプリがあり、以前は動作していました...昨日、HTTPS接続で突然エラーが表示され始めました。更新があったかどうかは覚えていませんが、Python 2.7.3rc2とPython 3.2はまったく同じように失敗しています。

私はそれをグーグルで調べて、人々がプロキシの後ろにいるときにこれが起こることを発見しましたが、私はそうではありません(そして、それが最後に働いてから私のネットワークでは何も変わっていません)。WindowsとPython 2.7.2を実行しているシステムのコンピューターには問題がありません(同じネットワーク内)。

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

どうしましたか?どんな助けも大歓迎です。

PS .:古いPythonバージョンは、私のシステムでもUSBからのライブセッションでも動作しませんが、Ubuntu 11.10ライブセッションでは動作します。


1
連絡しようとするすべてのSSLサイトで発生しますか?すべてのサイトで発生しない場合は、どのサイトが問題を引き起こしているのか教えてください。
ジェームズヘンストリッジ

まあ、私は経験豊富なプログラマーではなく、サイトのAPIからページを読み込もうとしていますが、それがSSLを必要とする唯一の呼び出しです。 。私は通常のurllib.urlopen(url).read()呼び出しのようにそれを使用してきましたが、機能していました。この質問に答える別のサイトのアドレスまたはpythonスクリプトを教えてください。
パブロ

ああ、私は言及するのを忘れました:サイトはMediafireです。問題を引き起こしているのはget_session_token呼び出しです。
パブロ

私はそのサイトでこれを再現することができました。問題のサイトを含めるように質問を更新しました。wgetも失敗するため、これはOpenSSLの問題であると思われます。
ジェームズ・ヘンストリッジ

これは、執筆時点ではstream.twitter.comで発生します。
MarkR

回答:


15

これは、12.04で見つかったOpenSSLのバージョンへのTLS 1.1および1.2サポートの追加に関連しているようです。接続の失敗は、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
---

-tls1コマンドライン引数でTLS 1.0を使用するように接続を強制すると、接続は成功します。

この問題に関するバグレポートをここに提出することをお勧めします。

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


2
ありがとうございました!バグを報告しました。関連情報を追加できるかどうかを確認してください:bugs.launchpad.net/ubuntu/+source/openssl/+bug/965371
Pablo

1
これは彼がPythonの問題をどのように回避するのに役立ちますか?
セリン

2
@Cerin:問題をPythonの何かではなくOpenSSLバグとして切り分け、バグトラッカーを使用するよう指示しました。その問題はその後修正されました。
ジェームズヘンストリッジ

12

私のようなpython初心者にとって、httplibを最も簡単な方法でオーバーライドする方法は次のとおりです。Pythonスクリプトの先頭に、次の行を含めます。


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

ここからは、urllibまたは通常使用するものを使用できます。

注:これは、Python 2.7用です。Python 3.xソリューションの場合、http.clientにあるHTTPSConnectionクラスをオーバーライドする必要があります。これは読者の演習として残しておきます。:-)


2
私はこのソリューションが本当に好きです。システムライブラリやその他のハッカーを変更する必要がありません。
MarkR

4
Ubuntu 12.04でPython 2.7.4を使用すると失敗します:NameError:name 'socket'が定義されていません。---「インポートソケット」も追加する必要があります。
ベン・ウォルター

Ubuntu 13.04で問題なく動作します。ありがとう!
ダーマテック

2
パッチするだけの理由はありませんhttplib。他のSSLソケットを使用する場合があります。ssl以下の私の答えのように、代わりにパッチを当てることができます。
手本

これは私にエラーを与えるBadStatusLine: ''
Cerin

8

HTTPSConnectionオブジェクトを変更することにより、httplib.pyファイルの変更を回避できます。

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)

connection.sockが定義されていない場合にのみ、リクエストメソッドは新しいソケットを作成します。ssl_versionパラメーターを追加して独自のものを作成すると、リクエストメソッドがそれを使用するようになります。その後、他のすべてが通常どおり機能します。

私は同じ問題を抱えていましたが、これは私にとってはうまくいきます。

よろしく


7

問題はであるssl、それはなぜパッチ適用、HTTPとは何の関係もありません、httplibあなたがパッチを適用できるかどうかssl。次のコードは、Python 2.6+のHTTPSを含むすべてのSSLソケットを修正する必要があります(組み込み、sslで試しませんでした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

いい答えだ。問題を解決する素敵でエレガントな方法。
chnrxn

3

httplib.pyの編集(Linuxでは/usr/lib/pythonX.X/httplib.py)

FIND HTTPSConnectionクラス宣言

  class HTTPSConnection(HTTPConnection):
....

クラスコード内のCHANGE行

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

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

その後、httplib HTTPSリクエストが機能するはずです

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
そのようなシステムファイルを編集することは本当に正しくありません。代わりに、コードで定義を変更して、変更する必要がある定義を再定義してください
–ζ

2

この問題は、WebサーバーでSSLv2が無効になっていることが原因である可能性がありますが、Python 2.xはデフォルトでPROTOCOL_SSLv23との接続を確立しようとします。

スタックオーバーフローに関する同様の問題に対する私の回答へのリンクを次に示します-/programming//a/24166498/41957

更新:これは機能的には上記の@temotoの回答と同じです。


TypeError:非バインドメソッド__init __()は、SSLSocketインスタンスを最初の引数として呼び出す必要があります(代わりに_socketobjectインスタンスを取得します)
sureshvv

うーん、partial()はクラスメソッドでは機能しません。すぐにより良いソリューションを投稿します。
chnrxn

@sureshvv、ソリューションの確認にご協力いただければ幸いです。
chnrxn

@temetoの答えはうまくいきました。
sureshvv

1

私のために働いた簡単な修正は、SSLのデフォルトプロトコルを上書きすることでした:

import ssl
ssl.PROTOCOL_SSLv23 = ssl.PROTOCOL_TLSv1

それはハックですが、今日の状況ではかなりうまく機能します。プードルの脆弱性が発見されて以来、TLSv1はほとんどインターネットで受け入れられる唯一のバージョンになりました。
chnrxn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.