Initial analysis

This random challenge is a web challenge and it contains multiple stages which we must solve to pass the challenge.

First stage

When we accept the initial page, we get redirected to the first stage.

This one is easy to solve. If we look at the source we see that the correct URL is commented out.

<div class="breadcrumbs"<

<!-- Shut off the door --<

<!-- <p<Please <a
href="/stage2/guest/cGFuZW1wbG95ZWU="<login</p< --<


Now we can copy that URL and append it to the server address and we should arrive to next stage.


Second stage

When we have arrived at next stage we must login somehow into the web service. We can use a SQL injection for that.

I have used the typical “‘ OR ‘1’=’1 –“ for my SQL injection and it will forward us to the next stage.

Third stage

When we have successfully used SQL injection we arrive at third stage where we see a web shell.

If we go to tools we will see that there is a ssh command which description says that we can use it to ssh into other machines. After some try and error we can see that we can run commands like ls to list files. Using “ls –l” we can see that there is a file named top_secret in the correct directory owned by superuser. We have to become superuser to read the content of that file.

To become superuser we can use the reset password function and by intercepting the traffic we could reset the password for superuser and then we can login.

We have a verification code which we must analyze the given token because it looks like a base64 encoded string combined with some hex numbers. If we take a closer look to the verification code we will see that the first 7 numbers are in hex decimal (hash?) and the rest of the string is base64 encoded.

Verification code: 4e9d0a9MTYwMDc1OTE=

If we decode “MTYwMDc1OTE=” with base64 we will receive 16007591 which looks like a ID or a counter. The first part looks like a hashsum which we can verify fast by using hashlib from python:

import hashlib

username = "test \\' OR \\'1\\'=\\'1\\' --"

print "MD5: {}".format(hashlib.md5(username).hexdigest())

print "SHA1: {}".format(hashlib.sha1(username).hexdigest())

print "SHA256: {}".format(hashlib.sha256(username).hexdigest())
MD5: 4e9d0a9f54b7f6cf62b679f286f84f80

SHA1: 9f536ca522db68be167078692359bef713d10f58

SHA256: 6a5fd1359fb5bc53e894c2a2638989212eabf0f051e35b364cff0eeaa291af5c

and its MD5. Now we can see that the verification is built using first 7 characters of the username hash and that the second part is a counter or a ID.

Now we must write a script which generates us a valid verification code for the superuser. It looks somehow like this:

import hashlib
import base64
import sys

def get_hash(username):

    return hashlib.md5(username).hexdigest()[0:7]

def decode(a):

    hash_username = a[:7]

    counter = base64.b64decode(a[7:])

    return counter

my_verification_code = sys.argv[1]

decoded_verification_code = decode(my_verification_code)


# generate verification code for superuser


verification_code = get_hash('superuser') +
base64.b64encode(str(int(decoded_verification_code) + 1))

print "Verification code for superuser: {}".format(verification_code)

Now we are ready to reset the superuser password and for that we need Burp Suite. We must configure the proxy in Burp Suite and enable Intercept.

The password reset site will show us a verification code for current user and now we must use this one to generate a new verification code for superuser because we have to increment the counter.

$ python 4e9d0a9MTYwMDc1OTk=

Verification code for superuser: 0baea2fMTYwMDc2MDA=

Now we must hit “Reset password” again and change the user in Burp Suite. We must change the user to “superuser” and forward the data to the server because internally the server will generate a new counter for new password reset and this time it will use superuser as username.

Now we can enter a dummy password in the password field and hit “Reset password” button. In Burp Suite we should get another event which we have to update again with “superuser”.

and if we forward the event it will show us that we have successfully have reset the password for superuser. Now we can login as superuser and our new password and continue with the next stage.

For that we have to log off and go back to:

Forth Stage

After we have successfully logged in we see the next challenge.

But if we enter the captcha it won’t work and tell us it’s an invalid captcha. We must download the image and analyse it.

The downloaded captcha is a PNG file but if we take a look into it with a hex editor we will see that there is a PDF file attached to the PNG. Because PDF is such a great file format we can simply rename the file to PDF and a PDF viewer should ignore the PNG image and show us the PDF file.

If we open the PDF in a hex editor and go to the XREF table we will see that not all objects are used (n = used, f = free).

We have to change this object to “n” to make it usable in the PDF but for that we also must increment the object counter from 1 to 2:

Now we can save the PDF, open it again in a PDF viewer and we should see the real captcha:

In our case its: bc6e48

We also should write the key which it shows us in the captcha because it will be useful for next stage. After we have entered the correct captcha we will get forwarded to next stage.

Fifth stage

Now we are logged in as superuser and we must figure out what to do next.

We can try to see if there are any emails for the current user.

It tells us that there is a file which verifies the key and the passphrase for pancat. We should download that binary and see what pancat is. For that we should try ssh again and call the tool pancat.

