HackThisSite Programming Level 11 involves decoding a string of randomly generated numbers using another randomly generated Shift number. This HackThisSite level is a bit more difficult than the first one. Unlike the other levels, it has a five second timer, which makes it almost impossible to copy/paste the randomly generated numbers into a script and then copy/paste the answer back into the form.

Now we could do something complex like use the new ronin-web-browser library to control an instance of Chrome, login to HackThisSite, navigate to the challenge webpage, scrape the randomly generated numbers, compute the answer, enter the answer into the form, and click the submit button. Luckily for us HackThisSite is designed like a classical PHP webapp. There’s no CSRF tokens, no JWT, no client-side JavaScript rendering, etc, just a session ID set in the session cookie. All we have to do is send the same HTTP requests that the browser would send.

First, We will need to extract the session ID from the cookie in the browser. To extract the session ID simply login to HackThisSite in a web browser, open the JavaScript console and evaluate document.cookie. The session ID will be the random characters after HackThisSite=.

Next we will need to write the Ruby script which will send an HTTP GET request with our session cookie to the challenge page at /missions/prog/11/, scrape the randomly generated numbers, calculate the answer, and then send an HTTP POST request also with our session cookie to the form end-point at /missions/prog/11/index.php.

For the script, we will use the ronin-support library which adds a bunch of helper methods and makes performing HTTP requests much easier. Note ronin-support is a dependency of ronin and should already be installed.

The solution to the challenge is basically to subtract the random Shift number from each of the randomly generated numbers, then convert each resulting number to an ASCII character, and join the ASCII characters into a String. Note however that the randomly generated numbers are separated by a random non-numeric deliminator character, such as %, ", $, etc. In order to extract only the numbers from the randomly generated string of numbers, we can use the String#scan method with the /\d+/ regex to return an Array containing only the numeric strings.

#!/usr/bin/env ruby
require 'ronin/support'
include Ronin::Support

# build the session cookie
session_id     = ENV['SESSION_ID']
session_cookie = "HackThisSite=#{session_id}"

# create a persistent HTTP connection to www.hackthissite.org:443
http = http_connect_uri('https://www.hackthissite.org', cookie: session_cookie)

# get the challenge page
response = http.get('/missions/prog/11/')

# check the response
unless response.code == '200'
  print_error "could not request webpage: HTTP #{response.code}"
  exit -1
end

# find the randomly generated numbers after "Generated String: "
unless (match = response.body.match(/Generated String:\s+([^<]+)/))
  print_error "could not find 'Generated String:' in response body"
  exit -1
end

# extract the randomly generated numbers
generated_string  = match[1]
generated_numbers = generated_string.scan(/\d+/).map(&:to_i)

# find the random Shift number
unless (match = response.body.match(/Shift:\s+(-?\d+)/))
  print_error "could not find 'Generated Shift:' in response body"
  exit -1
end

# extract the Shift number
shift = match[1].to_i

# debugging
print_info "Generated Numbers: #{generated_numbers.join(' ')}"
print_info "Shift: #{shift}"

# subtract Shift from each number, convert each number to a ASCII char, and join
shifted_numbers = generated_numbers.map { |number| number - shift }
decoded_ascii   = shifted_numbers.map(&:chr).join

# debugging
print_info "Answer: #{decoded_ascii}"

# submit the answer!
response = http.post('/missions/prog/11/index.php', referer:   'https://www.hackthissite.org/missions/prog/11/',
                                                    form_data: {solution: decoded_ascii})

# check the response
unless response.code == '200'
  print_error "failed to successfully submit the form: HTTP #{response.code}"
  exit -1
end

if response.body.include?('Sorry:')
  print_error "incorrect solution!"
  exit -1
else
  print_success "Congratz!"
end

Also note that the HackThisSite form end-point also expects a Referer header.

To run the Ruby script:

export SESSION_ID="..."
ruby solution.rb

Which should print the following output:

[*] Generated Numbers: 36 40 70 57 30 29 69 62 32 28
[*] Shift: -17
[*] Answer: 59WJ/.VO1-
[+] Congratz!

Now, if you check your profile page shows, it should show that you have solved Programming: (11).

If Ronin interests you or you like the work we do, consider donating to Ronin on GitHub, Patreon, or Open Collective so we can continue building high-quality free and Open Source security tools and Ruby libraries.