Uncovering Security Flaws in Hamster Kombat: A Developer's Journey to Grandmaster Status

Hamster Kombat has taken the world by storm, breaking a record previously set by Mr. Beast on YouTube—a feat that might be the first of its kind. Suddenly, everyone is eager to get involved and make some money. So, let's delve into what exactly Hamster Kombat is.

Hamster Kombat is a rapidly growing crypto game based on Telegram. In this game, players step into the shoes of a hamster CEO running a fictional cryptocurrency exchange. The game combines strategic gameplay with the thrill of earning real-world cryptocurrency rewards. Players can look forward to an upcoming token launch and airdrop, which adds to the excitement and potential for real earnings. The integration with Telegram makes it easily accessible and convenient for its rapidly expanding user base.

Now, let's pull our attention to the part about "Telegram." That's our turf—it's a mini web app, and we do web apps too. Before we dive into the technical aspects, let's understand how the game works and what we can tweak to make it uniquely ours since we don't settle for anything ordinary.

Drawing inspiration from the success of the Notcoin clicker game, Hamster Kombat aimed to create a unique experience that merges strategic management of a virtual crypto exchange with a tap-to-earn gaming model. This simple yet addictive gameplay mechanism involves players repeatedly tapping on the screen to perform actions that earn in-game rewards.

So, tap on the screen to make money—do people really have time for that? I don't, and I'm sure I'm not the only one thinking this way. I did a quick search on GitHub for projects automating the tapping and found quite a few. Some are full-blown projects that automate booster functions, collect daily rewards, and more. This piqued my interest in exploring the Hamster Kombat web app further to understand which parts have been exploited, why, and how. My aim in writing this blog post is to share insights on how I would avoid similar issues in our own projects.

Let's move forward with setting up our environment to explore the web app further. Since Hamster Kombat is restricted to mobile devices and I am using iOS, thus here is how I did the set up for my debugging environment:

On iOS Device:

  1. Go to Settings.
  2. Find the Safari icon and press on it.
  3. Scroll down and press Advanced.
  4. Enable the Web Inspector option.

On macOS:

  1. Open the Safari browser.
  2. Open Settings (⌘ + ,).
  3. Select the Advanced tab.
  4. Check the Show Develop menu in menu bar option at the bottom.

Next Steps:

  1. Connect the iOS device to the Mac via cable.
  2. Open the Mini App inside the iOS Telegram client.
  3. Open the Develop tab in the menu bar in Safari on macOS.
  4. Select the connected iPhone.
  5. Select the opened webview URL under the Telegram block.

The first thing I noticed under the Sources tab of the developer console is that this is a Nuxt project. I've never been happier doing a postmortem on an application.

Looking at the Network tab, I observed that the tapping action on the web app triggers a POST request to the endpoint clicker/tap.

Summary
URL: https://api.hamsterkombat.io/clicker/tap
Status: 200 OK
Source: Network
Address: 13.51.73.128:443
Initiator: 
entry.i4X6gxxf.js:17:19871

Request
POST /clicker/tap HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9
Authorization: Bearer ***TOKEN***
Connection: keep-alive
Content-Length: 54
Content-Type: application/json
Host: api.hamsterkombat.io
Origin: https://hamsterkombat.io
Referer: https://hamsterkombat.io/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148

Request Data
MIME Type: application/json
Request Data: 
    {"count":6,"availableTaps":492,"timestamp":1719511110}

Upon examining the request, I noted that it lacks a digital signature and does not include a CSRF token. The authorization is solely handled via a JWT token. To delve deeper, I also checked if CORS has been implemented. For this verification, I utilized a straightforward Python script:

import aiohttp
import asyncio
import json
import random
import time

async def main(job_id=1):
    url = "https://api.hamsterkombat.io/clicker/tap"

    current_time = int(time.time())
    random_number = random.randint(1, 10)
    timestamp = current_time - random_number

    payload = json.dumps({
        "count": 100,
        "availableTaps": 1498,
        "timestamp": timestamp
    })

    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ***TOKEN***'
    }

    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:
        async with session.post(url, headers=headers, data=payload) as response:
            response_text = await response.json()
            response_text = {
                'total': response_text['clickerUser']['totalCoins'],
                'balance': response_text['clickerUser']['balanceCoins'],
                'level': response_text['clickerUser']['level'],
            }
            print(f"Job {job_id} response: {response_text}")

asyncio.run(main())

This function completed smoothly without any exceptions or errors, highlighting that the web app is currently lacking the following essential controls:

  • CORS is not properly implemented, potentially allowing requests from any origin.
  • Lack of CSRF tokens and exclusive reliance on JWT tokens for authorization without additional safeguards, such as request signing or origin validation, exposes the API to automated abuse and replay attacks.

Additionally, let's verify if there is a rate limit configured for these requests. We can achieve this by running an infinite loop that continuously calls the main function responsible for making requests:

import aiohttp  
import asyncio  
import json  
import random
import time

async def main(job_id=1):  
    ...

async def run_jobs():  
    last_sync_run_time = 0  
  
    while True:  
        try: 
            await main()  
            await asyncio.sleep(0.5)  
        except Exception as e:  
            print(e)  
  
# Run the jobs  
asyncio.run(run_jobs())

