491

It's very annoying to have this limitation on my development box, when there won't ever be any users other than me.

I'm aware of the standard workarounds, but none of them do exactly what I want:

  1. authbind (The version in Debian testing, 1.0, only supports IPv4)
  2. Using the iptables REDIRECT target to redirect a low port to a high port (the "nat" table is not yet implemented for ip6tables, the IPv6 version of iptables)
  3. sudo (Running as root is what I'm trying to avoid)
  4. SELinux (or similar). (This is just my dev box, I don't want to introduce a lot of extra complexity.)

Is there some simple sysctl variable to allow non-root processes to bind to "privileged" ports (ports less than 1024) on Linux, or am I just out of luck?

EDIT: In some cases, you can use capabilities to do this.

tomix86
  • 1,336
  • 2
  • 18
  • 29
Jason C
  • 21,377
  • 10
  • 38
  • 33
  • 6
    In my experience, one reason for attempting this is to write a web server rather than use Apache (or lighttpd). – S.Lott Jan 05 '09 at 17:45
  • I've added the setcap stuff to my answer to the nearly identical http://stackoverflow.com/questions/277991/linux-how-to-run-a-server-on-port-80-as-normal-user – Paul Tomblin Jan 05 '09 at 20:07
  • Why does this have the IPv6 tag? – james woodyatt Mar 29 '10 at 20:36
  • 16
    Because in this instance, I was using IPv6, which was why some of the "usual" workarounds (authbind and iptables REDIRECT) didn't work for me. – Jason C Mar 30 '10 at 02:51
  • On CentOS 5, ip6tables v1.3.5 doesn't support the [new NAT table](http://mirrors.bieringer.de/Linux+IPv6-HOWTO/nat-netfilter6..html). Is there an any solution using ip6tables at all? I'd prefer to avoid additional software as mentioned in several answers below. – Stefan Lasiewski Oct 24 '14 at 01:15
  • 2
    http://serverfault.com/questions/112795/how-can-i-run-a-server-on-linux-on-port-80-as-a-normal-user – Ciro Santilli OurBigBook.com Dec 24 '14 at 14:43
  • 3
    There are a few ways to do it. See [Allow non-root process to bind to port 80 and 443?](http://superuser.com/q/710253/173513) on Super User. – jww Feb 19 '17 at 17:51
  • I’m voting to close this question because From the iptables tag: IPTABLES SUPPORT IS OFF-TOPIC. [What topics can I ask about here?](https://stackoverflow.com/help/on-topic) Support questions may be asked on https://superuser.com. Use this tag only for questions on programming with iptables. Questions about configuring iptables should be asked on Server Fault (https://serverfault.com/). Please delete this. – Rob May 30 '23 at 11:29

25 Answers25

498

Okay, thanks to the people who pointed out the capabilities system and CAP_NET_BIND_SERVICE capability. If you have a recent kernel, it is indeed possible to use this to start a service as non-root but bind low ports. The short answer is that you do:

setcap 'cap_net_bind_service=+ep' /path/to/program

And then anytime program is executed thereafter it will have the CAP_NET_BIND_SERVICE capability. setcap is in the debian package libcap2-bin.

Now for the caveats:

  1. You will need at least a 2.6.24 kernel
  2. This won't work if your file is a script. (i.e. uses a #! line to launch an interpreter). In this case, as far I as understand, you'd have to apply the capability to the interpreter executable itself, which of course is a security nightmare, since any program using that interpreter will have the capability. I wasn't able to find any clean, easy way to work around this problem.
  3. Linux will disable LD_LIBRARY_PATH on any program that has elevated privileges like setcap or suid. So if your program uses its own .../lib/, you might have to look into another option like port forwarding.

Resources:

Note: RHEL first added this in v6.

StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
Jason C
  • 21,377
  • 10
  • 38
  • 33
  • I think you should add a bit of discussion of the setuid and inetd/xinetd options, and make this the accepted answer. – Paul Tomblin Jan 08 '09 at 15:55
  • As for #2, I did find a way to deal with it. The interperter is a CAP-loader that is suid-root and interprets the CAP-flags on its target file. Clean, effective, but hard. – Joshua Jan 26 '09 at 05:32
  • 5
    Partial workaround for scripts: create a copy of the interpreter (e.g. bash), give it the capablities but restrict access to the copy to those users who need it. Of course, those users must be trusted, but they could change the script anyway. – Erich Kitzmueller Nov 19 '09 at 12:08
  • 4
    Aside from the aforementioned debian (binary) package, the developer's site is http://www.friedhoff.org/posixfilecaps.html associated papers/presentations/etc... – RandomNickName42 Jan 16 '10 at 15:34
  • have CONFIG_SECURITY_CAPABILITIES enabled – f3r3nc Dec 08 '10 at 21:52
  • 1
    Would this cause the same problem with the Java runtime as it would with a script? – C. Ross Feb 02 '11 at 17:52
  • 1
    on suse SLES 11.1 I had to add the kernel param file_caps=1 to grub menu.lst for this to work. – Shay Nov 10 '11 at 13:14
  • 3
    Yes @C.Ross since it would have to be applied to `/usr/bin/java` and then would open the capability to any java app running on the system. Too bad capabilities cannot also be set per-user. – joeytwiddle Aug 15 '13 at 11:02
  • 10
    Does the setcap setting persist across reboots; if not is there a standard place to put this rule so that is it run during system startup? Is `/etc/security/capability.conf` on Debian/Ubuntu any help? – joeytwiddle Aug 15 '13 at 11:06
  • 1
    This answer isn't the best way to go, see the first comment to [this answer](http://stackoverflow.com/a/9128979/1781435) (the correct answer, I think), for why. – Greg Slepak Feb 03 '14 at 20:54
  • what is the benefit to this and not just doing a port forward ? – Tegra Detra May 30 '14 at 16:17
  • 1
    if you want to add the capability directly in the code, then here's a proof of concept https://github.com/simonemainardi/cap_net_bind_service_test – simonemainardi Jun 28 '16 at 14:41
  • If you're comfortable with the implications, you can use this answer to set capabilities for your current python interpreter with `setcap 'cap_net_bind_service=+ep' $(readlink -f $(which python))` . I would recommend against doing that unless in a virtualenv, i.e. if you need sudo, reconsider. – gens Mar 01 '18 at 18:05
  • 2
    One more caveat: `setcap` applies a filesystem attribute, so it won't work if the fileserver is a read-only file, and the attribute gets dropped if the fileserver is recompiled. – Elle Fie Nov 19 '20 at 20:50
  • It solves the port issue for TFTP but when I applied it to the python3, it caused permissions errors while building Yocto. Resolved by `sudo setcap 'cap_net_bind_service=-ep' /path/to/python3` – Daniel Selvan Mar 29 '22 at 07:27
  • 2
    @JamesAndino port forward suffers from the original issue, another program can race on boot to take over the port. – eglasius Jan 18 '23 at 11:34
  • @ElleFie does that mean that one needs to re-apply the setcap everytime the target program changes? – eglasius Jan 18 '23 at 11:35
58

Update 2017:

Use authbind

Disclaimer (update per 2021): Note that authbind works via LD_PRELOAD, which is only used if your program uses libc, which is (or might) not be the case if your program is compiled with GO, or any other compiler that avoids C. If you use go, set the kernel parameter for the protected port range, see bottom of post. </EndUpdate>

Authbind is much better than CAP_NET_BIND_SERVICE or a custom kernel.

  • CAP_NET_BIND_SERVICE grants trust to the binary but provides no control over per-port access.
  • Authbind grants trust to the user/group and provides control over per-port access, and supports both IPv4 and IPv6 (IPv6 support has been added as of late).
  1. Install: apt-get install authbind

  2. Configure access to relevant ports, e.g. 80 and 443 for all users and groups:

    sudo touch /etc/authbind/byport/80
    sudo touch /etc/authbind/byport/443
    sudo chmod 777 /etc/authbind/byport/80
    sudo chmod 777 /etc/authbind/byport/443

  3. Execute your command via authbind
    (optionally specifying --deep or other arguments, see man authbind):

         authbind --deep /path/to/binary command line args
    
     e.g.  
    
         authbind --deep java -jar SomeServer.jar
    

As a follow-up to Joshua's fabulous (=not recommended unless you know what you do) recommendation to hack the kernel:

I've first posted it here.

Simple. With a normal or old kernel, you don't.
As pointed out by others, iptables can forward a port.
As also pointed out by others, CAP_NET_BIND_SERVICE can also do the job.
Of course CAP_NET_BIND_SERVICE will fail if you launch your program from a script, unless you set the cap on the shell interpreter, which is pointless, you could just as well run your service as root...
e.g. for Java, you have to apply it to the JAVA JVM

sudo /sbin/setcap 'cap_net_bind_service=ep' /usr/lib/jvm/java-8-openjdk/jre/bin/java

Obviously, that then means any Java program can bind system ports.
Ditto for mono/.NET.

I'm also pretty sure xinetd isn't the best of ideas.
But since both methods are hacks, why not just lift the limit by lifting the restriction ?
Nobody said you have to run a normal kernel, so you can just run your own.

You just download the source for the latest kernel (or the same you currently have). Afterwards, you go to:

/usr/src/linux-<version_number>/include/net/sock.h:

There you look for this line

/* Sockets 0-1023 can't be bound to unless you are superuser */
#define PROT_SOCK       1024

and change it to

#define PROT_SOCK 0

if you don't want to have an insecure ssh situation, you alter it to this:

#define PROT_SOCK 24

Generally, I'd use the lowest setting that you need, e.g. 79 for http, or 24 when using SMTP on port 25.

That's already all.
Compile the kernel, and install it.
Reboot.
Finished - that stupid limit is GONE, and that also works for scripts.

Here's how you compile a kernel:

https://help.ubuntu.com/community/Kernel/Compile

# You can get the kernel-source via package `linux-source`, no manual download required
apt-get install linux-source fakeroot

mkdir ~/src
cd ~/src
tar xjvf /usr/src/linux-source-<version>.tar.bz2
cd linux-source-<version>

# Apply the changes to PROT_SOCK define in /include/net/sock.h

# Copy the kernel config file you are currently using
cp -vi /boot/config-`uname -r` .config

# Install ncurses libary, if you want to run menuconfig
apt-get install libncurses5 libncurses5-dev

# Run menuconfig (optional)
make menuconfig

# Define the number of threads you wanna use when compiling (should be <number CPU cores> - 1), e.g. for quad-core
export CONCURRENCY_LEVEL=3
# Now compile the custom kernel
fakeroot make-kpkg --initrd --append-to-version=custom kernel-image kernel-headers

# And wait a long long time

cd ..

In a nutshell,

  • use iptables if you want to stay secure,
  • compile the kernel if you want to be sure this restriction never bothers you again.

sysctl method

Note:
As of late, updating the kernel is no longer required.
You can now set

sysctl net.ipv4.ip_unprivileged_port_start=80

Or to persist

sysctl -w net.ipv4.ip_unprivileged_port_start=80.

And if that yields an error, simply edit /etc/sysctl.conf with nano and set the parameter there for persistence across reboots.

or via procfs

echo 80 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Any reason for the downvote ? Root-hater, or did the kernel-compile instructions not work ? – Stefan Steiger Jun 25 '15 at 14:46
  • 11
    modifying the kernel makes future updates much more painful. i wouldn't do this, because i know i would be too lazy to keep updating my kernel regularly. it's nice that it's possible in theory, but it's not a viable option in a lot of circumstances. – kritzikratzi Jul 06 '15 at 14:21
  • Maybe a more secure way to do it is to change the chmod 777 /etc/authbind/byport/80 by chmod 544 /etc/authbind/byport/23 than chown login:group /etc/authbind/byport/23 – Jérôme B Jun 14 '18 at 12:29
  • 2
    `authbind`, works by hacking `LD_PRELOAD`, which does not work with GoLang webservers (https://www.reddit.com/r/golang/comments/cqlpnq/cant_use_authbind_to_allow_binding_to_port_80/ewyluyh). – Elle Fie Nov 19 '20 at 20:48
  • 1
    For running a quick dev server with `flask` and `werkzeug` serving content on port 80, `authbind` is money. Thank you. – dmn May 11 '22 at 15:11
48

You can do a port redirect. This is what I do for a Silverlight policy server running on a Linux box

iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 943 -j REDIRECT --to-port 1300
FlappySocks
  • 3,772
  • 3
  • 32
  • 33
  • 7
    Unfortunately this will only work for routed connections, i.e. not from the local machine. – zbyszek Feb 03 '12 at 11:52
  • @joeytwiddle I think it's in the script that runs (starts) your server, but I might be wrong. I'd also like to know :P – Camilo Martin Jul 09 '14 at 22:06
  • @CamiloMartin Looking again, the [Debian documentation](http://www.debian-administration.org/articles/386) which was linked from the question recommends placing it in your firewall script, or creating one at `/etc/network/if-up.d/firewall`. – joeytwiddle Jul 10 '14 at 08:08
  • 9
    @zbyszek This can work for local machine connections, with an additional iptables rule: http://stackoverflow.com/a/31795603/1356953 – 00500005 Aug 03 '15 at 19:51
  • In older kernels it seems [this wasn't supported for IPv6](http://stackoverflow.com/a/4911503/60075). But apparently it is supported in ip6tables v1.4.18 and Linux kernel v3.8. – Craig McQueen Sep 10 '15 at 01:58
  • To work for conections from the local machine, add an `OUTPUT` rule also. e.g. `iptables -t nat -I OUTPUT -o lo -p tcp --dport 943 -j REDIRECT --to-port 1300` — see [iptables port redirect not working for localhost](https://serverfault.com/a/211544/10513) – Craig McQueen Jun 21 '17 at 02:58
  • One problem with this is that it also redirects forwarded packets. E.g. if the machine does forwarding from Wi-Fi to Ethernet, it will redirect all port 80 connections when Wi-Fi clients try to connect to web servers on the Internet. But I found [you can make it only affect packets addressed to the box](https://serverfault.com/a/827932/10513), by adding `-m addrtype --dst-type LOCAL` – Craig McQueen Jun 22 '17 at 06:07
  • another problem with this is that it defeats the purpose of the original protection of privilege ports: a malicious program can race against your program on boot to take over your port – eglasius Jan 18 '23 at 11:40
45

For some reason no one mention about lowering sysctl net.ipv4.ip_unprivileged_port_start to the value you need. Example: We need to bind our app to 443 port.

sysctl net.ipv4.ip_unprivileged_port_start=443

Some may say, there is a potential security problem: unprivileged users now may bind to the other privileged ports (444-1024). But you can solve this problem easily with iptables, by blocking other ports:

iptables -I INPUT -p tcp --dport 444:1024 -j DROP
iptables -I INPUT -p udp --dport 444:1024 -j DROP

Comparison with other methods. This method:

  • from some point is (IMO) even more secure than setting CAP_NET_BIND_SERVICE/setuid, since an application doesn't setuid at all, even partly (capabilities actually are). For example, to catch a coredump of capability-enabled application you will need to change sysctl fs.suid_dumpable (which leads to another potential security problems) Also, when CAP/suid is set, /proc/PID directory is owned by root, so your non-root user will not have full information/control of running process, for example, user will not be able (in common case) to determine which connections belong to application via /proc/PID/fd/ (netstat -aptn | grep PID).
  • has security disadvantage: while your app (or any app that uses ports 443-1024) is down for some reason, another app could take the port. But this problem could also be applied to CAP/suid (in case you set it on interpreter, e.g. java/nodejs) and iptables-redirect. Use systemd-socket method to exclude this problem. Use authbind method to only allow special user binding.
  • doesn't require setting CAP/suid every time you deploy new version of application.
  • doesn't require application support/modification, like systemd-socket method.
  • doesn't require kernel rebuild (if running version supports this sysctl setting)
  • doesn't do LD_PRELOAD like authbind/privbind method, this could potentially affect performance, security, behavior (does it? haven't tested). In the rest authbind is really flexible and secure method.
  • over-performs iptables REDIRECT/DNAT method, since it doesn't require address translation, connection state tracking, etc. This only noticeable on high-load systems.

Depending on the situation, I would choose between sysctl, CAP, authbind and iptables-redirect. And this is great that we have so many options.

urusha
  • 644
  • 7
  • 5
  • 7
    Thanks for the great answer! It appears that this functionality first appeared in [Linux 4.11 in April 2017](https://kernelnewbies.org/Linux_4.11), so it wasn't around in 2009 when I first asked this question. I also did a quick test, and it seems to also work for IPV6, even though "ipv4" is in the sysctl name. – Jason C Jul 26 '18 at 01:34
37

Or patch your kernel and remove the check.

(Option of last resort, not recommended).

In net/ipv4/af_inet.c, remove the two lines that read

      if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
              goto out;

and the kernel won't check privileged ports anymore.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • 31
    A Very Bad Idea for a multitude of reasons. – Adam Lassek Jan 05 '09 at 22:25
  • 36
    This is bad idea. But would actually work. And sure made me laugh. – Oto Brglez Dec 10 '12 at 19:18
  • 35
    Of course it's a bad idea. That's why I said last resort. The point of open source is if it doesn't work the way you want you can change it. – Joshua Dec 10 '12 at 22:21
  • 29
    I'm not sure why you were downvoted. Malware writers don't care what port they listen on, and they are more than happy to open a port greater than 1024. It seems to me that all ports should require privileges, or no ports should require privileges. And requiring root to open a port below 1024 just means you have a high risk application running as root. That seems like a *really* dumb idea to me. Perhaps I'm missing something here... – jww Feb 02 '14 at 09:20
  • 14
    Back in the day, port < 1024 was used in UNIX to UNIX protocols to prove the code running on the other end was running as root. This functioned reasonably well for a set of UNIX servers with common security. – Joshua Feb 02 '14 at 16:22
  • 16
    This isn't a bad idea. The modern world doesn't work like UNIX did in the 70s, and as jww noted, the number of security issues it prevents (virtually none) isn't worth forcing web servers etc. to run as root, even if they drop privaledges correctly. There's a [good explanation of why the 1024 limit is obsolete here](http://www.staldal.nu/tech/2007/10/31/why-can-only-root-listen-to-ports-below-1024/). – Timmmm Jul 27 '15 at 09:00
  • Some more details on where to find the relevant check might make this an actual answer.... (Capabilities is a better idea though...) – Gert van den Berg Jan 21 '19 at 07:57
  • 1
    @GertvandenBerg: You are right. This answer feels kind of old now. When it wrote it, setting capabilities on a file didn't work reliably yet. Nevertheless, I have a real point to make. – Joshua Jan 22 '19 at 05:07
34

The standard way is to make them "setuid" so that they start up as root, and then they throw away that root privilege as soon as they've bound to the port but before they start accepting connections to it. You can see good examples of that in the source code for Apache and INN. I'm told that Lighttpd is another good example.

Another example is Postfix, which uses multiple daemons that communicate through pipes, and only one or two of them (which do very little except accept or emit bytes) run as root and the rest run at a lower privilege.

Kev
  • 118,037
  • 53
  • 300
  • 385
Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • 1
    Interestingly, this doesn't work under recent versions of Linux (maybe just Ubuntu) without CAP_SETUID set. So if you need setuid you're going to have to set this capability anyway. – hookenz Feb 19 '13 at 04:12
  • Dropping root privs is the right way to do this. Though it's not necessary to set setuid if you have init/upstart/systemd start the service. – Michael Hampton Feb 09 '14 at 16:34
  • 2
    This is difficult if the program is written in an interpreted language or bytecode interpreter such as C# (Mono), Java, Python. (Apparently Perl has done it via [`binfmt_misc`](https://www.kernel.org/doc/Documentation/binfmt_misc.txt) and its 'C' flag; I'm not sure about others.) – Craig McQueen May 14 '15 at 23:15
  • very little except emit bytes, that's not doing very little. – Ryan Jul 07 '16 at 18:09
  • If you set a binary setuid, it will run as a root process. The question, however, asks how to accomplish it without running your binary as root process. This solution is the answer to a different question, namely: "How can a non-priviledged user have a process bind to a priviledged port", but this is not the question posed, IMHO? – Michael Beer May 21 '19 at 10:41
  • The setuid method is the only thing I found that works on OS X Catalina (10.15). For example, to run `npm run serve --port 80` effectively, you would run `sudo chmod u+s $(which npm)`. – datico Mar 31 '21 at 21:27
31

Modern Linux supports /sbin/sysctl -w net.ipv4.ip_unprivileged_port_start=0.

Gene McCulley
  • 1,097
  • 1
  • 10
  • 15
25

You can setup a local SSH tunnel, eg if you want port 80 to hit your app bound to 3000:

sudo ssh $USERNAME@localhost -L 80:localhost:3000 -N

This has the advantage of working with script servers, and being very simple.

Gabriel Burt
  • 807
  • 8
  • 4
  • 1
    My personal favorite. Very easy to turn on and off and requires no system-wide changes. Great idea! – Dan Passaro Sep 22 '16 at 13:00
  • This is amazing. By far the simplest answer. – michaelsnowden Mar 29 '17 at 09:47
  • 2
    I would expect this to be moderately expensive and not a good idea in any performance bound situation, but I can't be sure without testing it. SSH encryption costs a nonzero amount. – lahwran Jul 31 '18 at 20:25
  • 8
    This could be done with netcat with no ssh overhead: `sudo nc -l 80 | nc localhost 3000` – Bugster Aug 05 '18 at 05:56
  • 2
    You are encrypting + decrypting only for moving the data to another port on the same machine? Also, this does not work for UDP traffic. – Daniel F Dec 24 '18 at 17:54
21

I know this is an old question, but now with recent (>= 4.3) kernels there is finally a good answer to this - ambient capabilities.

The quick answer is to grab a copy of the latest (as-yet-unreleased) version of libcap from git and compile it. Copy the resulting progs/capsh binary somewhere (/usr/local/bin is a good choice). Then, as root, start your program with

/usr/local/bin/capsh --keep=1 --user='your-service-user-name' \
    --inh='cap_net_bind_service' --addamb='cap_net_bind_service' \ 
    -- -c 'your-program'

In order, we are

  • Declaring that when we switch users, we want to keep our current capability sets
  • Switching user & group to 'your-service-user-name'
  • Adding the cap_net_bind_service capability to the inherited & ambient sets
  • Forking bash -c 'your-command' (since capsh automatically starts bash with the arguments after --)

There's a lot going on under the hood here.

Firstly, we are running as root, so by default, we get a full set of capabilities. Included in this is the ability to switch uid & gid with the setuid and setgid syscalls. However, ordinarily when a program does this, it loses its set of capabilities - this is so that the old way of dropping root with setuid still works. The --keep=1 flag tells capsh to issue the prctl(PR_SET_KEEPCAPS) syscall, which disables the dropping of capabilities when changing user. The actual changing of users by capsh happens with the --user flag, which runs setuid and setgid.

The next problem we need to solve is how to set capabilities in a way that carries on after we exec our children. The capabilities system has always had an 'inherited' set of capabilities, which is " a set of capabilities preserved across an execve(2)" [capabilities(7)]. Whilst this sounds like it solves our problem (just set the cap_net_bind_service capability to inherited, right?), this actually only applies for privileged processes - and our process is not privileged anymore, because we already changed user (with the --user flag).

The new ambient capability set works around this problem - it is "a set of capabilities that are preserved across an execve(2) of a program that is not privileged." By putting cap_net_bind_service in the ambient set, when capsh exec's our server program, our program will inherit this capability and be able to bind listeners to low ports.

If you're interested to learn more, the capabilities manual page explains this in great detail. Running capsh through strace is also very informative!

KJ Tsanaktsidis
  • 1,255
  • 1
  • 17
  • 28
  • 1
    The `--addamb` flag was introduced with libcap 2.26. My system had 2.25 and I had to build from source. – ngreen Jan 19 '19 at 03:40
  • 1
    This is the answer! Thank you for the detailed explanation. Like @ngreen, I had to compile capsh from source, but otherwise it worked beautifully. – Elle Fie Nov 19 '20 at 20:45
19

File capabilities are not ideal, because they can break after a package update.

The ideal solution, IMHO, should be an ability to create a shell with inheritable CAP_NET_BIND_SERVICE set.

Here's a somewhat convoluted way to do this:

sg $DAEMONUSER "capsh --keep=1 --uid=`id -u $DAEMONUSER` \
     --caps='cap_net_bind_service+pei' -- \
     YOUR_COMMAND_GOES_HERE"

capsh utility can be found in libcap2-bin package in Debian/Ubuntu distributions. Here's what goes on:

  • sg changes effective group ID to that of the daemon user. This is necessary because capsh leaves GID unchanged and we definitely do not want it.
  • Sets bit 'keep capabilities on UID change'.
  • Changes UID to $DAEMONUSER
  • Drops all caps (at this moment all caps are still present because of --keep=1), except inheritable cap_net_bind_service
  • Executes your command ('--' is a separator)

The result is a process with specified user and group, and cap_net_bind_service privileges.

As an example, a line from ejabberd startup script:

sg $EJABBERDUSER "capsh --keep=1 --uid=`id -u $EJABBERDUSER` --caps='cap_net_bind_service+pei' -- $EJABBERD --noshell -detached"
Cyberax
  • 1,667
  • 16
  • 18
  • And it actually does NOT work. Caps are not preserved over the user ID switch. It will work if you want to run as root, but with all capabilities dropped. – Cyberax Dec 18 '14 at 14:04
  • If I try that, I get "cannot execute binary file". In fact, I can't get `capsh` to do _anything_ other than "cannot execute binary file" when using the `--` or `==` options. I wonder what I'm missing. – Craig McQueen May 14 '15 at 01:03
  • @CraigMcQueen, everything after `--` is passed on to `/bin/bash`, so you may want to try with `-c 'your command'`. Alas, I seem to be experiencing the same problem as @Cyberax, because I get "permission denied" when trying to `bind`. – Amir Dec 26 '15 at 21:03
  • For this to work without setting file capabilities (or running as root), you would need to use [ambient capabilities](https://lwn.net/Articles/636533/) as described in [this Unix.SE answer](https://unix.stackexchange.com/a/303738). You could also use `--gid` instead of `sg` (or `--user` which sets `--uid`, `--gid`, and `--groups`): `sudo capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' --keep=1 --user="$service_user" --addamb=cap_net_bind_service -- -c 'exec $service $service_args'` – Kevinoid Dec 06 '18 at 17:35
16

Two other simple possibilities: Daemon and Proxy

Daemon

There is an old (unfashionable) solution to the "a daemon that binds on a low port and hands control to your daemon". It's called inetd (or xinetd).

The cons are:

  • your daemon needs to talk on stdin/stdout (if you don't control the daemon -- if you don't have the source -- then this is perhaps a showstopper, although some services may have an inetd-compatibility flag)
  • a new daemon process is forked for every connection
  • it's one extra link in the chain

Pros:

  • available on any old UNIX
  • once your sysadmin has set up the config, you're good to go about your development (when you re-build your daemon, might you lose setcap capabilities? And then you'll have to go back to your admin "please sir...")
  • daemon doesn't have to worry about that networking stuff, just has to talk on stdin/stdout
  • can configure to execute your daemon as a non-root user, as requested

Proxy

Another alternative: a hacked-up proxy (netcat or even something more robust) from the privileged port to some arbitrary high-numbered port where you can run your target daemon. (Netcat is obviously not a production solution, but "just my dev box", right?). This way you could continue to use a network-capable version of your server, would only need root/sudo to start proxy (at boot), wouldn't be relying on complex/potentially fragile capabilities.

StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
Martin Carpenter
  • 5,893
  • 1
  • 28
  • 32
  • 1
    Good suggestion. For some reason I didn't even think of using inetd. Except that the service in question is UDP-based, so it's slightly more complicated than TCP services. I have a solution working right now with setcap, but I'll have to give this some thought. – Jason C Jan 06 '09 at 03:06
  • For the second option, you can likely even start the proxy using inetd... (But IPTables is likely lower overhead...) – Gert van den Berg Jan 21 '19 at 07:59
16

My "standard workaround" uses socat as the user-space redirector:

socat tcp6-listen:80,fork tcp6:8080

Beware that this won't scale, forking is expensive but it's the way socat works.

Astro
  • 858
  • 1
  • 8
  • 8
14

Linux supports capabilities to support more fine-grained permissions than just "this application is run as root". One of those capabilities is CAP_NET_BIND_SERVICE which is about binding to a privileged port (<1024).

Unfortunately I don't know how to exploit that to run an application as non-root while still giving it CAP_NET_BIND_SERVICE (probably using setcap, but there's bound to be an existing solution for this).

Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
13

TLDR: For "the answer" (as I see it), jump down to the >>TLDR<< part in this answer.

OK, I've figured it out (for real this time), the answer to this question, and this answer of mine is also a way of apologizing for promoting another answer (both here and on twitter) that I thought was "the best", but after trying it, discovered that I was mistaken about that. Learn from my mistake kids: don't promote something until you've actually tried it yourself!

Again, I reviewed all the answers here. I've tried some of them (and chose not to try others because I simply didn't like the solutions). I thought that the solution was to use systemd with its Capabilities= and CapabilitiesBindingSet= settings. After wrestling with this for some time, I discovered that this is not the solution because:

Capabilities are intended to restrict root processes!

As the OP wisely stated, it is always best to avoid that (for all your daemons if possible!).

You cannot use the Capabilities related options with User= and Group= in systemd unit files, because capabilities are ALWAYS reset when execev (or whatever the function is) is called. In other words, when systemd forks and drops its perms, the capabilities are reset. There is no way around this, and all that binding logic in the kernel is basic around uid=0, not capabilities. This means that it is unlikely that Capabilities will ever be the right answer to this question (at least any time soon). Incidentally, setcap, as others have mentioned, is not a solution. It didn't work for me, it doesn't work nicely with scripts, and those are reset anyways whenever the file changes.

In my meager defense, I did state (in the comment I've now deleted), that James' iptables suggestion (which the OP also mentions), was the "2nd best solution". :-P

>>TLDR<<

The solution is to combine systemd with on-the-fly iptables commands, like this (taken from DNSChain):

[Unit]
Description=dnschain
After=network.target
Wants=namecoin.service

[Service]
ExecStart=/usr/local/bin/dnschain
Environment=DNSCHAIN_SYSD_VER=0.0.1
PermissionsStartOnly=true
ExecStartPre=/sbin/sysctl -w net.ipv4.ip_forward=1
ExecStartPre=-/sbin/iptables -D INPUT -p udp --dport 5333 -j ACCEPT
ExecStartPre=-/sbin/iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
ExecStartPre=/sbin/iptables -A INPUT -p udp --dport 5333 -j ACCEPT
ExecStartPre=/sbin/iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
ExecStopPost=/sbin/iptables -D INPUT -p udp --dport 5333 -j ACCEPT
ExecStopPost=/sbin/iptables -t nat -D PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 5333
User=dns
Group=dns
Restart=always
RestartSec=5
WorkingDirectory=/home/dns
PrivateTmp=true
NoNewPrivileges=true
ReadOnlyDirectories=/etc

# Unfortunately, capabilities are basically worthless because they're designed to restrict root daemons. Instead, we use iptables to listen on privileged ports.
# Capabilities=cap_net_bind_service+pei
# SecureBits=keep-caps

[Install]
WantedBy=multi-user.target

Here we accomplish the following:

  • The daemon listens on 5333, but connections are successfully accepted on 53 thanks to iptables
  • We can include the commands in the unit file itself, and thus we save people headaches. systemd cleans up the firewall rules for us, making sure to remove them when the daemon isn't running.
  • We never run as root, and we make privilege escalation impossible (at least systemd claims to), supposedly even if the daemon is compromised and sets uid=0.

iptables is still, unfortunately, quite an ugly and difficult-to-use utility. If the daemon is listening on eth0:0 instead of eth0, for example, the commands are slightly different.

Community
  • 1
  • 1
Greg Slepak
  • 1,613
  • 16
  • 17
  • 2
    Nobody should be using old-style aliases like `eth0:0` anymore unless they have a _really ancient_ Linux distribution. They have been deprecated for years and will eventually go away. – Michael Hampton Feb 09 '14 at 16:32
  • Michael, I think you'll find (if you click the link) that I was using `eth0:0` as a short way of saying "a public static IP on the same NIC", and in that sense, it is fairly common to see this on VPS setups with extra IPs (for example, SolusVM). – Greg Slepak Feb 09 '14 at 17:38
  • 1
    I think you mean OpenVZ. (SolusVM is a control panel.) And yes, OpenVZ does a lot of things wrong, networking being just one of them. – Michael Hampton Feb 09 '14 at 17:40
  • 1
    Nah, I mean by SolusVM. From /etc/network/interfaces: `# Generated by SolusVM` – Greg Slepak Feb 09 '14 at 18:22
  • Right, but SolusVM has to generate something that OpenVZ can work with. – Michael Hampton Feb 09 '14 at 18:25
  • 1
    Still not OpenVZ... At least I'm not using it, nor is my VPS. – Greg Slepak Feb 09 '14 at 18:29
  • In that case, yes, SolusVM is the problem. – Michael Hampton Feb 09 '14 at 18:30
  • 1
    Thank your for your concise explanation of why I couldn't get my prototype service running with capabilities and a non-root user. I was convinced I was doing something wrong, when it turns out that the capabilities functionality is useless for me! – Anthony Giorgio Jun 24 '14 at 02:45
  • 12
    Thank you for pointing out the capabilities feature of systemd. However there is no need for complex iptables when systemd starts the binary directly (not a script). Setting `AmbientCapabilities=CAP_NET_BIND_SERVICE` worked perfectly fine for me in combination with `User=`. – Ruud Sep 07 '16 at 21:31
  • While I appreciate you effort to research and combine different alternatives to a proper solution, it doesn't have much value if you don't explain what should be altered in your config, as well as explaining how it should actually be used. – Forage Nov 29 '16 at 13:12
  • 1
    @Forage I thought that was pretty clear. The main thing to change are the port numbers. The other things that need changing are obvious (e.g. the binary you're using, the working directory, user/group settings, etc.). If you understand systemd unit files, it should be clear. – Greg Slepak Dec 01 '16 at 01:51
12

systemd is a sysvinit replacement which has an option to launch a daemon with specific capabilities. Options Capabilities=, CapabilityBoundingSet= in systemd.exec(5) manpage.

zbyszek
  • 5,105
  • 1
  • 27
  • 22
  • 3
    I previously recommended this answer, but after trying it, now I do not. See [my answer](http://stackoverflow.com/a/21653102/1781435) for an alternative that still uses `systemd`. – Greg Slepak Feb 08 '14 at 23:30
  • But I'm running on WSL2, which no native systemctl support :( – nuclear Jan 23 '21 at 14:20
11

With systemd, you just need to slightly modify your service to accept preactivated sockets.

You can later use systemd socket activate.

No capabilities, iptables or other tricks are needed.

This is content of relevant systemd files from this example of simple python http server

File httpd-true.service

[Unit]
Description=Httpd true 

[Service]
ExecStart=/usr/local/bin/httpd-true
User=subsonic

PrivateTmp=yes

File httpd-true.socket

[Unit]
Description=HTTPD true

[Socket]
ListenStream=80

[Install]
WantedBy=default.target
j123b567
  • 3,110
  • 1
  • 23
  • 32
10

Port redirect made the most sense for us, but we ran into an issue where our application would resolve a url locally that also needed to be re-routed; (that means you shindig).

This will also allow you to be redirected when accessing the url on the local machine.

iptables -A PREROUTING -t nat -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -A OUTPUT -t nat -p tcp --dport 80 -j REDIRECT --to-port 8080
00500005
  • 3,727
  • 3
  • 31
  • 38
7

At startup:

iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080

Then you can bind to the port you forward to.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Tegra Detra
  • 24,551
  • 17
  • 53
  • 78
4

There is also the 'djb way'. You can use this method to start your process as root running on any port under tcpserver, then it will hand control of the process to the user you specify immediately after the process starts.

#!/bin/sh

UID=$(id -u username)
GID=$(id -g username)
exec tcpserver -u "${UID}" -g "${GID}" -RHl0 0 port /path/to/binary &

For more info, see: http://thedjbway.b0llix.net/daemontools/uidgid.html

evandrix
  • 6,041
  • 4
  • 27
  • 38
mti2935
  • 11,465
  • 3
  • 29
  • 33
4

Bind port 8080 to 80 and open port 80:

sudo iptables -t nat -A OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

and then run program on port 8080 as a normal user.

you will then be able to access http://127.0.0.1 on port 80

assayag.org
  • 709
  • 10
  • 24
  • Better to encapsulate this in some script or application because it may not end well for the inexperienced messing with iptables. Best to also add commands to drop the rules for reversing this setup – TheRealChx101 Jan 05 '22 at 08:55
  • setcap 'cap_net_bind_service=+ep' /root/anaconda3/bin/python3.8 setcap 'cap_net_bind_service=+ep' /root/anaconda3/bin/ipython – CS QGB Aug 29 '22 at 06:19
3

Use the privbind utility: it allows an unprivileged application to bind to reserved ports.

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • `privbind`, like `authbind`, works by hacking `LD_PRELOAD`, which does not work with GoLang webservers (https://www.reddit.com/r/golang/comments/cqlpnq/cant_use_authbind_to_allow_binding_to_port_80/ewyluyh). – Elle Fie Nov 19 '20 at 20:48
1

Since the OP is just development/testing, less than sleek solutions may be helpful:

setcap can be used on a script's interpreter to grant capabilities to scripts. If setcaps on the global interpreter binary is not acceptable, make a local copy of the binary (any user can) and get root to setcap on this copy. Python2 (at least) works properly with a local copy of the interpreter in your script development tree. No suid is needed so the root user can control to what capabilities users have access.

If you need to track system-wide updates to the interpreter, use a shell script like the following to run your script:

#!/bin/sh
#
#  Watch for updates to the Python2 interpreter

PRG=python_net_raw
PRG_ORIG=/usr/bin/python2.7

cmp $PRG_ORIG $PRG || {
    echo ""
    echo "***** $PRG_ORIG has been updated *****"
    echo "Run the following commands to refresh $PRG:"
    echo ""
    echo "    $ cp $PRG_ORIG $PRG"
    echo "    # setcap cap_net_raw+ep $PRG"
    echo ""
    exit
}

./$PRG $*
StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
duanev
  • 936
  • 13
  • 17
1

I tried the iptables PREROUTING REDIRECT method. In older kernels it seems this type of rule wasn't supported for IPv6. But apparently it is now supported in ip6tables v1.4.18 and Linux kernel v3.8.

I also found that PREROUTING REDIRECT doesn't work for connections initiated within the machine. To work for conections from the local machine, add an OUTPUT rule also — see iptables port redirect not working for localhost. E.g. something like:

iptables -t nat -I OUTPUT -o lo -p tcp --dport 80 -j REDIRECT --to-port 8080

I also found that PREROUTING REDIRECT also affects forwarded packets. That is, if the machine is also forwarding packets between interfaces (e.g. if it's acting as a Wi-Fi access point connected to an Ethernet network), then the iptables rule will also catch connected clients' connections to Internet destinations, and redirect them to the machine. That's not what I wanted—I only wanted to redirect connections that were directed to the machine itself. I found I can make it only affect packets addressed to the box, by adding -m addrtype --dst-type LOCAL. E.g. something like:

iptables -A PREROUTING -t nat -p tcp --dport 80 -m addrtype --dst-type LOCAL -j REDIRECT --to-port 8080

One other possibility is to use TCP port forwarding. E.g. using socat:

socat TCP4-LISTEN:www,reuseaddr,fork TCP4:localhost:8080

However one disadvantage with that method is, the application that is listening on port 8080 then doesn't know the source address of incoming connections (e.g. for logging or other identification purposes).

Craig McQueen
  • 41,871
  • 30
  • 130
  • 181
0

Answer at 2015/Sep:

ip6tables now supports IPV6 NAT: http://www.netfilter.org/projects/iptables/files/changes-iptables-1.4.17.txt

You will need kernel 3.7+

Proof:

[09:09:23] root@X:~ ip6tables -t nat -vnL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 REDIRECT   tcp      eth0   *       ::/0                 ::/0                 tcp dpt:80 redir ports 8080
    0     0 REDIRECT   tcp      eth0   *       ::/0                 ::/0                 tcp dpt:443 redir ports 1443

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 6148 packets, 534K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 6148 packets, 534K bytes)
 pkts bytes target     prot opt in     out     source               destination
HVNSweeting
  • 2,859
  • 2
  • 35
  • 30
0

There is a worked example of doing this with a file capable shared library linked to an unprivileged application on the libcap website. It was recently mentioned in an answer to a question about adding capabilities to shared libraries.

Tinkerer
  • 865
  • 7
  • 9