19

I'd like to accept connections briefly for development when I'm NATed, and so I'm attempting to do this:

$ ssh [email protected] -R 80:localhost:80

Which fails as I'm trying to bind a port that is to low:

Warning: remote port forwarding failed for listen port 80

So I've discovered that I can do setcap 'cap_net_bind_service=+ep' /my/application to allow it to listen to ports lower than 1024. So I've got this in my suders crontab:

@reboot setcap 'cap_net_bind_service=+ep' /usr/sbin/sshd

But it's still not letting me bind on port 80. What am I doing wrong? I'm just going to use nginx to proxy to 8080 or iptables or something instead, but I'm still curious why what I was trying to do didn't work.

Kit Sunde
  • 4,394
  • 10
  • 30
  • 34
  • What filesystem are you using? Not all filesystems support setcap. – Gilles 'SO- stop being evil' Jun 24 '12 at 01:20
  • @Gilles I'm using ext4, it's the default Ubuntu image off AWS and I haven't done anything fancy with it. – Kit Sunde Jun 24 '12 at 07:03
  • It sounds as if you are root on the box. Maybe I'm ignorant but shouldn't just executing the first line as root solve your problem? – Christian Jun 25 '12 at 07:26
  • @Christian You can't bind on <1024 without `sudo` by design. Like how you cannot `apt-get install foo` without first doing `sudo`. – Kit Sunde Jun 25 '12 at 08:23
  • @KitSunde Yeah, root is an un-Ubuntuic way of saying `sudo`. It's a user that has all the privileges that a normal user only gets when doing `sudo`. root is also sometimes called the superuser. So basically my question to you was: You seem to be able to do `sudo` on the box, why can't you in this case? – Christian Jun 25 '12 at 12:14
  • @Christian I'm not sure if it's possible to run the equivalent of `$ ssh [email protected] -R 80:localhost:80` in a way where it would run as superuser on the remote (without connecting to a user that is always rooted in any event), since the command is executing on my local machine, but one of the bindings is happening on the remote. Running it as root on my local machine doesn't solve the issue where the remote doesn't want me to bind to `80`. I'd welcome a solution to that though. – Kit Sunde Jun 25 '12 at 12:57
  • I'm still not sure if I get it. If you have root on both boxen, why don't you connect to some port on the remote machine and if it needs to be port 80 there, you set up a second tunnel with superuser privileges on that machine. That can be scripted of course and you can also do it with ssh or with netcat. But probably I'm still not getting it :/ – Christian Jun 25 '12 at 14:01
  • @Christian Right so setting up a second tunnel from `80 -> 8000` on the remote machine is how I've worked around the inability to do it directly from remote `80` on the unprivileged ubuntu user (since I can't seem to root `ubuntu` when making the connection) to local `80`. However it should be possible to use `setcap` and allow an unprivileged user to bind on <1024 on a specific, so then the second loopback tunnel on the remote from `80 -> 8000` wouldn't be necessary. I have the whole thing setup on my end with what you are suggesting, that's a non-issue. :) – Kit Sunde Jun 25 '12 at 14:30
  • I see. Sorry for the misunderstanding in that case. I'm afraid I have no experience with `setcap` but good luck in finding someone who does :) – Christian Jun 25 '12 at 20:14
  • Shouldn't it be `/usr/bin/ssh` instead of `/usr/sbin/sshd` in your `setcap` command? – ByteNudger Jul 15 '12 at 11:34
  • @ByteNudger `ssh` is the client for `sshd` as I understand it. `sshd` is the application on the server that would do the actual binding. – Kit Sunde Jul 15 '12 at 14:35

6 Answers6

23

As explained in @dwurf's accepted answer, ssh will only bind to ports less than 1024 for the root user.

This is a great use case for socat if you are not root.

First, do a remote forward to port 8080 (or any other allowed port) of the remote machine:

ssh [email protected] -R 8080:localhost:80