After running the script continuously for an extended period, I found no imposed rate limits. While I acknowledge that a tapping game may not strictly enforce such limits, making requests every 0.5 seconds without interruption for over 24 hours exceeds normal user behavior. As a developer, I would consider implementing measures to discourage such behavior, ensuring fair access and preventing abuse of the game's resources. Additionally, I would implement a refresh token mechanism to automatically renew JWT tokens periodically, disrupting the effectiveness of simple automated scripts.

Finally, while going through the Reddit handle for Hamster Kombat, I also learned that Profit Per Hour (PPH) is crucial in the game. Mining activities are necessary to increase PPH, and it's recommended to revisit the app every 3 hours to ensure the miners continue operating. If the app is left unattended, the miners will cease their mining activities. Additionally, I noticed that whenever the game is started, it makes two additional POST calls to the endpoints clicker/sync and clicker/config.

Summary
URL: https://api.hamsterkombat.io/clicker/sync
Status: 200 OK
Source: Network
Address: 16.170.66.30:443
Initiator: 
entry.i4X6gxxf.js:17:19871

Request
POST /clicker/sync HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9
Authorization: Bearer ***TOKEN***
Connection: keep-alive
Content-Length: 0
Host: api.hamsterkombat.io
Origin: https://hamsterkombat.io
Referer: https://hamsterkombat.io/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Summary
URL: https://api.hamsterkombat.io/clicker/config
Status: 200 OK
Source: Network
Address: 51.21.54.68:443
Initiator: 
entry.i4X6gxxf.js:17:19871

Request
POST /clicker/config HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9
Authorization: Bearer ***TOKEN***
Connection: keep-alive
Content-Length: 0
Host: api.hamsterkombat.io
Origin: https://hamsterkombat.io
Referer: https://hamsterkombat.io/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148

Maybe we could invoke these endpoints every 10 minutes to simulate continuous engagement with the game and keep the miners running. With this in mind, here's my final revised script.

import aiohttp  
import asyncio  
import json  
import time  
import random  
  
  
async def main(job_id=1):  
    url = "https://api.hamsterkombat.io/clicker/tap"  
  
    current_time = int(time.time())  
    random_number = random.randint(1, 10)  
    timestamp = current_time - random_number  
  
    payload = json.dumps({  
        "count": 100,  
        "availableTaps": 1498,  
        "timestamp": timestamp  
    })  
  
    headers = {  
        'Content-Type': 'application/json',  
        'Bearer ***TOKEN***'
    }  
  
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:  
        async with session.post(url, headers=headers, data=payload) as response:  
            response_text = await response.json()  
            response_text = {  
                'total': response_text['clickerUser']['totalCoins'],  
                'balance': response_text['clickerUser']['balanceCoins'],  
                'level': response_text['clickerUser']['level'],  
            }  
            print(f"Job {job_id} response: {response_text}")  
  
  
async def sync(job_id=1):  
    url = "https://api.hamsterkombat.io/clicker/sync"  
  
    headers = {  
        'Content-Type': 'application/json',  
        'Bearer ***TOKEN***'
    }  
  
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:  
        async with session.post(url, headers=headers) as response:  
            response_text = await response.json()  
            print(f"Job {job_id} response: {response_text}")  
  
  
async def config(job_id=1):  
    url = "https://api.hamsterkombat.io/clicker/config"  
  
    headers = {  
        'Content-Type': 'application/json',  
        'Bearer ***TOKEN***'
    }  
  
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False)) as session:  
        async with session.post(url, headers=headers) as response:  
            response_text = await response.json()  
            print(f"Job {job_id} response: {response_text}")  
  
  
async def run_jobs():  
    last_sync_run_time = 0  
  
    while True:  
        try:                     
            if time.time() - last_sync_run_time > 600:  
                print('trigger sync and config')  
                last_sync_run_time = time.time()  
                await sync()  
            await main()  
            await asyncio.sleep(0.5)  
        except Exception as e:  
            print(e)  
  
  
# Run the jobs  
asyncio.run(run_jobs())

During my exploration of the Hamster Kombat web app, I identified several security weaknesses. These included the absence of CORS implementation, CSRF tokens, insufficient rate limiting, and the lack of a refresh token mechanism. Moreover, all requests were authenticated using a single JWT token. These vulnerabilities create opportunities for automated abuse and unauthorized access, compromising the game's integrity.

Over the course of running the script for a week, I achieved Grandmaster status at level 9 with a PPH of +638.37K. Each day, I logged into the game briefly to ensure I hadn't been banned. During these short sessions, I managed my account by purchasing new miners and upgrading existing ones. Automating coin acquisition with the script enabled me to fund these updates and purchases, illustrating how these weaknesses can be exploited.

While my experiment showcased how to leverage these vulnerabilities for high levels of success in the game, it also highlights the importance of adhering to basic security principles in web development. Ensuring robust security measures not only protects the application's integrity but also provides a fair and enjoyable experience for all users.

As developers, it's crucial to remain vigilant and proactive in identifying and addressing potential security gaps. By doing so, we can create more secure and resilient applications, safeguarding both our work and our users. I hope this post serves as a valuable reminder and guide for reinforcing security in your own projects. With that note, until next time, ciao!

Popular posts from this blog

Unlocking Success: Crafting an Integrated E-Commerce Marvel with Ewity

How I created something like Google Meet and Zoom