Skip to content

Commit 1901831

Browse files
committed
Don't allow separate transfer hosts by default
1 parent 980371b commit 1901831

3 files changed

Lines changed: 15 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ client.ftp.verbose = true
6969

7070
Create a client instance. Configure it with a timeout in milliseconds that will be used for any connection made. Use 0 to disable timeouts, default is 30 seconds. Options are:
7171

72-
- `allowSeparateTransferHost (boolean)`, the FTP spec makes it possible for a server to tell the client to use a different IP address for file transfers than for the initial control connection. Today, this feature is very rarely used. Still, the default for this is set to `true` for backwards-compatibility reasons. If you experience any issues with NAT traversal in local networks or want to provide more security and prevent FTP bounce attacks, set this to `false`.
72+
- `allowSeparateTransferHost (boolean)`, the FTP spec makes it possible for a server to tell the client to use a different IP address for file transfers than for the initial control connection. This is a potential vector for FTP bounce attacks, so by default this is set to `false` and the library will throw an error if a server tries to redirect transfers to a different host. Set this to `true` only if you are connecting to a server that legitimately requires it.
7373

7474
`close()`
7575

src/Client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ export interface ClientOptions {
6060
}
6161

6262
const defaultClientOptions: ClientOptions = {
63-
allowSeparateTransferHost: true,
63+
/** For security reasons this library should not allow separate transfer hosts by default. */
64+
allowSeparateTransferHost: false,
6465
maxListingBytes: 40 * 1024 * 1024
6566
}
6667
const LIST_COMMANDS_DEFAULT = () => ["LIST -a", "LIST"]

src/transfer.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ export async function enterPassiveModeIPv4(ftp: FTPContext): Promise<FTPResponse
6464
}
6565

6666
/**
67-
* Prepare a data socket using passive mode over IPv4. Ignore the IP provided by the PASV response,
68-
* and use the control host IP. This is the same behaviour as with the more modern variant EPSV. Use
69-
* this to fix issues around NAT or provide more security by preventing FTP bounce attacks.
67+
* Prepare a data socket using passive mode over IPv4.
68+
*
69+
* Will throw an error if the IP provided by the PASV response doesn't match the one of the control connection.
70+
* The error will contain detailed information. This is done to provide more security by preventing FTP bounce
71+
* attacks.
7072
*/
7173
export async function enterPassiveModeIPv4_forceControlHostIP(ftp: FTPContext): Promise<FTPResponse> {
7274
const res = await ftp.request("PASV")
@@ -78,7 +80,13 @@ export async function enterPassiveModeIPv4_forceControlHostIP(ftp: FTPContext):
7880
if (controlHost === undefined) {
7981
throw new Error("Control socket is disconnected, can't get remote address.")
8082
}
81-
await connectForPassiveTransfer(controlHost, target.port, ftp)
83+
// Strip IPv4-mapped IPv6 prefix (e.g. "::ffff:1.2.3.4" → "1.2.3.4") so the
84+
// comparison works regardless of whether the OS uses a dual-stack socket.
85+
const normalizedControlHost = controlHost.replace(/^::ffff:/i, "")
86+
if (normalizedControlHost !== target.host) {
87+
throw new Error(`PASV returned another host (${target.host}) for data transfer that you have connected to (${controlHost}). Even though the FTP protocol allows this, basic-ftp disables this feature by default for security reasons. If you do need this feature, instantiate the Client with the optional paramter "allowSeparateTransferHost: true". See the README documentation for more information.`)
88+
}
89+
await connectForPassiveTransfer(normalizedControlHost, target.port, ftp)
8290
return res
8391
}
8492

0 commit comments

Comments
 (0)