Hacking the Medium partner program

Mohammad-Ali Bandzar
6 min readSep 26, 2020

This is the journey detailing how my name was added to humans.txt for scoring my first bug bounty, a severity 2 one at that! Im writing this as i’ve always personally been interested in how people discover security vulnerabilities. Furthermore, vulnerability itself is incredibly easy to exploit, details can be found at the end of this article. (pls scroll slowly so I can rake in some partner program earnings)

Back story

I was originally going to write an article describing testing I conducted in an attempt to figure out how much of the $5 monthly fee is given to writers through the Medium partner program. I had planned to test out different interactions which I thought might affect a writer’s compensation. Such as reading the article for different amounts of time and seeing if interacting with the article (through means such as: highlighting text, clapping) would affect the writer’s compensation. Since I am on a shoestring budget I only wanted to pay for a single “control” account to do all my testing from. I originally had 20+ different scenarios I intended to test and could only test a single one per day as Medium Partner Program earnings are calculated daily, I was looking for ways to automate the process. Thus, I went exploring through the chrome developer tools to analyze how data is transmitted back to medium.com to see if it was possible to automate some of my tests.

I began by one by one blocking requests to certain url paths that I believed could be transmitting the metrics used to calculate my partner program earnings. After a lot of trial and error I discovered that it was requests to medium.com/_/batch that was transmitting all the data used to calculate my partner program earnings.

a screenshot of some of the data transmitted back to medium HQ through /_/batch endpoint

I didn’t have any way of figuring out what a lot of the data transmitted back to the medium.com/_/batch endpoint represents, I had at the time planned to perform a replay attack of the requests made to this endpoint only modifying timestamp data to make it relevant to the time the replay attack is launched (which could be days after I initially captured all the requests made to this endpoint).

Since I didn’t want to copy-paste all my cookies into the python script I was writing to preform my replay attack. I tried clearing my browser of all cookies after loading the webpage, Without any of the cookies present and being transmitted to medium.com/_/batch I was still able to generate partner program earnings. I then attempted to analyze the request JSON data to see if I could simply generate all the post request data required for a session of an arbitrary duration at once (essentially a whole bunch of page scroll events) and transmit it all in one go to your JSON endpoint medium.com/_/batch. (this would have allowed me to automate testing sessions of different lengths). That, unfortunately, did not work.

I originally believed that it was because the server was likely validating transmission time to stop someone from potentially creating partner program earnings for a writer for a period of time that they were not a member, say months in the future or past. But after some more experimentation (with requests sent at the correct timing and different payloads) I believe that the most probable explanation of why this didn’t work is that the eventID in the JSON payload is some sort of hash containing the UserID and the date of the page view as there does appear to be a pattern in EventIDs as the first 4 letters of the ID appear to be the same for requests from the same UserID and day. Due to code obfuscation, I was unable to figure out exactly how these userIDs are generated.

Discovering the vulnerability

It was at this very same time I realized that nothing unique to the user was being transmitted to medium.com/_/batch other than the userID that was a part of every json object in the request payload (as demonstrated in the photo above). Modifying the userID by itself did not allow me to generate partner program earnings. (Keep in mind that no matter what you request from the endpoint it will always respond with ”success”: true.) So I was taking stabs at the dark with trial and error attempting to figure out how the endpoint was authenticating these requests. I belive that it was because I did not know how to compute valid EventIds (this is only something I believe needs to be done, I have no idea if these Ids represent anything beyond a random string of letters and numbers). I originally tried to use a find and replace on the website’s main HTML file to replace my current userID with that of another paid account I owned then trying to call the JS functions that are called when the window is initialized. That unfortunately didn’t get me anywhere as the code obfuscation on medium.com is too aggressive for me to understand what was going on. My next plan was to launch a Man in the Middle attack against myself using something like MITMProxy the Python library that would allow me to find and replace my userID with one of the people I wanted to impersonate while the data was in transit before it ever reached my web browser.

Lucky break

I decided to try changing the requesting UID cookie value for the request to the main HTML page of an article. To my absolute amazement medium.com embedded the uid cookie value into the webpage as JSON data before validating that the session cookie and UID cookie represent a valid logged in session. Although the webpage does correctly identify my uid/session cookie as being invalid; it will still embed the UID data from the request cookie into the webpage. In the response, it sends back the “set-cookie” headers with information of a new session UID starting with “lo_”. Because of this, whatever UID cookie value the main web page request is made with, all batch requests (your api endpoint) will use that value as the “UserId”. Here’s an example where I’ve changed the userID to “Hello World”

This is the request payload for the /_/batch endpoint, ive expanded 2 of the array items at random

I also found out that it is incredibly easy to find out peoples userIDs all you have to do is navigate to their profile and search for “{\”id\”:” in the HTML code of the webpage to find out their userID and you will see that the Netflix Tech BlogUserID is :”c3aeaf49d8a4”(on a side note, these userIDs are meant to be public)

So now if you change your UID cookie to one of a paying user, then load up an article you have written on medium, Medium will transmit data to its /_/batch endpoint using someone else’s userID. At this point, if you were to read the article, it would generate revenue for the writer. Medium in no way validates the userID values and just assumes them to be correct.

Heres the article if you want to check it out

I was able to generate 34 cents by impersonating about 10 people in a single day. I obviously did not want to go overboard testing the concept as I did not want Medium thinking I had ill intent. I reported the bug through their bug bounty program first before writing any of this up.


  • To conclude, I discovered that the member reading time appears to have no relationship to partner program earnings.(refer to above photo)
  • We learnt that medium.com will embed any userID cookie value you transmit to them into their webpage (this is something they haven’t yet fixed).
  • We learnt that the medium partner program endpoint does not verify that the user has a valid logged in session but instead just blindly accepts UserIDs and assumes they are correct.
  • Moreover we have no idea if I’m the first person to have ever discovered this vulnerability, Medium partner program writers could have been cheated out of an unknown amount of money as they could have been sharing their revenue with people who had exploited this vulnerability.

Hire Me

Feel free to reach out to me by email: Mohammad-Ali@Bandzar.com