GRIMM.WTF /

  /l、             
(゚、 。 7         
  l  ~ヽ       
  じしf_,)ノ
  open/close

Fansly Livestreams Privilege Persistence


tldr

Unless a stream is restarted, a user who had access to view a stream on the platform would be able to maintain that access by recording the stream, circumventing any change in access permissions (typically associated with some form of payment) which the client properly updates to stop the user from viewing the stream.

Backstory

Around 5 months ago, I started working on this discord bot which allows creators on the subscription-based social media platform Fansly, to notify their discord community on new post or live streams, helping bring functionality common with other platforms (eg. Twitch, YouTube, etc.) to this one. However, to my knowledge, Fansly does not provide oauth or API/library, similar to something like discord.js, for you to install and be able to interact with their platform. This means in order to get this working, you have to rely on making direct API calls to their platform using your user account token, typically referred to as a selfbot.

In order to make this easier on myself, to reverse-engineer their API I used mitmproxy2swagger and got to interacting with their platform, from following, unfollowing, scrolling feeds, reading DMs, liking posts, and of course, live streams. Now while the bot really only needed to focus on two things, live streams and the creator’s timeline feed, I thought better safe to have more in case of new features for the bot.

The platform allows for pretty rigorous permission and access control for their creators to setup, from requiring a user to be following the creator, to requiring the user to be subbed to any or a specific tier, to requiring the user to purchase a stream ticket and any mix of the options (eg. following AND purchase a ticket OR subbed to tier wtv), the point being creators have a lot of control for who gets to access their content.

In interacting with the platform I’ve noticed a decent amount of creators require you to follow them to access their streams, so I followed a creator to be sure to map what the streaming endpoint would return when you have access to a stream. Example of what the streaming endpoint returns:

"endpoint": "https://apiv3.fansly.com/api/v1/streaming/channel/{creator_id}?ngsw-bypass=true"

{
  "success": true,
  "response": {
    "id": "string",
    "accountId": "string",
    "playbackUrl": "string",
    "chatRoomId": "string",
    "status": 0,
    "version": 0,
    "createdAt": 0,
    "updatedAt": {},
    "stream": {
      "id": "string",
      "historyId": "string",
      "channelId": "string",
      "accountId": "string",
      "title": "string",
      "status": 0,
      "viewerCount": 0,
      "version": 0,
      "createdAt": 0,
      "updatedAt": {},
      "lastFetchedAt": 0,
      "startedAt": 0,
      "permissions": {
        "permissionFlags": [
          {
            "id": "string",
            "streamId": "string",
            "type": 0,
            "flags": 0,
            "price": 0,
            "metadata": "string",
            "verificationFlags": 0
          }
        ],
        "accountPermissionFlags": {
          "flags": 0,
          "metadata": "string"
        }
      },
      "whitelisted": true,
      "accountPermissionFlags": 0,
      "access": true,
      "playbackUrl": "string"
    },
    "arn": {},
    "ingestEndpoint": {}
  }
}

However, something interesting happened when i went back to the stream to map the response, the stream was going on as usual, but then the stream view updated to show a screen similar to the below image, minus the following requirement (I didn’t take a screenshot at the time so had to try to find someone with this setup.) This would normally be fine, their permissions seem to be setup properly and I, who only followed could no longer see the stream, but for a short while after I could still hear what was going on. At the time I didn’t think much of it and just went back to interacting to reverse the API but it’s been in the back of my head ever sense.

Access Persistence

As shown in the api response, there’s a playbackUrl key returned to the stream object, this is only there if you have access to the stream and would contain a link similar to the following:

"playbackUrl": "https://8e69c661e048.us-east-1.playback.live-video.net/api/video/v1/us-east-1.862541535858.channel.1CevFyOcvoei.m3u8?token={jwt.access.token}"

Having some background in web-scraping and automation I’m familiar with taking these playlist urls and using ffmpeg to record the stream output, which works as expected. However upon any access permission change the client updates to show you no longer have access to the stream, as stated previously, but that didn’t stop the ongoing recording from ffmpeg, allowing you to bypass payment to gain access to exclusive parts of a stream.

This wasn’t limited to the creator changing permissions either, a user could easily follow to gain access to the stream, start recording and unfollow, losing access to the stream on the client, but still being able to save and potentially redistribute in real time.

Key points

  1. Once authorised and accessed users can initiate a recording of the stream.
  2. This recording will continue uninterrupted, even when the streamer changes access permissions.
  3. This only works if you start recording at any point of a stream you have access to. If you try to record a stream while not having access, the API properly responds without the playbackUrl key-pair.
  4. A creator restarting the stream to start with the new access permissions prevents the further recording of the stream.

Feature not a bug

I reported the issue to Fansly January 27, 2025 and on January 29, 2025 I was told their development team was contacted and said the “feature is working as intended and were unable to identify any bugs with regard to live stream permissions,” but at least their help section for livestreaming got updated to warn creators to restart streams to properly apply new access permissions (I do not have a picture of the before).