// Ruby for Pentesters · Part 1

Your first recon tool

Install Ruby 4, meet irb, and write your very first script — an HTTP client that fingerprints a target. We point it at the retired HackTheBox machine Bizness and confirm what's running.

Rubynet/httpReconBeginner
7 min read

Welcome. Over this series you’re going to learn Ruby the way you’ll actually use it as a pentester: by writing your own tooling and pointing it at a real target. That target is Bizness, a retired HackTheBox machine. By the last part you’ll have a single Ruby program — and then a proper Metasploit module — that carries the whole chain from a blank terminal to a root shell.

No prior Ruby needed. This first part is the foundation: get the language running, learn how a Ruby program is shaped, and write a script that talks HTTP and tells us what the box is running.

Everything here is against a retired lab machine you own a connection to. Build tools, point them at things you’re allowed to touch.

Getting Ruby 4

The site you’re reading runs on Ruby 4, so let’s match it. Check what you have:

$ ruby -v
ruby 4.0.6 (2025-12-25 revision ...) [x86_64-linux]

If that prints a 4.x version, you’re set. If not, install it with your platform’s version manager (rbenv install 4.0.6, mise use ruby@4.0.6, or your distro’s package). The exact patch level doesn’t matter for this series; anything on the 4.0 line is fine.

irb: your scratchpad

Ruby ships with an interactive shell called irb. It runs code as you type it, which makes it the fastest way to try something. Launch it and do some math, then make a string:

$ irb
irb(main):001> 40 + 2
=> 42
irb(main):002> "hello".upcase
=> "HELLO"

Two things to notice. First, everything is an expression40 + 2 and "hello".upcase both return a value (irb prints it after the =>). Second, "hello" is an object, and .upcase is a method you call on it. In Ruby, even a plain string knows how to do things. You’ll lean on that constantly.

Type exit to leave irb.

Your first script

irb is for experiments; real tools live in files. Open a file called recon.rb and start with one line:

puts 'hello from Ruby'

puts (“put string”) prints its argument and a newline. Run the file:

$ ruby recon.rb
hello from Ruby

That’s the whole loop you’ll use forever: write to a .rb file, run it with ruby. If you’ll run it directly a lot, add a shebang as the first line so the shell knows how to execute it:

#!/usr/bin/env ruby
puts 'hello from Ruby'

Then chmod +x recon.rb and you can run ./recon.rb.

Variables and strings

A variable is a name for a value. No type declarations — you just assign:

target = 'bizness.htb'
puts "scanning #{target}"

That #{...} inside a double-quoted string is interpolation: Ruby evaluates the expression and drops the result into the string. It’s cleaner than gluing strings with +, and you’ll use it in every payload you build later.

Talking HTTP

Recon starts with a simple question: what answers when I knock? Ruby’s standard library has an HTTP client built in — no gems to install. Pull it in with require:

require 'net/http'
require 'uri'

target = URI('https://bizness.htb/')

http = Net::HTTP.new(target.host, target.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE   # lab box uses a self-signed cert

response = http.get('/')

puts "Status: #{response.code} #{response.message}"
puts "Server: #{response['server']}"

Walking it line by line:

  • require 'net/http' loads the HTTP client; require 'uri' loads the URL parser.
  • URI('https://bizness.htb/') turns the string into a URL object, so we can ask it for .host (bizness.htb) and .port (443) instead of splitting strings by hand.
  • Net::HTTP.new(host, port) creates a client. use_ssl = true because it’s HTTPS. Lab boxes serve self-signed certificates, so verify_mode = VERIFY_NONE tells Ruby not to reject the connection — the real-world equivalent of curl -k.
  • http.get('/') sends the request and hands back a response object.
  • response.code, .message, and response['server'] read the status and a header back off that object.

Run it:

$ ruby recon.rb
Status: 200 OK
Server: nginx/1.18.0 (Ubuntu)

An nginx reverse proxy — but that’s just the front door. The interesting part is what’s behind it.

When the knock goes unanswered

A real target isn’t always up. If the host is down, the port is filtered, or the VPN dropped, http.get doesn’t return a response — it raises an exception, and your script dies with a backtrace. A recon tool should expect that and say something useful instead. Wrap the risky call in begin/rescue:

begin
  response = http.get('/')
rescue StandardError => e
  abort "[-] #{target.host} is not answering: #{e.message}"
end
  • begin ... rescue ... end runs the code and catches any error raised inside.
  • StandardError is the parent of the everyday failures — a refused connection, a timeout, a DNS miss — so one rescue covers them; => e binds the exception object so we can read e.message.
  • abort prints to stderr and exits non-zero, which is how a command-line tool should fail.

Exceptions are Ruby’s error channel. You’ll rescue them any time you touch the network, the filesystem, or anything that can fail out from under you — which, in this line of work, is most things.

Fingerprinting the app

The response body is the HTML the server sent. Let’s look for a tell. Add:

body = response.body

if body.include?('Apache OFBiz')
  puts '[+] Apache OFBiz detected'
else
  puts '[-] OFBiz not found in body'
end

response.body is just a String, and String#include? returns true or false — a boolean — depending on whether the substring is present. We feed that to an if/else to branch. Run it again:

$ ruby recon.rb
Status: 200 OK
Server: nginx/1.18.0 (Ubuntu)
[+] Apache OFBiz detected

There it is. Apache OFBiz — a big Java ERP suite — sitting behind nginx. That single fact decides the whole engagement: OFBiz has a well-known authentication-bypass CVE from 2023, and confirming the stack is what points us at it. You just wrote the tool that found it.

The finished script

Put together, your first recon tool:

#!/usr/bin/env ruby
require 'net/http'
require 'uri'

target = URI('https://bizness.htb/')

http = Net::HTTP.new(target.host, target.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

response = http.get('/')
puts "Status: #{response.code} #{response.message}"
puts "Server: #{response['server']}"

if response.body.include?('Apache OFBiz')
  puts '[+] Apache OFBiz detected'
else
  puts '[-] OFBiz not found in body'
end

Twenty lines, no dependencies, and it does something real.

Takeaways

  • Ruby code lives in .rb files you run with ruby, or you experiment live in irb. Everything is an object, and you call methods on objects.
  • puts prints; variables are just names you assign to; "#{...}" interpolation builds strings cleanly.
  • net/http + uri give you a full HTTP client in the standard library — Net::HTTP.new, use_ssl, VERIFY_NONE for lab certs, .get, then read .code / .body off the response.
  • Anything that can fail — a request, a file — raises an exception; wrap it in begin/rescue and abort with a clear message instead of dumping a backtrace.
  • Recon is just asking a question and reading the answer. Ours found Apache OFBiz, and that’s the thread we pull next.

Exercises

Reading a tool isn’t writing one. Try these before moving on:

  1. Print the response’s Content-Type header and the body’s length on one line with interpolation — e.g. text/html; charset=utf-8, 24187 bytes.
  2. Add an elsif: if the body doesn’t mention OFBiz but the Server header contains nginx, report [?] nginx, unknown app. Only fall to the [-] branch when neither matches.
  3. Point the script at a port nothing listens on (URI('https://bizness.htb:9999/')) and confirm your begin/rescue prints one clean line instead of a backtrace.

Next — Part 2: we turn that OFBiz finding into access, writing a Ruby function that walks straight through the authentication check.

← All parts