2

I'm trying to connect to a FTP server behind a firewall that allows incoming connections in the range 6100-6200 only. I have successfully connected to this server using curl like this:

curl --ftp-port :6100-6200 --list-only ftp.server

But I'd like to reproduce the behaviour of this curl command with other clients that are more friendly to use from Python. In principle Linux's ftp, but I'm open to other options if someone suggest a good one. I tried ftplib but it seems that this library does not allow you to select ports; I've tried it unsuccessfully.

Currently I can not make it work with ftp:

230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> ls
227 Entering Passive Mode (XXX,XXX,XXX,XXX,202,251).
ftp: connect: Connection refused

The same set of commands work from my laptop, therefore it seems clear that the problem is the firewall.

How can I force ftp to negociate a data connection in a port in the range 6100-6200, so emulating the behaviour of curl?

AdminBee
  • 21,637
  • 21
  • 47
  • 71
Pythonist
  • 737
  • 7
  • 15
  • why not simply use python's `ftplib`? That's part of the standard python library and certainly better than calling `ftp` and communicating with that process (especially considering how bad FTP as a protocol is, itself, I'd try to avoid every additional layer of uncertainty). – Marcus Müller Jul 20 '21 at 10:38
  • I tried, but it does not work (I posted a question here: https://stackoverflow.com/questions/68374018/connect-to-ftp-via-pythons-ftplib-behind-a-firewall). I'm still trying to understand what's going on, but the problem seems to be that this library does not allow to select the data ports.. – Pythonist Jul 20 '21 at 10:40
  • The answer that said it was impossible to do that is 11 years old and not true anymore, but I see you've tried that, and it failed since things said you couldn't bind to that address. Are we sure that's not the case because you tried to bind to a port that was in use just shortly before and is not ready for re-use yet? – Marcus Müller Jul 20 '21 at 10:43
  • You mean that I was unlucky, but if I retry it could work now? I do not think that's possible, I tried several times. Anyway, if it is possible, how can I achieve that? How can I specify ports then? Maybe you want to reply in the other question? – Pythonist Jul 20 '21 at 10:47
  • 2
    So the thing is, I'm not sure this is what you really want: You are *sure* you want passive mode, right? Because "target server behind firewall" does sound an awful lot like you want active mode. – Marcus Müller Jul 20 '21 at 10:47
  • but: Martin over there at the other question is right, you need to be more clear which firewall restricts which packets, I'm mostly confused at this point! – Marcus Müller Jul 20 '21 at 10:50
  • It would be useful to know if the obfuscated XXX,XXX,XXX,XXX are representing a public IP address (typically of the firewall/router/NAT but this could also be the case without NAT at all) or a private IP address. Then to foresee additional issues, if you are using SSL with FTP or only plain unencrypted FTP. Beside this I can only concur with Marcus Müller that for a server behind a firewall normal/active FTP is easier than passive FTP. – A.B Jul 20 '21 at 11:07
  • @MarcusMüller Well I guess I just don't know whether I want active or passive. Overall, I want to use a FTP client to fetch files, and I wish I hadn't have to care about ftp modes at all (last week I was happy not knowing this concept whatsoever ;p). The fact is that I can fetch files both within `ftplib` and `ftp` in my laptop (no firewall), so I guess I'm successfully using there passive mode... but the same approach fails from a computer where there is a firewall (where connections from other ports than the range 6100-6200 are systematically rejected). – Pythonist Jul 20 '21 at 11:16
  • @A.B this is a fully public IP of a FTP server that even allows anonymous users. I guess I could give the details, but I think they are not especially relevant for the issue. – Pythonist Jul 20 '21 at 11:17
  • Does this ftp server offer the same files via http or https? if so, use one of those instead (note: all or almost all web server admins will have enabled HTTP GET requests, very few will enable HTTP PUT, so this would work for downloading files, but not uploading). – cas Jul 20 '21 at 12:01

1 Answers1

3

When you use FTP in passive mode, the server tells the client which (server-side) data port to use. The well-known FTP protocol includes no way for the client to express requests on which port range to use at the server end. There could be some extensions that could change that, but those are not necessarily widely supported.

In your example, the message

227 Entering Passive Mode (XXX,XXX,XXX,XXX,202,251).

comes directly from the FTP server, as it's telling the client: "I'm listening for a data connection from you at IP address XXX.XXX.XXX.XXX, port 51683" (= 202*256 + 251).

Each TCP connection has two port numbers: a local port number and a remote port number. Usually, an outgoing connection just picks the first free local port in the OS-specified range of ports to be used for outgoing connections, and the remote port is specified according to the service that's being used. In case of passive FTP, the server will pick the remote port according to its configuration and will tell it to the client in the form of a FTP 227 response.

There are generally two ways to handle passive FTP in firewalls:

a) The firewall and the FTP server need both be configured in cooperation to accept/use a specific range of ports for passive FTP data connections, so the server won't even try to select a port the firewall is not going to let through,

or b) the firewall needs to listen in on the FTP command channel traffic, determine the port numbers used for each data connection and dynamically allow passive FTP data connections between the FTP client and server using the port numbers declared on the command channel.

If you are using Linux iptables/netfilter firewall, this is exactly what the protocol-specific conntrack extension module for FTP does. You'll just need to tell it what control connections it's allowed to listen to, since the previous policy of listening on all FTP control connections passing through the firewall system turned out to be exploitable by bad guys, and now such extensions will no longer be used automatically. For details, see this page or this question here on U&L SE.


curl actually uses FTP in passive mode by default, but when you use the --ftp-port option it switches to active mode. From the man page (highlight mine):

-P, --ftp-port

(FTP) Reverses the default initiator/listener roles when connecting with FTP. This option makes curl use active mode. curl then tells the server to connect back to the client's specified address and port, while passive mode asks the server to setup an IP address and port for it to connect to.


Regarding Python and ftplib, note that the question you referred to is more than 10 years old, and there's now a new answer added by Marcus Müller:

Since Python 3.3, ftplib functions that establish connections take a source_addr argument that allows you to do exactly this.

telcoM
  • 87,318
  • 3
  • 112
  • 232
  • Thanks for the explanation. It helped a lot. I realise now that what I was asking makes not much sense. Somehow I wanted to select the *destination* ports, which I now see is the role of the server in passive mode. You could mess with *source* ports, but it makes little sense. Actually, the `source_address` option of `ftplib` is NOT made for this, and therefore it does not work. The reason is that the same port is tried to be used for both control and data channels, which of course raises an exception when actually used. It is designed only for the control channel in active mode. – Pythonist Jul 23 '21 at 07:46