October 18, 2026
How I Bypassed a Region-Locked Comment Feature on a Public Bug Bounty Program
A comment feature was blocked in my country, but it still worked fully through the API.
By Mostafa Waleed
5 min read
Why I picked a target with 2,000+ reports
Every hunter sees a program like this and thinks about skipping it. A big video platform with support videos and reels, and a leaderboard with thousands of resolved reports. If that many people already tested it, what is left for you? ๐ค
I tested it anyway, mostly to challenge myself. A big program is not fully clean, it is just clean from the easy bugs. The first things everyone tries, like IDOR on video IDs or broken auth on signup, are already gone. What stays is the weird logic that only breaks when you set up something most people never try. This writeup is about one of those cases.
Mapping the app, and one that got away
I started like everyone does. I made an account, read the full program policy, and listed the features worth testing. Sign in, sign up, every parameter I could touch, and a lot of trying to open private videos I should not see. Nothing.
The first thing that worked was a race condition on the like button. I sent the like request many times at once, and the counter went up more than it should ๐. It was a real bug, but on a program this big a bug like this is almost always already reported. It got closed as duplicate ๐ฅฒ. That is normal, and I want to say it clearly: finding a real bug and getting paid for it are two different things.
So I kept going.
A feature that only existed somewhere else
While testing the pro feature, I saw that the subscription was not available in my country. That is not a bug, it is just a business choice. But it made me think. If one feature is blocked by region, other features are probably blocked too, and this kind of region logic is often done in a messy way.
So I did a simple test. I opened two browsers side by side: one with a VPN in a supported country, and one on my normal connection. I opened the same videos in both and looked for anything that showed in one but not the other.
For a few minutes I saw nothing. Then it hit me: the VPN browser had a comments section, and mine did not. From my country I could not read comments and I could not post them. The feature was just not there.
The restriction lived in the UI, not the API
A missing button does not tell you where the block really is. Maybe the backend blocks it, or maybe only the app hides it while the API still answers anyone. The only way to know is to ask the API directly.
So I took the two requests behind the feature from the VPN browser, the GraphQL request that reads comments and the one that posts a comment, and I replayed them from my normal connection with the VPN off. Both examples below are simplified and only for illustration. The host, IDs, and token are placeholders.
Reading comments:
POST /graphql HTTP/2
Host: api.example.com
Authorization: Bearer <AUTH_TOKEN>
Content-Type: application/json
{
"operationName": "GetVideoComments",
"variables": { "videoId": "<VIDEO_ID>", "page": 1 },
"query": "query GetVideoComments($videoId: String!, $page: Int) { video(id: $videoId) { id title comments(first: 10, page: $page) { totalCount edges { node { id text createDate } } } } }"
}POST /graphql HTTP/2
Host: api.example.com
Authorization: Bearer <AUTH_TOKEN>
Content-Type: application/json
{
"operationName": "GetVideoComments",
"variables": { "videoId": "<VIDEO_ID>", "page": 1 },
"query": "query GetVideoComments($videoId: String!, $page: Int) { video(id: $videoId) { id title comments(first: 10, page: $page) { totalCount edges { node { id text createDate } } } } }"
}Posting a comment:
POST /graphql HTTP/2
Host: api.example.com
Authorization: Bearer <AUTH_TOKEN>
Content-Type: application/json
{
"operationName": "CreateComment",
"variables": { "input": { "videoId": "<VIDEO_ID>", "text": "Hello world" } },
"query": "mutation CreateComment($input: CreateCommentInput!) { createComment(input: $input) { status comment { id text createDate } } }"
}POST /graphql HTTP/2
Host: api.example.com
Authorization: Bearer <AUTH_TOKEN>
Content-Type: application/json
{
"operationName": "CreateComment",
"variables": { "input": { "videoId": "<VIDEO_ID>", "text": "Hello world" } },
"query": "mutation CreateComment($input: CreateCommentInput!) { createComment(input: $input) { status comment { id text createDate } } }"
}And it worked ๐. From a country where the UI showed no comments at all, I could read the whole comment list and post new comments that went through. The backend never checked where the request came from. It just trusted the app to hide the feature, and the app is the one thing the attacker fully controls.
Root cause: the check was never on the server
The feature was blocked by region, but the block was in the wrong place. The app decides whether to show the comments UI based on your region. The GraphQL resolvers behind it do not do the same check.
What should happen: a comment read or write from a blocked region is rejected by the API, no matter which app sent it.
What actually happens: the API answers any logged-in user. The region block only works while you use the official app the normal way.
This is a classic client-side security check. The rule is real and on purpose, but it was never enforced on the server, only in the frontend. Anyone with a proxy and a logged-in account can go around it, because going around a UI is easy.
Impact, without exaggerating
I want to be clear here, because this kind of bug is easy to oversell.
What I proved: a user in a region where comments are off can still read and post comments by calling the API directly. That is the full proven impact.
Why it still matters: companies block features by region for real reasons, like content moderation rules, local laws, and legal risk. When the block is only on the surface, the company thinks a control is working in a market where it is not. So this is more of a compliance and policy problem than a data leak, and that is the angle that made the report land.
What I did not claim: no account takeover, no access to private data, no admin actions. Only report what you can prove. It keeps your report strong and keeps the triager on your side.
The web report was accepted as medium and rewarded ๐.
Same bug, different door
The program policy had a line I remembered: the same bug found in a different parameter can count as a separate report. The scope also had an Android app, so I installed it, sent its traffic through my proxy, and looked for the comment feature again.
It was there, and the app used a different request than the website ๐. Same bug, different parameter, so by the program's own rules it was a separate, valid report. That became my second valid report.
Final score on this target: three reports. One duplicate (the like race condition) and two valid comment bypasses, web and mobile.
Takeaways For hunters:
- A big, crowded program is not empty, it is just missing the easy stuff. Look for logic that only breaks with a setup most people skip, like comparing two sessions from different regions.
- Read the policy well enough to know when one idea can turn into two reports.
- Only report what you can prove. It looks more professional.
Believe in yourself, that is what makes you sit down and test a scary target. But method is what turns a small "wait, where is that button?" into a real finding. Enjoy hunting boys โ๏ธ