So far in this series we've found a cross site scripting (XSS) vulnerability and exploited it to find out the admin address of the blog's admin panel. The admin panel used security by obscurity and web server based basic authentication - fortunately for us the backup I restored didn't have the basic authentication configured :) .
What prevented our SQL injection?
Back in part two I commented that I was confused as to why SQL injection was failing. This took me a lot of digging to work out. First I checked my code and confirmed there was no sanitisation (I'll explain that in a moment) going on - there wasn't. Quite literally I would take the data the user submitted via
HTTP POST using the PHP
$_POST global array and pass it into a function. That function would then insert the values into the SQL query and run it. Below is an example of the tag insertion code from the old blog, the only edit I've made being indentation (back then I didn't see the point. I was wrong!).
Next I turned my attention to PHP's now defunct magic quotes option, which was an early attempt at sanitising data by escaping quotes. The magic quotes feature was removed in PHP 5.4.0 and this lab server is running version "5.3.10-ubuntu3.9". Checking the PHP configuration I can see magic quotes aren't enabled so that's not the web application's saviour either. If you're still running a really old PHP version 5.3 (or earlier) you're advised not to use magic quotes and instead sanitise the data properly.
Eventually I stumbled upon a Stack Overflow question where someone was asking why they couldn't run multiple queries with PHP's
mysql_query() function. That in turn led me to the manual page for that function which makes it nice and clear:
mysql_query() sends a unique query (multiple queries are not supported) to the currently active database on the server that's associated with the specified
Note the key text "multiple queries are not supported". We have our answer, now I'm happy.
I've mentioned sanitisation a lot but what does that actually mean? To sanitise is to clean and what we want to do is clean the user provided input. It's important to realise that you cannot trust user provided data. Someone could be trying to be malicious or they may be innocent but your application doesn't handle their input correctly. Either way, we have to make sure the user input is safe and "clean" for use.
A really basic piece of sanitisation would have been to check for the presence of
<script> tags and reject any input that contained them. Had I done that in my old code the cross site scripting (XSS) would have failed. Alternatively I could have used PHP's
htmlentities() function to "convert all applicable characters to HTML entities" which would have translated all of the
<. I could have used a combination of
mysql_real_escape_string() too, which would have allowed me to use apostrophes in my blog posts. Ideally the bad input should have been rejected.
Can we serve other images?
Without server access or a way to upload images, serving images requires the image to be hosted elsewhere unless we embed the image into the comment. We can achieve this by base64 encoding our image file, making it text, and then pasting it into the comment itself. Encoding just means to represent the data another way, so we're essentially converting the image data into text data.
In the admin panel we can see the image is being rendered by the browser:
<script> tag to put the image on the page (and host the image elsewhere online).
Can we upload files?
There's no file uploading as part of the admin backend so there's no easy way to attempt to upload malicious scripts. An attacker's best option would be to exploit a vulnerability on the web server itself, but that's outside the scope of this post series.
I'm not going to attack the blog web application any further as we've largely exhausted our options. The next post in this series will look at steps that could have been taken to secure the code.
Banner image: Screenshot of the image in the comment.