pyftpdlib 服务器在 PASV/EPSV 模式下重置客户端连接:curl: (13) Bad PASV/EPSV response: 200

问题描述 投票:0回答:1

我正在尝试通过 FTPS 将文件下载到设备。我使用curl(7.88.1)作为远程计算机上的客户端,并使用pyftpdlib(1.5.7)在本地计算机上实现FTP服务器。尝试下载 my_file.txt 时,curl 输出:

curl: (13) Bad PASV/EPSV response: 200

在 Wireshark 中,我可以看到,当客户端尝试在被动端口上建立 TLS 连接时,pyftpdlib 服务器会使用 RST、ACK 数据包响应“客户端问候”。

这是我的 FTP 服务器的基本实现:

import os
import threading

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import TLS_FTPHandler
from pyftpdlib.servers import ThreadedFTPServer


CERTFILE = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                        "certificate.pem"))
KEYFILE = os.path.abspath(os.path.join(os.path.dirname(__file__),
                                        "private_key.pem"))

class FtpSrv:
        def __init__(self):
                self.server = None
                self.server_thread = None

        def start_ftp_server(self, port=2121):
                server_address = ('0.0.0.0', port)
                authorizer = DummyAuthorizer()
                authorizer.add_anonymous(os.getcwd())

                handler = TLS_FTPHandler
                handler.authorizer = authorizer
                handler.banner = "pyftpdlib based ftpd ready."
                handler.passive_ports = [1280]
                handler.certfile = CERTFILE
                handler.keyfile = KEYFILE

                self.server = ThreadedFTPServer(server_address, handler)

                self.server.max_cons = 256
                self.server.max_cons_per_ip = 5

                self.server_thread = threading.Thread(target=self.server.serve_forever, daemon=True)
                self.server_thread.start()

server = FtpSrv()
server.start_ftp_server(port=2128)

然后在远程 Linux 计算机上运行以下curl 命令:

curl --ssl-reqd ftp://xxx.xxx.4.233:2128/myfile.txt -o my_file.txt -k -v

...并查看返回的输出:

curl: (13) Bad PASV/EPSV response: 200

远程计算机上的详细卷曲日志记录以下内容:

