Net::FTP stops trusting the host address in passive responses
CVE-2021-31810 lets a malicious FTP server direct a client's data connection toward another address. The corrected default uses the control connection's peer address while retaining the negotiated data port.
An FTP client can connect to the intended server and still be redirected elsewhere when it opens the data connection. Ruby's July 7 advisory for CVE-2021-31810 describes that trust problem in Net::FTP. A malicious server can use its passive-mode response to make the client contact an address and port selected by the server.
The advisory identifies possible probing of services that are private from the attacker's perspective, including port scans and banner extraction. The client supplies the network position needed to reach those services. Accepting the original FTP connection does not imply that the remote server should gain authority to select arbitrary further destinations reachable by the client.
I like Ruby's approachable networking interfaces, but a convenient client method still needs a precise rule for interpreting server-supplied endpoints. This fix makes that rule narrower. The address for a passive data connection comes from the control peer by default, while the response continues to supply the data port.
Passive mode supplies a second endpoint
The corrected Net::FTP implementation shows the relevant sequence. When passive mode is active, transfercmd obtains a host and port through makepasv, opens a socket and then sends the transfer command. For an IPv4 control connection, makepasv issues PASV and parses the server's 227 response.
That response includes address and port fields. The parser recognizes four comma-separated address components followed by two port components. The port calculation combines the latter components by shifting the first eight bits and adding the second. For the test values twelve and thirty-four, the resulting port is 3106.
The vulnerable trust relationship concerns the host selection. A valid-looking response can contain an address belonging to another service. If the client uses it unquestioningly, the server determines where the client opens its next connection. The response's syntactic validity does not establish authority over that destination.
The corrected parser retains the response's port but chooses the host differently. Unless explicitly configured otherwise, it uses the IP address of the existing control socket's peer. This ties the new connection's host to the endpoint already participating in the FTP session.
The new default is visible in the constructor
The released source documents use_pasv_ip as an option and defaults it to false. With false, the host comes from the control connection. With true, the parser uses the address in the PASV response. The source therefore exposes a compatibility choice instead of silently deleting the ability to honor the supplied host.
The following constructor illustrates the corrected default without opening a network connection. No host is supplied, so this example only constructs the client configuration.
require "net/ftp"
ftp = Net::FTP.new(nil, use_pasv_ip: false)
The option is an address-trust decision. Setting it to true permits the server's reply to supply the host again. It should not be treated as an interchangeable spelling for passive mode itself. The passive option selects the transfer arrangement, while use_pasv_ip selects how one response field influences the destination.
Using the established peer also does not make every possible transfer safe. The server still selects a port on that peer, and the FTP session retains its other protocol behavior. The fix removes the default ability to redirect the client's data connection to an unrelated address through this field. It is a specific restriction, not a general guarantee about all network effects of an FTP operation.
Tests distinguish parsing from destination policy
The release's FTP tests exercise both configurations. With use_pasv_ip enabled, a response containing a private address returns that supplied address and the decoded port. This checks that the explicit compatibility option retains its documented behavior.
For the default configuration, the test installs a synthetic socket peer address. It parses the same kind of response with a different supplied host and expects the socket peer to win. That assertion directly tests the trust rule. A test checking only the numeric port would leave the security-relevant destination unchanged and undetected.
The suite also rejects malformed PASV responses. Wrong reply codes produce a reply error, while a response missing the expected fields produces a protocol error. These checks address different properties from host policy. A response can be well formed and still contain an address the client should not trust.
The distinction is especially important for diagnosing a successful connection to the wrong service. No parser exception is required for that failure. All fields can have the expected shape, and socket creation can succeed. The error lies in granting destination authority to the response. Changing the host-selection policy therefore addresses a condition that stricter field formatting alone would not resolve.
The implementation uses EPSV for a non-IPv4 peer in this path. Its separate parser takes the port from the response and the host from the control socket. Reading the branches helps locate the corrected PASV behavior precisely instead of describing every passive response form as identical.
The published update route is the interpreter
The advisory lists Ruby 2.6.7 and earlier in the 2.6 series, 2.7.3 and earlier in the 2.7 series, and 3.0.1 and earlier in the 3.0 series as affected. It explicitly recommends updating Ruby itself. Although net-ftp is a default gem in Ruby 3.0.1, the advisory identifies a packaging issue that prevents treating a gem-only update as the prescribed remedy.
The same day's Ruby 3.0.2 release announcement includes this vulnerability among its security fixes. That connects the source behavior to a released interpreter update. It also gives operators a concrete distribution containing the correction instead of leaving them to infer availability from a repository change.
I regard the peer-address default as an appropriate limit on the server's authority. An FTP server can negotiate a transfer without gaining general access to the client's network routing position. The corrected implementation preserves that distinction by separating the negotiated port from the independently established control peer address.