2011 CTF Writeup

About

The CTF this year was themed around andrewg and named Destiny Beard ][. The CTF ran for most of the conference including the Saturday night party and had over 50 registrations (individuals and teams) in advance.

Winners

The winner of the CTF was kitten and emilio. They scored a $1000 cash prize courtesy of Paterva. Bling.

Runner-ups were whiteandnerdy and 9447 who scored a selection of bookware.

Thanks to

The CTF was put together by andrewg, ash, ecalos, snyff, and thoth. Wily was also handing out tokens during his lockpicking contest.

Very special thanks to Roelof at Paterva for the generous prizes.

Sample Levels

We've chosen several of the levels to write about this year.

Unix Levels

level09

This level is a PHP script that implements a very basic markup type language. When reading over the code, you will see the function "markup()" has a parameter called "$use_me" which is unused in the code, giving us a strong hint that the vulnerability is in that piece of code, and that the unused parameter will play a large part in exploiting it.

The next step is then to analyze the code and find out where the particular bug is, however, it's not immediately obvious unless you've done any PHP security auditing, so the next step is to research the function "preg_replace" and any security issues associated with it.

Looking up preg_replace, we see the following:

If this modifier is set, preg_replace() does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences.

Caution

The addslashes() function is run on each matched backreference before the substitution takes place. As such, when the backreference is used as a quoted string, escaped characters will be converted to literals. However, characters which are escaped, which would normally not be converted, will retain their slashes. This makes use of this modifier very complicated.

Caution

Make sure that replacement constitutes a valid PHP code string, otherwise PHP will complain about a parse error at the line containing preg_replace().

We see that the code supplied in the replacement function must be carefully constructed to avoid any php code injection issues. Looking at the vulnerable code again, we see that

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

So, we must construct input to cause code execution when the code is parsed then executed.

Putting those things together a little bit, we see that if we construct some input like:

[email {${shell_exec($use_me)}}]

The output we get will look like the following:

php5 level9.php input.txt 'ls' 2>&1 
PHP Notice:  Undefined variable: input.txt
level9.php

 

level18

Level 18 requires a bit of C programming knowledge. There are some code constructs that look like they could be exploited via memory corruption in setuser() and format string exploitation in notsupported(), however the FORTIFY_SOURCE and Stack Smashing Protection (SSP) compiler improvements prevent that from being a suitable avenue of attack.

There is some code to execute a shell if you are logged in, however the login requires a password which its gets from a file that is not readable by the level18 account.

Reading the login code closer, shows the following:

case 'd':

	globals.debugfile = fopen(optarg, "w+");

Which allows us to open a file and write logging stuff to it, and additionally login code does the following:

void login(char *pw)

{

	FILE *fp;


	fp = fopen(PWFILE, "r");

	if(fp) {

		char file[64];



		if(fgets(file, sizeof(file) - 1, fp) == NULL) {

			dprintf("Unable to read password file %s\n", PWFILE);

			return;

		}


		if(strcmp(pw, file) != 0) return;		

	}

	dprintf("logged in successfully (with%s password file)\n", fp == NULL ? "out" : "");



	globals.loggedin = 1;



}

If it fails to open the file, it will log you in. Notice that it doesn't close the password file either? So there is two methods of attack, one is to set a low file resource limit (see ulimit in bash manpage), or to do the login command a number of times to make it open the files until it fails.

However, there's another trick needed to make the shell command work:

} else if(strncmp(line, "closelog", 8) == 0) {

			if(globals.debugfile) fclose(globals.debugfile);

			globals.debugfile = NULL;

One file must be closed to allow the program to execute by opening libraries.

With the login command repeated enough times you will be logged in, followed by the closelog command, allows you use the shell command to execute a shell.

Nebula CTF image

If you'd like to do the RUXCON Linux Capture The Flag challenges, a virtual machine can be downloaded from exploit-exercises.com/download, under the "Nebula" name.

Binary Levels

Coming soon.

Misc Levels

Crossword

The crossword was thrown into the middle of the Ruxcon 2011 handbook. The idea is to fill in the answers for the crossword, then work out the anagram of the highlighted boxes. This was made to have quite a lot of scene and specific references with the idea you can't Google for every question, you either know it or you don't. Turns out security crossword puzzles online really suck. The crossword can be downloaded here and seen below.

Andrewg Ascii Art

With the theme around andrewg, we wanted some levels to pay homage to the great man. The following level shows andrewg in ascii after running it through one of those jpeg -> ascii converters.

The HTML body is made up purely of 0's and 1's, we spotted some people copy and pasting the entire block and trying to convert it to ascii/hex. Ba-beeeww.

When you inspect the source for the web-page you'll spot this javascript routine:

When you hit any key (except one) andrewg will say 'roflzoom' which is the default catch in the switch statement. The 0x1b is for the ESC key and if you tap it it's fairly likely nothing will happen.

The loop iterates over all of the font tags which make up the 0 and 1 blocks in the HTML. It then extracts the rgb values for the font color and uses this in a condition on line 53 also considering the current seconds and year on the system clock and sees if that matches f (which is 2003 in decimal). This should be the first hint, since the story of the CTF relates to andrewg travelling back to Ruxcon 2003.

It turns out that for blocks where r + g + b == 0 the innerHTML of those font tags if concatenated gives a binary string that when converted to ascii gives you a token.

It's either possible to play with the javascript/source in the browser or the trick to get the right condition to fire is to reset your system clock to 2003 and hit the ESC key at 0 seconds. You'll then see the following:

The funny thing is it's actually a pain to copy and paste this, and most people would be inclined to CTRL + C it, which jerkingly enough will trigger the 'roflzoom' and you'll need to wait another minute to try again.

Web Levels

Lolcathost

The challenge lolcathost was available on http://paris.ruxcon:9666/, it is a simple web application. When you try to connect to it, you receive the following response a 401 error message:

% telnet paris.ruxcon 9666  
Trying 127.0.0.1...  
Connected to paris.ruxcon. Escape character is '^]'.  
GET / HTTP/1.0  
HTTP/1.1 401 Unauthorized  
X-Frame-Options: sameorigin  
X-Xss-Protection: 1; mode=block  
Content-Type: text/html; charset=utf-8  
Content-Length: 51  
Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18)  
Date: Wed, 30 Nov 2011 04:37:42 GMT  
Connection: close    