It looks like that pancat expects a key, a passphrase and a file. For the key argument, we can use the one from our solved captcha because it showed us the real captcha and a key. The key was “12asdf12e12eafsdfh65gg34r2”. As the file argument, we should use top_secret file and the passphrase we have to figure out. The authentication binary can verify the passphrase if we have a valid key and passphrase.

To download authentication binary we can use the double encoding trick (“../../”). If we try this we should be able to download the binary:

Using file we can figure out that the downloaded binary is an ELF 64-bit executable:

authentication: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 2.6.24, BuildID[sha1]=cbc1994165d19affee280efffb63919cc6b7b9e0, not stripped

Now we must figure out the logic behind it the verification function.

Reverse engineering authentication

If we look at the binary with Binary Ninja we will see that the application takes two arguments. First argument is the key which we already know and the second argument is the passphrase. It will use the passphrase as salt for Linux crypt() function. The result of crypt() will be used for pwd_verify() function and if that returns 1 we have passed the check and the we should theoretically have the result.

But if we take a closer look at crypt() function we will see that the salt function is only 2 characters long. We can still reverse engineer the algorithm but that application will only return us at maximum 2 characters of the passphrase.

Here is the algorithm for the pwd_verify() function which could turn around and brute force it but it will only work for first 2 characters because of crypt() implementation. Here is the implementation of the algorithm in python:

def pwd_verify(key):

    k1 = ['i', 'w', 'I', '|', 'I', 'y', '_', '8', 'O', ';', 'I', '<', '\~']

    k2 = ['Z', 'Q', 'W', ']', 'V', 'T', 'Y', 'R', '[', 'U', 'X', '\\\\', 'S']

    result1 = [None] * (len(k1))

    result2 = [None] * (len(k2))

    for i in range(len(k1)):

        c = (ord(k2[i]) - 0x51)

        result1[c] = k1[i]

    for i in range(len(k2)):

        if ((i + 1) & 1):

            result2[i] = chr((ord(key[i]) + 5) & 0xff)


            result2[i] = chr((ord(key[i]) + 7) & 0xff)

            fResult = True

    for i in range(len(k1)):

        if result1[i] != result2[i]:

            fResult = False

        if fResult:

            print "You won the game!"


            print "You have lost."

We must think about a clever way how to solve this one and because we can’t get the passphrase from the binary file.

Blind SQL injection using sqlmap

We can go back to stage 2 and use blind SQL injection to query the database for more information. Blind SQL injection returns a TRUE/FALSE result and we can use this on second stage of the challenge because we know that it would let us login if the query returns TRUE and it would tell us that it’s an invalid username if the query returns FALSE.

For that we have to go back to stage 2 in a new web browser incognito window. Now we can use a blind SQL injection to figure out the table names and query the data from the interesting tables. For example, if we want to know if the sqlite_version starts with ‘3’ we can do this: test’ OR (select substr(sqlite_version(), 1, 1) == ‘3’) –

and if the query returns TRUE we will see this as response:

We start to feel like real hackers now but building blind sql queries might become complicated and we should use sqlmap which can do that for us.

Sqlmap configuration

To find the table names from the database we can have to use sqlmap but for that we need a requestfile which we can record with Burp Suite.

Now we must save that raw data to a file named ‘stage2.txt’ and then we can run sqlmap with this command line:

$ python -r ~/samples/stage2.txt –dbms=sqlite -p password –tables –risk=3 -v3

After a few seconds it will show us an interesting table named Shell_history.

We can now query the first 10 elements of that table using:

$ python -r ~/samples/stage2.txt –dbms=sqlite -p password -T Shell_history –dump –risk=3 –hex –start=1 –stop=10 –threads=4

Sqlmap will take a while to query the database schema but after that we should already see the first elements from the table. After around 15 minutes we will receive the first 10 elements from the Shell_history table but there is no pancat command there. Using the start and stop argument we can specify which output entry we want to retrieve.

Here are the first 10 commands from the shell:

Now that we know the column name for commands we can also specify that in the next query to make it a little faster:

$ python -r ~/samples/stage2.txt –dbms=sqlite -p password -T Shell_history –dump –risk=3 –hex –start=10 –stop=19 –threads=4 -C command

But still no luck. We must increment the counter again and see if we receive some interesting commands with the next query. We have to repeat this step until we see the pancat command which contains the passphrase. Using start=50 and stop=59 we can finally see some interesting commands:

“pancat -k 12asdf12e12eafsdfh65gg34r2 -p r12e21%12312fgaqj2# top_secret” looks exactly like what we are searching for.


Now we must go back to our session where we are still logged in as superuser and go to ssh tool and by entering the command “test; pancat -k 12asdf12e12eafsdfh65gg34r2 -p r12e21%12312fgaqj2# top_secret” we can read the flag:

Finally, we have the final flag: PAN{CONTENT_VULNERBILITY}.