使用 ftplib 连接到 FTP TLS 1.2 服务器
问题描述
我尝试连接到仅支持 TLS 1.2 的 FTP 服务器使用 Python 3.4.1
I try to connect to a FTP Server which only supports TLS 1.2 Using Python 3.4.1
我的代码:
import ftplib
import ssl
ftps = ftplib.FTP_TLS()
ftps.ssl_version = ssl.PROTOCOL_TLSv1_2
print (ftps.connect('108.61.166.122',31000))
print(ftps.login('test','test123'))
ftps.prot_p()
print (ftps.retrlines('LIST'))
客户端错误:
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:598)
服务器端出错:
Failed TLS negotiation on control channel, disconnected. (SSL_accept():
error:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol)
示例中的凭据正在用于测试.
The credentials in the example are working for testing.
解决方案
最终解决方案见文末.其余的是调试问题所需的步骤.
See the end of this post for the final solution. The rest are the steps needed to debug the problem.
我尝试使用 Python 3.4.1 连接到仅支持 TLS 1.2 的 FTP 服务器
I try to connect to a FTP Server which only supports TLS 1.2 Using Python 3.4.1
你怎么知道的?
ssl.SSLEOFError: EOF 发生违反协议 (_ssl.c:598)
ssl.SSLEOFError: EOF occurred in violation of protocol (_ssl.c:598)
我会建议客户端和服务器之间的许多 SSL 问题之一,例如服务器不支持 TLS 1.2、没有通用密码等.这些问题很难调试,因为您要么只收到一些 SSL 警报,要么服务器将简单地关闭没有任何明显原因的连接.如果您有权访问服务器,请在服务器端查找错误消息.
I would suggest one of the many SSL problems between client and server, like the server not supporting TLS 1.2, no common ciphers etc. These problems are hard to debug because you either get only some SSL alert or the server will simply close the connection without any obvious reason. If you have access to the server look for error messages on the server side.
您也可以尝试不强制使用 SSL 版本,而是使用默认版本,以便客户端和服务器同意两者都支持的最佳 SSL 版本.如果这仍然不起作用,请尝试使用已知与该服务器一起工作的客户端,并捕获好连接和坏连接的数据包并进行比较.如果您需要有关该帖子的帮助,请将数据包捕获发送到 cloudshark.org.
You may also try to not to enforce an SSL version but use the default instead, so that client and server will agree to the best SSL version both support. If this will still not work try with a client which is known to work with this server and make a packet capture of the good and bad connections and compare. If you need help with that post the packet captures to cloudshark.org.
Edit#1:刚刚在测试服务器上使用 python 3.4.0 和 3.4.2 进行了尝试:
Edit#1: just tried it with python 3.4.0 and 3.4.2 against a test server:
- python 3.4.0 执行 TLS 1.0 握手,即忽略设置
- python 3.4.2 成功与 TLS 1.2 握手
在这两个版本中,ftplib 都有一个小错误,如果 ftps.ssl_version
是别的东西,它会发送 AUTH SSL
而不是 AUTH TLS
TLS 1.0,即 SSLv3 或 TLS1.1.+.虽然我怀疑这是问题的根源,但实际上可能是 FTP 服务器以不同方式处理 AUTH TLS
和 AUTH SSL
.
In both versions ftplib has the minor bug, that it sends AUTH SSL
instead of AUTH TLS
if ftps.ssl_version
is something else then TLS 1.0, i.e. SSLv3 or TLS1.1.+. While I doubt that this is the origin of the problem it might actually be if the FTP server handles AUTH TLS
and AUTH SSL
differently.
编辑#2 和解决方案:
数据包捕获显示设置 ftps.ssl_version
无效,SSL 握手仍将仅使用 TLS 1.0 完成.查看 3.4.0 中 ftplib 的源代码:
A packet capture shows that setting ftps.ssl_version
has no effect and the SSL handshake will still be done with TLS 1.0 only. Looking at the source code of ftplib in 3.4.0 gives:
ssl_version = ssl.PROTOCOL_TLSv1
def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
certfile=None, context=None,
timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):
....
if context is None:
context = ssl._create_stdlib_context(self.ssl_version,
certfile=certfile,
keyfile=keyfile)
self.context = context
由于在调用 ftplib.FTP_TLS()
时调用了 __init__
,因此 SSL 上下文将使用 ftplib 使用的默认 ssl_version
创建(ssl.PROTOCOL_TLSv1
) 而不是你自己的版本.要强制执行另一个 SSL 版本,您必须为您自己的上下文提供所需的 SSL 版本.以下对我有用:
Since __init__
is called when ftplib.FTP_TLS()
is called the SSL context will be created with the default ssl_version
used by ftplib (ssl.PROTOCOL_TLSv1
) and not with your own version. To enforce another SSL version you must to provide your own context with the needed SSL version. The following works for me:
import ftplib
import ssl
ctx = ssl._create_stdlib_context(ssl.PROTOCOL_TLSv1_2)
ftps = ftplib.FTP_TLS(context=ctx)
print (ftps.connect('108.61.166.122',31000))
print(ftps.login('test','test123'))
ftps.prot_p()
print (ftps.retrlines('LIST'))
或者,您可以全局设置协议版本,而不仅仅是为此 FTP_TLS 对象:
Alternatively you could set the protocol version globally instead of only for this FTP_TLS object:
ftplib.FTP_TLS.ssl_version = ssl.PROTOCOL_TLSv1_2
ftps = ftplib.FTP_TLS()
还有一个小而重要的观察:看起来 ftplib 不进行任何类型的证书验证,因为它接受了这个与名称不匹配的自签名证书而不会抱怨.这使得主动中间人攻击成为可能.希望他们将来能修复这种不安全的行为,在这种情况下,这里的代码会因为证书无效而失败.
And just a small but important observation: it looks like that ftplib does not do any kind of certificate validation, since it accepts this self-signed certificate which does not match the name without complaining. This makes a active man-in-the-middle attack possible. Hopefully they will fix this insecure behavior in the future, in which case the code here will fail because of an invalid certificate.
相关文章