Only 127.0.0.1 is allowed to access this token :P

The error message gives a serious hint on how to get the token... The trick was as simple as providing a crafted Host header:

% telnet paris.ruxcon 9666  
Trying 127.0.0.1... 
Connected to paris.ruxcon. Escape character is '^]'.  
GET / HTTP/1.1  
Host: 127.0.0.1  
HTTP/1.1 200 OK   
X-Frame-Options: sameorigin  
X-Xss-Protection: 1; mode=block  
Content-Type: text/html;charset=utf-8  
Content-Length: 36  
Server: WEBrick/1.3.1 (Ruby/1.9.2/2011-02-18)  
Date: Wed, 30 Nov 2011 04:41:24 GMT  
Connection: Keep-Alive  

079d5f69-f865-4b87-bf1f-7ce52135ac1d
Reallybasic

Really basic was supposed to be an easy SQL injection but using sqlite::memory was definitely a wrong choice and made the challenge unavailable for a long time. The problem was that once the database connection drops (after a period of inactivity), the database drops making the SQL injection unexploitable (the in memory database only exists through the connection). I originally choose sqlite::memory to avoid problems during injection or after a reboot... definitely not the smartest choice :/ lesson learned.

Anyway, while writing an SQL injection challenge, you want to prevent people from using Sqlmap as much as you can. So you had to inject in the basic authentication, injecting a single quote will throw an error message and give some hint on the nature of the vulnerability.

To bypass the authentication, you just had to build an always true condition using either: - " ' or '1'='1' ) -- " since most ruby databases' wrappers put the conditions between brackets. - "admin' or '1'='1" based on the way sqlite3 handles conditions (the username admin had to be guessed).

Udphp

Udphp was a UDP server doing an eval on any string received. To avoid people to get a shell on the box, PHP was recompiled without support for exec, system, ..., pcntl_exec.

In order to get the token, you needed to generate a UDP packet doing a simple read on a file named token, for example, you could use: $f=fopen('token','r'); return fread($f,199); and sniff the token with wireshark

Topplet

Topplet was a web page with a small Java applet. You could easily retrieve and reverse the Java using a browser and jad (Java decompiler). Once the Java code retrieve, you could see that the authentication was comparing the username to "ruxcon" and the MD5 of the password provided with the string: "If only it was that easy, keep searching :P ".

It was impossible to guess or build a correct password. In order to get the token, you just had to build a Java client using the same code but without authentication or send the exact same UDP packet that the application uses.

Network Levels

Coming soon.