Stealing from scammers using SQL Injection | Ethical Hacking Payback — Part 1
Have you ever heard about these loot box gambling sites where you can bet the items you won playing games (like CS:GO), and get better skins?
Well, a friend of mine started receiving messages on his Steam profile inviting him to new gambling websites.
He played on some of them and had fun, but something happened:
He got scammed.
This is a serious subject. Too serious to talk about it here in detail. Here’s five cents on what my opinion is, but if you don’t want to get political, feel free to skip to the technical details.
Gambling culture has been spread across our society like a common, cool and natural thing. It is not. It’s a legal drug. People think it’s ok to have physical Sport-Gambling stores across our towns, websites that allows you to gamble using cryptocurrencies, Twitch streams showing you how people earn millions from virtual casinos…
This is not ok, and we should all be concerned about how young-aged citizens gets trapped into this world, end up losing all their money (in this case, we’re talking about +20k), having depression, and not getting help from authorities because the medical system is collapsed. Fuck that.
How did it begin
I knew he got scammed right when he told me what happened, but it was not that obvious to him:
This guy from Steam awarded him $10 for joining the website. He used the credit for gambling and won some extra cash. Cool! But there were some rules:
- The withdrawal feature was disabled until reaching level 10.
- The only way to gamble was using your own Steam inventory items. You couldn’t gamble the items you had already won on the page, because you needed to withdraw them first.
He spent some days adding items from Steam, spending the credit on loot boxes, getting new skins, and levelling up the account.
Eventually, he spent around $2000 and never reached Level 10. And this is when he called me.
I’m pretty sure I should have already reached Level 10. The Level bar has been stuck at Level 9 for two days.
To me, this was a pretty obvious scam, but I couldn’t know for sure.
I needed to know the truth.
There was activity on the website, I can say that. You could see users gambling in real-time, people winning and losing bets. It didn’t really look suspicious. But I saw this “Online Users” counter on the website’s footer, and it was always between 700 and 715, no matter the time of the day. Weird. Here’s what we did:
- We used the support chat on their website, but no one answered.
- We also tried to talk with the guy from Steam that invited him. No answer either.
Later, I realized that the footer's social media links were pointing to an existing gambling site that ran out of business a couple of years ago. The scammers were clearly supplanting them.
This is when I decided to hack them.
Disclaimer: We need to keep in mind that, if this had been a legit gambling site, what I did would have been completely wrong. No one should try gaining access to your application to investigate if your service is legitimate or not.
But I’m pretty glad that I did it.
Yeah, they had an admin page. I ran FFUF against the website and found it in less than 2 minutes.
It looked quite different to the main webpage, though. I felt like it could be a pretty old admin panel tool they installed a while ago.
It really seemed vulnerable to SQL injection, so I tried running some payloads to log in, like:
' or 1=1;--
" or 1=1;--
%' or 1=1;--
%" or 1=1;--
But nothing worked.
Then I tried some blind SQL injection payloads:
" or sleep(10);--
' or sleep(10);--
And surprise: the webpage got stuck loading when I ran the payload starting with
'. The admin panel was SQL-vulnerable!
At this point, I started using sqlmap to get all the database information. I dumped the database structure, and the content of the tables I was interested in.
There was a table called
admins, which looked just like what I was looking for: a table to check which users can log in to the admin panel.
The structure was quite simple:
| `id` | `login` | `password` | `hash` | `date` |
I noticed the
hash field was an MD5 hash, and the
password field contained values starting with
$2y$10$ is the value that the
password_hash PHP function returns when there’s no extra configuration. The cost is 10 and there’s no salt configuration by default. You can check the documentation for further reference.
I wondered if I could run stacked queries to insert my own user into the table. So, I created a PHP script on my machine, called the
password_hash function to generate the password, used the
md5 function to create the hash, and went to the login page to run the following payload:
'; insert into `admins` (`id`, `login`, `password`, `hash`, `date`) VALUES (100001, 'test', '$2y$10$...', '6e894...', '');--
Then I tried to log in with my new username and password.
And it worked, I was in!
Analysing and tweaking the admin panel
Here is when I confirmed they were scammers. They had an admin panel to manage the website’s fake data: they were able to set the random amount of users connected to the website that was being displayed in the footer, there was a list with hundreds of bots that were supposed to be betting, and then a whole list of real users that had registered on the website.
My friend was on that list.
The webpage was fully configurable, so it could be set up with different names, logos, and themes in just a couple of clicks. I’m pretty sure there must be several copies of this website over the internet, scamming people.
I remember there was a checkbox allowing registered users to reach level 10. It was disabled.
The first option that came into my mind was tweaking this configuration to allow users to reach level 10 and let them withdraw the items. But soon I realized there was no withdrawal feature developed. If you enabled that option, users could see the Withdrawal page, but it was blank.
Local File Disclosure
One of the first things that I noticed was that they were using this widely known exploitable way to load pages: passing the page name on a GET parameter:
They must be loading PHP files — I thought.
I tried loading the dashboard page directly by visiting
/admin/dashboard.php, and the dashboard section appeared. Cool!
And that’s when I wondered: what happens if I try loading a non-existing file in the
I tried going to
/admin?page=qwerty, but it redirected me to
These kinds of tests are mandatory while trying to exploit an application. You’ll need to find a way to differentiate OK and KO behaviours in order to analyse the results of your payloads.
So, what do we know so far?
- They’re asking for a file name in the
- Seems like they’re adding
.phpat the end of the
pageparameter to load the PHP file. (like
$GET['page'] . ".php")
- If you try to include a non-existing file, you get redirected to the dashboard page.
I had to keep digging. There were a lot of ways this could be vulnerable.
At this point, my main question was if I would be able to load files from a different folder (AKA: path traversal).
There was only one file I knew existed on the website: the
index.php file in the document root. So, I tried visiting
/admin?page=../index, but got redirected to the dashboard page.
There were mainly two reasons why this could not be working:
Either it was not possible to load files from a different folder, or the system was filtering the
../ string in some way.
Then I tried to exploit it using URL encoding.
I’ll do the conversion for you:
So, I went to
/admin?page=%2e%2e%2findex, but again, got redirected to the dashboard page.
And right after that, I went to
/admin?page=%2e%2e%2e%2f%2e%2findex and saw the main web page embedded inside the admin panel!
Bypassing Path Traversal Filters
Wait, what did just happen?
This is something I usually do while testing for path traversal file inclusions. Programmers try to avoid path traversals by removing the
../ strings, but some of them don’t know this needs to be done recursively.
Why, though? — You may be asking.
For the same reason why this payload worked! Let’s take a deeper look:
- This is the payload that I sent:
- This is how it looks without URL encoding:
- Now, let’s remove every
../from the string:
What’s the result?
../index ! That’s what we were looking for!
If they had used a recursive function to remove the dangerous strings, this is how it would have worked:
But they were only checking it once!
Null Byte Injection
So, now we also know we can include files from different folders.
We must do something with the extension. The code must be adding
.php at the end of the
pageparameter. How can we avoid this from happening?
Well, some time ago, I discovered you can use null bytes (%00) to indicate the end of a string. The end of the content.
So, what does it happen if I visit
It redirected me to the dashboard page.
The system has not been able to find the file. Let’s try adding the extension in the payload and see how that works.
/admin?page=%2e%2e%2e%2f%2e%2findex.php%00 in the URL, and the main web page got embedded inside the admin panel one more time! 😎
Disclosing important files
Now we have everything we need to exploit this properly.
Final test: reading the
I don’t really care where we are in the filesystem: We may be at
/home/<project>, etc. I just want to get out of every single folder and load the file. How would you do this in a regular terminal?
# Go back (../) as many times as you want.
# If you're 5 levels deep in the filesystem (/dir1/dir2/dir3/dir4/dir5),
# adding 100 "../" will lead you to the root of the system (/)
Let’s convert our payload. There are only three necessary steps:
- Convert every
..././) to bypass the filter.
- URL encode
- Add a null byte to the end of the payload ⇒
And there we go! I loaded the
/etc/passwd file inside the admin panel! 🎉
Of course not. There are still a lot of details to be explained. We ended up stealing some of their funds in a very fun way. But this story is getting pretty long, so I’ll continue writing it as soon as I can!
Continue reading (not available yet): Stealing from scammers using SQL Injection | Ethical Hacking Payback — Part 2