Then on the remote machine, map port 80 to port 8080:

sudo socat TCP-LISTEN:80,fork TCP:localhost:8080

Notes:

As suggested by Dirk Hoffman, these two commands can be combined into a single line:

ssh -t [email protected] -R 8080:localhost:80 sudo socat TCP-LISTEN:80,fork TCP:localhost:8080

(The -t is necessary in case you need an interactive terminal to enter your sudo password.)

Ben Mares
  • 461
  • 1
  • 4
  • 8
  • 1
    brilliant! love it! – Dirk Hoffmann Apr 10 '20 at 17:09
  • 1
    also possible to execute both in one command like so: ssh -t -i ~/.ssh/id_rsa -R 0.0.0.0:8080:0.0.0.0:8080 user@remoteMachine -- "sudo socat TCP-LISTEN:80,fork TCP:localhost:8080" – Dirk Hoffmann Apr 10 '20 at 17:38
  • Using `socat` command, I get `2020/08/14 19:25:31 socat[386] E bind(5, {AF=2 0.0.0.0:80}, 16): Address already in use` and docker container says in ports`0.0.0.0:8080->8080/tcp`, and I didn't get any error while running your commands, but still on my laptop's browser(`https://localhost:80/`), it still can access the packets. [Running this container on server](https://github.com/virtualzone/docker-container-stats). Any suggestion,what is going wrong? – Anu Aug 15 '20 at 02:33
  • Hi @Anu, my guess is that another service (e.g. Apache or nginx) is already using port 80. I would check [what program is using the port](https://askubuntu.com/questions/278448/how-to-know-what-program-is-listening-on-a-given-port) and, assuming it's not essential, shut it down. – Ben Mares Aug 16 '20 at 14:58
  • `ssh -N -f -L localhost:8080:localhost:8080 remote_user@remote_host`. This worked for me – Anu Aug 17 '20 at 23:26
20

OpenSSH will flat-out refuse to bind to privileged ports unless the user id of the logged in user is 0 (root). The relevant lines of code are:

if (!options.allow_tcp_forwarding ||
    no_port_forwarding_flag ||
    (!want_reply && listen_port == 0) ||
    (listen_port != 0 && listen_port < IPPORT_RESERVED &&
    pw->pw_uid != 0)) {
        success = 0;
        packet_send_debug("Server has disabled port forwarding.");

Source: http://www.openssh.com/cgi-bin/cvsweb/src/usr.bin/ssh/serverloop.c?annotate=1.162 lines 1092-1098

If you're curious, pw is of type struct passwd * and on linux is defined in /usr/include/pwd.h

dwurf
  • 392
  • 2
  • 6
6

I meet the similar problem, so the solution I ended up is to add DNAT rule to the OUTPUT chain of nat table:

iptables -t nat -A OUTPUT -d 127.0.0.0/8 -p tcp --dport 80 \
-j DNAT --to-destination :8080

This rule effectively replaces the destination port 80 with 8080 for all locally generated tcp packets.

If you wish to allow any incoming connections to be forwarded as well then add one extra rule to the PREROUTING nat chain:

iptables -t nat -A PREROUTING -d 10.0.0.200 -p tcp --dport 80 \
-j REDIRECT --to-port 8080

where 10.0.0.200 is the IP address of the interface that should be forwarding incoming connections to your web service

Serge
  • 8,371
  • 2
  • 23
  • 28
1

From the client end, you can run ssh via sudo:

sudo ssh -L 80:127.0.0.1:80 [email protected]

It works, although I'm admittedly not sure about the security implications for your local system.

1

Elaborating on the solution proposal of Ben Mares above,

below is a one liner which:

opens two remote port forwards:
1. remote port 8888 to local port 80
2. remote port 8443 to local port 443

on the remote machine socat connects anything
1. arriving on port  80 to be streamed to port 8888
   which is then tunneled to local host port  80
2. arriving on port 443 to be streamed to port 8443
   which is then tunneled to local host port 443

ssh -t -i ~/.ssh/id_rsa \
    -R 0.0.0.0:8888:0.0.0.0:80 \
    -R 0.0.0.0:8443:0.0.0.0:443 \
    remoteUser@remoteMachine \
    -- "(sudo socat TCP-LISTEN:80,fork TCP:localhost:8888) & \
        sudo socat TCP-LISTEN:443,fork TCP:localhost:8443"

so, if you execute the command, you have to give your root password of the remote machine (to be able to list on ports 80/443) and then (as long as the tunnel is established) anything that arrives on port 80 or 443 of the remote host, will be tunneled to your local machine ports 80 or 443 respectively...

remember!!

for being able to bind on all network interfaces (0.0.0.0) you have to edit your remote machines /etc/ssh/sshd_config and set GatewayPorts clientspecified or GatewayPorts yes

you can check this on the remote machine with netstat -tlpn | grep -E '8888|8443' which should show:

tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8443            0.0.0.0:*               LISTEN      -

and not

tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8443          0.0.0.0:*               LISTEN      -
  • Hi Dirk, I just saw your answer. If I correctly understand your command, then incoming external traffic arriving at the remote host will be tunneled on not only for ports 80 and 443, but also for 8888 and 8443. I would guess that in most cases those additional ports are not desired. And the GatewayPorts would only apply to 8888/8443, right? – Ben Mares Aug 16 '20 at 15:18
  • actually the ssh remote port forwards are from remote ports 8888+8443 to your local machine ports 80+443. the socat redirects are from external ports 80+443 (of the remote host) to remote hosts ports 8888+8443. if you have remote ports 8888+8443 open for external incoming traffic is up to you... – Dirk Hoffmann Aug 16 '20 at 16:40
  • But assuming there is no firewall, then 8888 and 8443 are open for external incoming traffic on the remote machine, right? And the GatewayPorts setting is enabling the external incoming traffic, right? In other words, I believe that if instead you did `-R 127.0.0.1:8888:0.0.0.0:80`, then you wouldn't need to deal with GatewayPorts, and you wouldn't be exposing 8888. Wouldn't that be better? – Ben Mares Aug 16 '20 at 16:45
  • but you cannot tell socat to forward to a remote port, so you need to forward to a local port which then is tunneled to your local machine. Plus you cannot let ttwo apps (ssh AND socat listen on the same port) – Dirk Hoffmann Aug 16 '20 at 16:48
  • and the whole thread is about the fact THAT YOU ARE NOT ALLOWED to do a rwmote port forward an ports <1024 – Dirk Hoffmann Aug 16 '20 at 16:56
  • Hi Dirk, sorry to be vague earlier, I wasn't at my computer, so I couldn't try any experiments. My goal is to understand why your answer requires changing GatewayPorts, but my answer does not. To bind to ports bigger than 1024, you can adjust GatewayPorts to enable binding on all interfaces, so in that case it makes sense, but you don't need socat. To bind to ports smaller than 1024 without root, you cannot bind directly via ssh, so you need to use socat. But with socat, the connection to ssh comes from localhost, so it makes no sense to adjust GatewayPorts. Am I missing something? – Ben Mares Aug 16 '20 at 20:33
  • well, it is an answer to the (original) question (almost) which works, and with most things there isn't just one or even a best answer, so feel free to post an answer that solves the problem in another way... :) – Dirk Hoffmann Aug 18 '20 at 09:28
0

You could also just add your public key to the /root/.ssh/authorized_keys list. If you are paranoid then you will want to prohibit running of any commands as root like:

no-pty,no-agent-forwarding,no-X11-forwarding,command="/bin/noshell" ssh-rsa YOUR_PUBLIC_KEY 

Note that logging in as root may require adding "PermitRootLogin without-password" in /etc/ssh/sshd_config.

Daniel
  • 33
  • 3
moschops
  • 101