I am expecting the following command to fail with a permission exception because it is running as an unprivileged user. But instead, it appears to succeed.
% docker run --rm -u nobody alpine nc -l 0.0.0.0 443
% docker exec -it b2b471d05398 sh
~ $ id
uid=65534(nobody) gid=65534(nobody)
~ $ ps
PID USER TIME COMMAND
1 nobody 0:00 nc -l 0.0.0.0 443
8 nobody 0:00 sh
15 nobody 0:00 ps
~ $ %
I have tried to disable various capabilities, but still, none of this prevents the nc
from running successfully and binding to the port.
docker run --rm -u nobody --cap-drop=SETUID --cap-drop=NET_BIND_SERVICE --cap-drop=SETFCAP --cap-drop=NET_RAW alpine nc -l 0.0.0.0 443
In response to David Maze’s answer,
I built an image with Debian GNU/Linux
.
Here is the Dockerfile
:
FROM python:slim-buster
EXPOSE 80
USER nobody
CMD python -m http.server 80
The docker build command
docker build -t test .
Still able to bind on a privileged port by non-root user
docker run --rm test
I also tried this for dropping the capability:
docker run --rm --cap-drop=SETUID --cap-drop=NET_BIND_SERVICE --cap-drop=SETFCAP --cap-drop=NET_RAW test
Any idea what should I do to remove the capability and trigger the error that I am trying to reproduce?
2
Answers
TL;DR:
include option
--sysctl "net.ipv4.ip_unprivileged_port_start=1024"
in your commandLong answer:
Introduction
in docker docs it is stated that
this does not refer to the user you are intending to use inside the container but to your ${USER}. It is important because that will grant all the things docker can do!
However while nobody is not a proper user that can logon the uid exists and docker will just run the program with that uid. see here
Testing the stated issue
I tried the netcat issue, but I did not find any proper resources on the alpine nc. Testing it seemed to not properly open ports. 🤷
The python server gives more insight when testing.
If you try the following you will see that the security is properly working for the host network.
correctly permission denied:
granted for root ( as you can see from the introduction, you are root equivalent ):
But yes the "issue" exists for bridges:
Explanation
Looking around you can find the docker bridge interface is not included in the port opening limitation. Please see issue merge.
And as usual looking at their tests you will find "the answer" to your underlying question.
Permission denied in all 3 flavors with bridge
And here it is granted correctly for root (a privileged user, while demanding privileged user for access)
Alpine is based on a minimal tool set called BusyBox, which contains its own implementation of many standard command-line utilities. The
nc
syntax that BusyBox supports iswhere
-l
sets up "listen" mode and-p 443
indicates the listening port. (Indeed, without the-p
option, I’d expect BusyBoxnc
to exit immediately with a-l
option and two positional arguments, and then you wouldn’t be able todocker exec
into the container.)Without a
-p
option, BusyBoxnc
will pick an arbitrary port and print it to stderr. That won’t be a privileged port, which is why you’re not getting an error there.docker logs
should show the port number, andnetstat
inside the container should show the listener on the alternate port.