curl --ssl-reqd ftp://xxx.xxx.4.233:2128/myfile.txt -o my_file.txt  -k -v
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying xxx.xxx.4.233:2128...
* Connected to xxx.xxx.4.233 (xxx.xxx.4.233) port 2128 (#0)
< 220 pyftpdlib based ftpd ready.
> AUTH SSL
< 234 AUTH SSL successful.
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [6 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [930 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [52 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [52 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* Server certificate:
*  subject: C=XX; ST=Some-State; L=xxx; O=xxx; CN=xxx.xxx.4.233
*  start date: Aug  1 23:59:00 2023 GMT
*  expire date: Jul 31 23:59:00 2024 GMT
*  issuer: C=xx; ST=Some-State; L=xxx; O=xxx; CN=xxx.xxx.4.233
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
} [5 bytes data]
> USER anonymous
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [217 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [217 bytes data]
* old SSL session ID is stale, removing
{ [5 bytes data]
< 331 Username ok, send password.
} [5 bytes data]
> PASS [email protected]
{ [5 bytes data]
< 230 Login successful.
} [5 bytes data]
> PBSZ 0
{ [5 bytes data]
< 200 PBSZ=0 successful.
} [5 bytes data]
> PROT P
{ [5 bytes data]
< 200 Protection set to Private
} [5 bytes data]
> PWD
{ [5 bytes data]
< 257 "/" is the current directory.
* Entry path is '/'
* Request has same path as previous transfer
} [5 bytes data]
> EPSV
* Connect data stream passively
* ftp_perform ends with SECONDARY: 0
{ [5 bytes data]
< 229 Entering extended passive mode (|||1280|).
* Connecting to xxx.xxx.4.233 (xxx.xxx.4.233) port 1280
*   Trying xxx.xxx.4.233:1280...
* Connected to xxx.xxx.4.233 (xxx.xxx.4.233) port 2128 (#0)
* SSL re-using session ID
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [673 bytes data]
> TYPE I
* Recv failure: Connection reset by peer
* OpenSSL SSL_connect: Connection reset by peer in connection to xxx.xxx.4.233:2128
* Failed EPSV attempt. Disabling EPSV
} [5 bytes data]
> PASV
{ [5 bytes data]
< 200 Type set to: Binary.
* Bad PASV/EPSV response: 200
* Remembering we are in dir ""
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
* Connection #0 to host xxx.xxx.4.233 left intact
curl: (13) Bad PASV/EPSV response: 200

pyftpdlib 服务器上的调试日志输出以下内容:

concurrency model: multi-thread
masquerade (NAT) address: None
passive ports: 1280->1280
poller: 'pyftpdlib.ioloop.Select'
authorizer: 'pyftpdlib.authorizers.DummyAuthorizer'
handler: 'pyftpdlib.handlers.type'
max connections: 256
max connections per ip: 5
timeout: 300
banner: 'pyftpdlib based ftpd ready.'
max login attempts: 3
SSL certfile: '.\\certificate.pem'
SSL keyfile: '.\\private_key.pem'
xxx.xxx.4.233:10700-[] FTP session opened (connect)
xxx.xxx.4.233:10700-[] -> 220 pyftpdlib based ftpd ready.
xxx.xxx.4.233:10700-[] <- AUTH SSL
xxx.xxx.4.233:10700-[] -> 234 AUTH SSL successful.
[debug] securing SSL connection (<TLS_FTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700')>)
[debug] call: _do_ssl_handshake, err: ssl-want-read (<TLS_FTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True)>)
[debug] SSL connection established (<TLS_FTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True)>)
xxx.xxx.4.233:10700-[] <- USER anonymous
xxx.xxx.4.233:10700-[] -> 331 Username ok, send password.
xxx.xxx.4.233:10700-[anonymous] <- PASS ******
xxx.xxx.4.233:10700-[anonymous] -> 230 Login successful.
xxx.xxx.4.233:10700-[anonymous] USER 'anonymous' logged in.
xxx.xxx.4.233:10700-[anonymous] <- PBSZ 0
xxx.xxx.4.233:10700-[anonymous] -> 200 PBSZ=0 successful.
xxx.xxx.4.233:10700-[anonymous] <- PROT P
xxx.xxx.4.233:10700-[anonymous] -> 200 Protection set to Private
xxx.xxx.4.233:10700-[anonymous] <- PWD
xxx.xxx.4.233:10700-[anonymous] -> 257 "/" is the current directory.
xxx.xxx.4.233:10700-[anonymous] <- EPSV
xxx.xxx.4.233:10700-[anonymous] -> 229 Entering extended passive mode (|||1280|).
[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening xxx.xxx.4.233:1280 at 0x1efe97835d0>)
[debug] securing SSL connection (<TLS_DTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True, user='anonymous')>)
[debug] call: close() (<pyftpdlib.handlers.PassiveDTP xxx.xxx.4.233:1280 at 0x1efe97835d0>)
xxx.xxx.4.233:10700-[anonymous] <- TYPE I
xxx.xxx.4.233:10700-[anonymous] -> 200 Type set to: Binary.
[debug] call: close() (<TLS_DTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True, user='anonymous', ssl-data=True)>)
xxx.xxx.4.233:10700-[anonymous] <- PASV
xxx.xxx.4.233:10700-[anonymous] -> 227 Entering passive mode (xxx,xxx,4,233,5,0).
xxx.xxx.4.233:10700-[anonymous] <- QUIT
xxx.xxx.4.233:10700-[anonymous] -> 221 Goodbye.
[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening xxx.xxx.4.233:1280 at 0x1efe9783710>)
[debug] call: _do_ssl_shutdown() -> shutdown(), err: zero-return (<TLS_FTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True, user='anonymous')>)
[debug] call: close() (<TLS_FTPHandler(id=2129885457744, addr='xxx.xxx.4.233:10700', ssl=True, user='anonymous')>)
xxx.xxx.4.233:10700-[anonymous] FTP session closed (disconnect).
[debug] closing IOLoop (<pyftpdlib.ioloop.Select (fds=0, tasks=0) at 0x1efe9783050>)

注意: 使用相同的证书和密钥以及被动端口 (1280) 将 Filezilla FTP 服务器配置为“需要基于 TLS 的显式 FTP”。文件传输正常。

python-3.x ssl curl ftps pyftpdlib
1个回答
0
投票

关于您提到的 curl: (13) Bad PASV/EPSV response: 200 错误,需要注意的是,此错误虽然看起来可能出乎意料,但在某些情况下实际上在逻辑上可能是正常的。您遇到的200响应实际上是“TYPE I”回复:< 200 Type set to: Binary。根据 RFC959 规范,PASV 支持的响应通常为 227、500、501、502、421 和 530。

但是,出现的主要问题是当客户端在建立超过 1280 的扩展被动模式 (EPSV) 后发起 TLS 协商时:TLSv1.3 (OUT)、TLS 握手、Client hello (1) 在这种情况下,客户端尝试重新使用会话 ID,而服务器会阻止此特定操作。

作为建议创建一个SSL上下文并启用会话缓存

import ssl
class FtpSrv:
    def __init__(self):
            self.server = None
            self.server_thread = None

    def start_ftp_server(self, port=2121):
            server_address = ('0.0.0.0', port)
            authorizer = DummyAuthorizer()
            authorizer.add_anonymous(os.getcwd())

            handler = TLS_FTPHandler
            handler.authorizer = authorizer
            handler.banner = "pyftpdlib based ftpd ready."
            handler.passive_ports = [1280]
            handler.certfile = CERTFILE
            handler.keyfile = KEYFILE

            

            ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            ssl_context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE)
            ssl_context.set_session_cache_mode(ssl.SESS_CACHE_BOTH)
            handler.tls_context = ssl_context

            self.server = ThreadedFTPServer(server_address, handler)

            self.server.max_cons = 256
            self.server.max_cons_per_ip = 5

            self.server_thread = threading.Thread(target=self.server.serve_forever, daemon=True)
            self.server_thread.start()

之后我们可以看看错误是否仍然发生。

© www.soinside.com 2019 - 2024. All rights reserved.