wifidog / wifidog-gateway

Repository for the wifidog-gateway captive portal designed for embedded systems
GNU General Public License v2.0
755 stars 317 forks source link

Investigate use of CAP_NET_ADMIN #88

Open mhaas opened 9 years ago

mhaas commented 9 years ago

We don't really have to run as UID 0 except to execute iptables.

mhaas commented 9 years ago

Example code: http://www.lst.de/~okir/blackhats/node125.html

acv commented 9 years ago

Can we rely on capabilities being available on the embedded linuxes?

So we'd need CAP_NET_RAW, CAP_NET_BIND_SERVICE, and CAP_NET_ADMIN.

Dunno if the capabilities pas down to children. If they do, then that should work as-is, if they don't we might need to learn how to speak iptables to the kernel direct.

acv commented 9 years ago

Some users may find that this breaks logging since wifidog may not have the permission to open its log file anymore.

mhaas commented 9 years ago

The caps pass down to children if you set the inheritables correctly, I believe. We probably don't need CAP_NET_BIND_SERVICE unless you want to listen on ports < 1024. 2600 is the default, I believe. Since wifidog can restart itself (IIRC), we need to make sure that keeps working. Also, make sure any sockets keep working, i.e. that the permissions are set up correctly.

Perhaps we can chown the files before setuid(nobody). Or we can warn the user - some breakage can be expected when upgrading.

acv commented 9 years ago

Good point... I forgot that we rely on firewall rules and don't listen on actual 80.

mhaas commented 9 years ago

Looking into this further, it seems we should use libcap (instead of the raw syscalls). libcap.so comes in at 18kb on my i386 system. This suggests that capability usage should be optional, like SSL.

I wonder if it's possible to restrict CAP_NET_ADMIN to CAP_INHERITABLE so that only child processes (e.g. iptables) get that capability.

mhaas commented 9 years ago

I'm playing around in https://github.com/mhaas/wifidog-gateway/tree/feature-capabilities

mhaas commented 9 years ago

It turns out that iptables must be run as root. That is quite inconvenient.

sudo capsh --gid=1000 --uid=1000 --caps=cap_net_admin-ep -- -c "iptables -X input"

As an alternative strategy, we could: 1) remain UID 0, but drop unneeded capabilities 2) use seteuid(nobody) and switch back to root before executing iptables (see man 2 setuid)

I wonder how "bad" strategy 1) would be. man 7 capabilities indicates that on execve, the started process gets all privileges if the real user id is 0. Capabilities are probably not that useful if you can easily regain lost ones, though :(

mhaas commented 9 years ago

Hum, iptables works if I set the capabilites on the executable.

laga@moar:~/dev/wifidog-gateway$ getcap ./iptables-cap ./iptables-cap = cap_net_admin,cap_net_raw+eip laga@moar:~/dev/wifidog-gateway$ ./iptables-cap iptables -X input iptables: No chain/target/match by that name.

mhaas commented 9 years ago

Alright, the code in my branch is working now. I have not tested the wdctl restart feature.

Here's the kicker: the iptables executable needs to have some special bits granted which basically say "yo, it's cool if we inherit these caps":

setcap cap_net_admin,cap_net_raw+ei /usr/bin/xtables-multi

That's.. unfortunate.

mhaas commented 9 years ago

See man 7 capabilities, the Transformation of capabilities during execve() section in particular. Or this thread on stackoverflow

mhaas commented 9 years ago

It looks like OpenWRT does not support extended attributes on ext4 OOTB. That is unfortunate, as we won't be able to call iptables. As there is no iptables API, we essentially can't use capabilities.

What we can do, however, is seteuid() and then switch back to UID 0 when we have to do privileged stuff. This way, we won't have to link against libcap.

mhaas commented 9 years ago

Thinking about this some more, we can probably combine both ideas.

seteuid is problematic because an attacker could, theoretically, regain all privileges. We can limit that by granting capabilities as previously described. The kicker is that the real UID is still 0, so the child process (iptables) inherits the capabilities properly without requiring file-based capabilities. Additionally, we wouldn't have to switch back to UID 0 for the iptables call to work.

This is pretty much still crap, because the hypothetical attacker could still write whatever into /etc/rc.local and have it executed as root. Running wifidog inside a chroot would help ameloriate this, except that you can easily break out of chroot AFAICT if you are root.

Anyways, dropping privileges with seteuid does prevent some classes of attacks. Proper privilege separation would help here. The slave process never runs as root in this example and a chroot would probably not be strictly necessary.

Learning about this stuff is fun, but I wonder why the APIs have to be so.. almost-but-not-quite-broken.

mhaas commented 9 years ago

Update: running jailed as uid0 with the minimal set of capabilities is indeed safe. The chroot capability is needed to break out of the jail.

shashankmjain commented 6 years ago

Hi , I am trying the iptables via a non-root user exactly similar to the case you have. I run the command sudo capsh --uid=1002 --caps=cap_net_admin-eip -- -c "/home/jain_sm/testip" where testip is the executable executing iptables.

Also I do setcap CAP_NET_RAW,CAP_NET_ADMIN=ei /sbin/xtables-multi

This still gives privilege issue. Only if I give eip to xtables-multi this works. Ofcourse I would not give eip to xtables-multi as then anyone can execute iptables.

Can you suggest how this worked for you I am on Linux Kernel 4.14 with Ubuntu 14

shashankmjain commented 6 years ago

Got it working with capsh --keep=1 --user=test --caps="cap_net_admin,cap_net_raw+eip" -- -c "/home/jain_sm/helloip.sh" (had to use --keep=1)