In message <http://marc.info/?l=openbsd-misc&m=141616701418506&w=1>
I wrote:
> Web browsers scare me: they're huge pieces of code, un-audited, they
> have embedded Turing-complete interpreters, they live in a horribly
> imsecure environment, [[...]]
> 
> So, I'm thinking about how to exploit-mitigate a web browser (I'll use
> firefox here for purposes of illustration, but this is basically generic
> to any other web browser).  This is in the context of a single-user
> OpenBSD desktop (say a laptop).
> [[...]]
> 
> I can see several possible forms of exploit-mitigation:
> (a) use the noscript firefox extension to block javascript
> (b) use capsicum to sandbox forefox and any plugin processes
> (c) run firefox in a chroot jail
> (d) have firefox talk to an Xephyr(1) instance
>     so it's semi-isolated from the main X server
> (e) maybe have firefox go through an ssh tunnel to localhost
> (f) run firefox as an unpriviliged user _firefox, group _firefox, and
>     use Unix file permissions to deny that user access to $HOME/

Thank you to everyone who's responded!

Daniel Dickman pointed to the quark formally-verified web browser.
This is interesting research... but if I'm reading their paper correctly,
their formally-verified security properties still permit the browser
to (for example) send my ~/.ssh/ private keys to evil.com. :(  I'm not
sure whether these properties block the installation of keyloggers,
either.

Jorge Gabriel Lopez Paramount's idea of putting the web browser inside
a read-only virtual machine is clever.  Virtual machines have lots of
security holes (http://marc.info/?l=openbsd-misc&m=119318909016582&w=1
and http://xenbits.xen.org/xsa/), but making the VM read-only and/or
re-installing it often should mitigate that to some extent.



I now have (e) and (f) running ok on 5.6-stable, on a Thinkpad T60
laptop (3GB RAM, 2.0GHz Intel Core Duo).  My normal login is in login
class 'staff', for which I've upped the memory limits to infinity.
I've installed the firefox-31.0 package, and created a new unpriviliged
user _firefox (group _firefox and no other groups, login class staff).

Some further details:

The obvious way of doing (f) is to make either the firefox binary,
or a wrapper program, setuid/setgid _firefox.  Unfortunately, it turns
out that that doesn't drop supplementary group ids.  That is, if my
normal id is in group wheel, then even after executing a setuid/setgid
wrapper, the process is still in group wheel. :(

Since Perl (unlike shells) allows safe setuid scripts, I thought of
using the Perl Proc::UID perl module.  Alas, this is broken -- it won't
compile on any modern perl, and the bugs in question have been open with
no change in status for > 3 years.

So... back to C.  After a bit of poking around with setgroups(2), I
found that the wrapper has to be setuid/setgid root in order to be allowed
to drop the supplemental groups.  Following Chen, Wagner, & Dean's
paper "Setuid Demystified"
  http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf
and inspired by /usr/src/usr.sbin/ntp.c lines 145-148, I wound up with
the following wrapper:

--- begin wrapper ---
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

#define ERROR_EXIT_STATUS       1
#define PROGRAM_TO_EXECUTE      "/usr/bin/id"

/*
 * This wrapper
 * - drops any supplementary groups
 * - changes to uid & gid _firefox, and
 * - executes another program
 */
int main(void)
{
/* FIXME: should really look these up via getpwnam(3) and getgrnam(3) */
const uid_t firefox_euid = 2000;
const gid_t firefox_egid = 2000;

const int my_euid = geteuid();
const int my_egid = getegid();
printf("in wrapper before dropping privs: my_euid=%d, my_egid=%d\n",
       (int) my_euid, (int) my_egid);

printf("dropping any supplementary groups...\n");
if (setgroups(0, NULL) != 0)
        {
        perror("unable to drop supplementary groups");
        return ERROR_EXIT_STATUS;
        }

printf("setting gid to _firefox...\n");
if (setresgid(firefox_egid, firefox_egid, firefox_egid) != 0)
        {
        perror("unable to set firefox group id");
        return ERROR_EXIT_STATUS;
        }

printf("setting uid to _firefox...\n");
if (setresuid(firefox_euid, firefox_euid, firefox_euid) != 0)
        {
        perror("unable to set firefox user id");
        return ERROR_EXIT_STATUS;
        }

printf("executing %s\n", PROGRAM_TO_EXECUTE);
execl(PROGRAM_TO_EXECUTE, PROGRAM_TO_EXECUTE, NULL);

/* we only get to here if the execl() failed */
perror("unable to execute");
return ERROR_EXIT_STATUS;
}
--- end wrapper ---

If compiled and made setuid-root and setgid-wheel, this successfully
drops all its priviliges (including supplemental groups).

But, when I changed /usr/bin/id to (say) /usr/X11R6/bin/xterm or
/usr/X11R6/bin/xclock (for testing), I then found that the X server
(rightfully) refuses to accept a connection from a process running
with uid/gid _firefox.  I played around a bit with copying my .Xauthority
file to the _firefox home directory, but couldn't get that to work,
so I decided to use (e) (have firefox go through an ssh tunnel) instead.
This also gives a tiny bit more isolation for the firefox process --
no more shared-memory between firefox and the X server.

So, configured sshd to allow X forwarding and to allow (only) user
_firefox publickey authentication, added a pf rule to block outside
access to sshd, and activated sshd.

Then I created a new public keypair in ~/.ssh/firefox_id_rsa{,.pub}, with
no passphrase, and copied the public key to ~_firefox/.ssh/authorized_keys
(mode 600).  I renamed /usr/local/bin/firefox to /usr/local/bin/firefox.bin
and put the following script (executable, but no special priviliges) in
my ~/bin/ :

--- begin start-firefox script ---
#!/bin/sh
ssh -X -i ~/.ssh/firefox_id_rsa _firefox@localhost \
    '/usr/local/bin/firefox.bin -no-remote -new-instance' \
    2>&1 >/dev/null &
--- end start-firefox script ---

Running this script produces a couple of warning messages that we're
blocking some X progocol extensions:

Xlib:  extension "RANDR" missing on display "localhost:10.0".
Xlib:  extension "MIT-SHM" missing on display "localhost:10.0".

Blocking firefox from accessing these seems like a security boost to me:
according to  http://en.wikipedia.org/wiki/RandR , RANDR "facilitate the
ability to resize, rotate and reflect the root window of a screen", and
MIT-SHM is a X protocol extension for using shared memory to communicate
between a client (firefox) and the X server.

After these warnings, firefox starts and runs ok as uid/gid _firefox.
It seems about as (un)responsive as usual.  (Perhaps a better way to
phrase that would be "firefox is sluggish enough in its usual operation
that the extra overhead of the ssh tunnel isn't noticable".)  I haven't
tried any plugins yet.

Other notes:
* Firefox likes $HOME/Desktop as a spooling area for saving things,
  so I've made ~_firefox/ and ~_firefox/Desktop/ both mode 755, so that
  I can copy files out of that easily.  I don't see any security risk
  in this (given my context of a single-user desktop/laptop, with the
  "Desktop" directory only used as a transient spool directory).
* Making my home directory mode 750 (to block firefox from having any
  access to it) has the unfortunate side effect of excluding all my
  files from locate(1).  Since the locate database is built by user
  'nobody', my "solution" is to add myself to that group.  Is there a
  security risk in this?

-- 
-- "Jonathan Thornburg [remove -animal to reply]" 
<jth...@astro.indiana-zebra.edu>
   Dept of Astronomy & IUCSS, Indiana University, Bloomington, Indiana, USA
   "There was of course no way of knowing whether you were being watched
    at any given moment.  How often, or on what system, the Thought Police
    plugged in on any individual wire was guesswork.  It was even conceivable
    that they watched everybody all the time."  -- George Orwell, "1984"

Reply via email to