Nebula is the first of three exploit discs provided by exploit exercises. I’ve seen a couple walkthroughs of these levels around, but as a completionist, and for future reference, I’ve cleaned my notes up a bit and am posting them here. I will also post my notes for the other two systems sometime after. This post includes a walkthrough of all 20 levels.
Level 00
Introduction level; requires the user to find a setuid file. find / -perm +6000 -type f -exec ls -ldh {} \; > files.txt
and cat files.txt | grep flag00
gives us two files, /bin/.../flag00
and /rofs/bin/.../flag00
. Pick one and get your flag.
Level 01
First “real” level. A binary with an environmental vulnerability; the source code given clearly lays out that its loading the users environment, and running echo with a string. By modifying our environmental PATH, we can “search” for echo first in another directory and launch a shell. export PATH=/tmp:$PATH
to search for binaries in /tmp first, then create a shell script that launches bash. Flag captured.
Level 02
Much like the previous level, this involves the manipulation of environmental variables. Here it’s pulling $USER
and inserting it into a string to echo out. Because we control this variable, we can terminate the string and execute a shell with the following: export USER="test && /bin/bash && echo"
. Launch the binary and capture the flag.
Level 03
This level has a crontab that runs every 5 minutes, and executes anything found in the local writable.d
folder, then removes it. It, surprise, runs as our flag. If we shove a malicious script into the run folder that generates a shell executable for us, we should be able to suid it and capture our flag. Generate a shell script with the following in writable.d:
1 2 3 4 |
|
You’ll just need to create shell.c
in tmp and wait for it to run; I took the source code from level 1 and changed the system command to execute /bin/bash instead. Execute chmod +x
on run.sh and wait for your binary to be generated. Once it is, execute it for your flag. One thing to note is that it would be much easier to echo a shell script and suid that; alas, Linux prevents us from suid’ing scripts, and for very good reason.
Level 04
Here we have another executable binary and a token file, which we must read. The binary reads a name from stdin, checks if it’s called “token” and if so, exits, otherwise it reads the file. To circumvent this simple test, we can create a symbolic link to the token file and read that instead. Executing ln -s /home/flag04/token /tmp/test
allows us to then read /tmp/test and dump the token file. In it is the password for flag04.
Level 05
This level aims to educate the user on directory permissions. Browsing to /home/flag05
and ls -lAh
gives us several hidden folders, including a .backup folder with a tar file. If we untar this, it appears to be a backup of flag05’s ssh directory. Copying the private key over will allow us to SSH in and capture our fifth flag.
Level 06
The informational page notes that this flag’s credentials came from a legacy Unix system. Executing cat /etc/passwd | grep flag06
gives us a traditional DES encrypted password. Loading this into john the ripper cracks the flag’s password, giving us our sixth flag.
Level 07
Level 7 has a web server running on port 7007 that hosts a simple ping application. No sanitation is performed on input, so obviously its prone to injection. Leveraging the shell we wrote for level 3, we can simply generate a new shell script to compile and suid the binary. By setting Host equal to 192.168.1.1 | sh /tmp/run2.sh
, we’ll generate our shell and capture the flag.
Level 08
This level has a single pcap file with, likely. a password somewhere. I scp’d this off so I could parse it in Wireshark, but it could also be done using tcpdump -qns 0 -X -r capture.pcap | more
. Opening the pcap gives us a login attempt with a password backdoor…00Rm8.ate. The periods are represented by \x7f
, which just so happens to be backspace. su to flag08 and capture the flag.
Level 09
This level requires some prerequisite knowledge of PHP and an edge-case vulnerability. When pre_replace is used with the /e flag, the replacement string is substituted, evaluated, and replaced in the original string. By looking at the PHP source code, we see the following $contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
. This regex matches the pattern [email EMAIL]
, where EMAIL will be evaluated by the spam function, then eval’d by PHP. Notice the function takes two arguments, but only uses one; a clue, by any means.
In order to exploit this, we need to set the email portion of the regex to spawn us a shell. This can be done with the following entry: ([email {${system($use_me)}}])
, where $use_me is $argv[2] to the executable. Wrapping the command in ${system}
allows the PHP engine to interpret the command properly, and the extra pair of curly braces for escaping. Pass in /bin/bash to capture your flag.
Level 10
Level 10 introduces a bit of networking; an application binary exists in the flag’s folder, which reads a file and host from stdin and sends the file to the host over port 18211. The binary first makes a call to access() to verify the user has adequate privileges; if not, a connection to the remote host is made and the file sent. The vulnerability is a classic TOCTTOU (time of check to time of use); the application first checks if the user has access, then goes about creating a socket, making the connection, and sending the header. With the following script, we can brute force the race condition:
1 2 3 4 5 6 7 |
|
I then started up a netcat listener on my remote host. After about 30-40 iterations, I had flag 10’s password.
Level 11
This is our first tricky level, I thought, that required a bit of leg work and thought. The instructions state there are two ways to finish this level; I’m assuming that’s through one of two code paths present in the binary. Essentially if our content header is < 1024 we take the first transformation path, and if it’s > 1024, we take the second. The objective is to pass in already encrypted data that, when decrypted, can grab our flag. If we send in encrypted data after a 1024 byte buffer, we can get decrypted commands to the system() call. I wrote a small C application that runs the exact runtime from the given source and spits to stdout, which can then be piped to flag11 ./generate getflag | /home/flag11/flag11
.
Level 12
This level has a local telnet script listening on port 50001 that employs a lua script to handle all of its happenings. The script accepts a password, hashes it, and checks it against a hard coded hash. The vulnerability here is how it’s actually verifying the password; the following line exemplifies the issue: prog = io.popen("echo "..password.." | sha1sum", "r")
. The listener receives input, and echo’s it into sha1sum. If we inject commands into our password, we should be able to snag the flag. Executing f | getflag > /tmp/test | echo "test"
as a password should result in our flag when you cat /tmp/test
.
Level 13
I was initially a little confused about the goal for this level; cat /etc/passwd | grep 1000
returned the nebula account, which we “technically” don’t know. I then started looking for ways to preload a modified library, so we could override the getuid() call and just return 1000. This led me here, which I was painlessly able to pull off:
1 2 3 4 5 |
|
Running this dumps our token to stdout. Another method may be modifying the binary itself, but I don’t know how legal that is. This was a neat vulnerability, and something I haven’t run into before.
Level 14
Now I’m convinced that running the binaries in a debugger is legal. This level has a binary that only encrypts information; so, the objective here is to discover the encryption algorithm and write a complimentary decryption routine. Playing around with the encryption tool allowed me to quickly discover what it was doing, without having to delve into assembly:
1 2 3 4 5 6 7 8 |
|
So to reverse this, we just need to subtract the index. I wrote a quick python script for doing this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Now we just need to pass in the output of the encrypted token file to obtain our flag.
Level 15
If we strace flag15, we see a ton of access attempts to libc.so.6 in various folders in /var/tmp/flag15
. My first idea was to forgo the libc loading stuff and try wrapping the puts call and use the preload vulnerability again:
1 2 3 4 5 6 7 8 9 |
|
Unfortunately, the protection mechanism for preloading libraries catches us. The loader will completely ignore the preloaded library if the RUID is not equal to the EUID and unlike level 13, we need to execute a binary, not simply obtain a token embedded in the binary.
So instead we need to compile a statically linked library and get it to call that library with whatever function it’s using. Object dumping the file, we find our main quite small:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
The only function it calls is puts, so we need to override that; we also need a target location. /var/tmp/flag15/libc.so.6
appears to be the least nested. Here’s the library code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
We just need to compile and statically link this to the library path:
1 2 3 4 5 6 7 8 |
|
This, apparently, means that the library version number is lower on the shared object. So we’ll need to generate a version file and link it in:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Recompile with —version-script=/tmp/versionz and…
1 2 3 4 5 |
|
Level 16
Level 16 has an HTTP server that hosts a simple CGI script. In it, it parses off two parameters (username & password) from the URL, then the username is converted to uppercase and everything after a space is stripped. It then uses it as an argument to egrep:
1
|
|
This level took a bit of thought and a frustrating amount of tinkering; it’s obvious where the vulnerability is, but it’s not so obvious what we actually need to do. All input is run through the two filters, so we’ll need to either send in post-manipulated data that will be changed back when run, or some other voodoo that’ll correctly be interpreted by bash and not touched by the filters.
The easiest, and shortest, command will be a script. Normally, you wouldn’t have access to the underlying host, but we do, so for now we’ll take advantage of it. I”ll admit I spent more time Googling around for this one than any of the previous; I happened to stumble into this Stack Overflow post about bash wildcard expansion:
1 2 3 4 5 6 |
|
Wildcards aren’t anything new, but I never knew that you could use it as such. This means we need to have our username evaluated as /*/shell
, so when it’s expanded it’ll be /tmp/SHELL
. The final result:
1
|
|
Add in a netcat call back to a script SHELL in tmp (see level 17) and it’s game over.
Level 17
This level is yet another vulnerable listener, this time implemented in Python. The vulnerability lies in the Pickle module, where one look at documentation gets you:
1
|
|
Because our input is never sanitized, we just need to send a specially crafted input string to be unpickled and execute malicious code. This Blackhat whitepaper came in handy when figuring out how the parsing engine worked, and once I had that figured out it was pretty easy:
1 2 3 4 5 6 7 8 9 10 11 |
|
I had a netcat shell listening on port 5555 for the call back. I used this method because the Nebula box doesn’t have netcat-traditional on it, which lacks the -e flag. This is a neat way of opening a reverse shell without the fuss of named pipes.
Level 18
According to the level documentation, this can be completed in three different ways at three different difficulty levels. The binary appears to be a hackney attempt at some sort of login program; with it, a user can “login” to elevate privileges, set user, do some debugging, and some other smaller things. Immediately though I see a buffer overflow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
I guess not. It appears it’s been compiled with a bit of protection:
1 2 3 4 |
|
On deeper inspection of the code, it appears there are several logic flaws that could allow simple execution of a root shell.
The first issue is that there’s no restriction on what file we’re debugging to, so long as we’ve got the privileges required to open it. In this case, this means we can debug to the same file that we’re checking passwords against. Since we can’t actually read the file, we need to infer:
1 2 3 4 5 6 |
|
So we read without error and we know the exact data in the file. The problem with this is that it strips newlines, and with fgets we don’t have a nice way of inserting them.
The second issue I discovered involved the command site exec, which doesn’t properly format output, and won’t append newlines (which may be used to further the first issue).
1 2 3 4 5 6 7 8 9 |
|
This means that our binary was likely compiled with FORTIFY_SOURCE=2. Here’s the patch that explains what it is and prevents, and the differences between 1 and 2. Because I don’t take doors slammed in my face very well, and it’s late, it’s time to break out the phrack:
1 2 3 4 5 6 7 8 9 |
|
Exploitation of this wasn’t trivial, but it was eye-opening into some of the things you can accomplish with format string vulnerabilities. Because our binary was compiled with FORTIFY_SOURCE, there are essentially two things we need to do: disable the FORTIFY flag, and disable the argument filler. The argument filler essentially sets every argument in arguments[argc] to -1, then fills in the user supplied arguments. Any -1’s remaining will cause an error. At a high level we’re doing this: + Disable FORTIFY_SOURCE flag + Modify nargs to blow up + Be happy
The Phrack article does a better job of explaining this than I do, so if you’d like an in-depth analysis of all this, follow the article. Anyway — finding and disabling FORTIFY_SOURCE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
There’s obviously a bit of cheating here, but bare with me: we’re essentially breaking on vfprintf, which receives a file pointer, a formatter, and an argument list. We then take a peek at the stack, note our file pointer, and find the flag inside (0x000000004). We know need to calculate the offset:
1 2 3 4 5 |
|
And accounting for off-by-one, it’s 2848. So:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Great, we’ve got the correct value for clobbering that flag. Now we need to find the offset for clobbering nargs:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
I got stuck here for awhile, and after some googling I discovered someone had already solved this level this way! So I took his advice and created an environmental variable that would lower the stack address and not segfault. The final phase is now upon us; smuggling shellcode in. The Phrack article gets a little hairy at this point, so instead of screwing around with adjusting stack offsets, I decided to just take the route that v0id took in his blog post. His process involves setting the loggedin variable by way of taking control of uninitialized stack memory, thanks to Rosenburg’s fantastic post on this topic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
So we just need to find the loggedin variable, then call shell on it:
1 2 3 4 5 |
|
Stick that into our LD_PRELOAD, sprinkle in a bit of stack alignment, and…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
Success! It’s clearly passing in the -d flag to sh, so…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
A shell was waiting for me on 192.168.1.74:5555. Lots of subtle intricacies here, but really quite fun.
The third issue is the following snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
If the application fails to read PWFILE, the flow will automatically assume the user is logged in. This appears to be either the easy or intermediate way, as source code explicitly calls this case out (with%s password file). This could be defeated by opening up a ton of files and running the binary, effectively erroring out fopen and getting the loggedin flag set.
Level 19
We now arrive at the final level of Nebula. This level checks the owner of the calling process and, if root, pops a shell. This level was kinda neat because it requires you to have an understanding of how forking and parent/child processing works; and if you know that, it’s pretty easy. In this level we’re going to exploit an orphan process and its reclamation process. When a parent of a child process terminates, the child process stays alive and becomes an orphan process. This orphan process is automatically reclaimed by an init process:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
So we want to fork off a process and kill the parent before it calls flag19. Then, when it goes to stat the process, it will stat init instead of us thereby assuming the role of root. Here’s the code for achieving that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
And flag was waiting for us in /tmp/flagged
.