// Ruby für Pentester · Teil 9
Dein Werkzeug testen
Ein Werkzeug, das du erneut ausführst, verdient ein Sicherheitsnetz. Wir lernen minitest kennen, Rubys eingebautes Test-Framework, und wie man testbaren Code schreibt — die Logik, die du prüfen kannst, vom Netzwerk trennen, das du nicht prüfen kannst.
Teil 8 hat aus unseren Skripten ein echtes Werkzeug gemacht. Werkzeuge werden bearbeitet, und Bearbeitungen brechen Dinge — meist auf die stille Art, wo es noch läuft, aber Müll zurückgibt. Ein Test ist ein zweites Stück Code, das prüft, ob das erste noch tut, was du denkst. Einmal geschrieben, wird jede künftige Änderung gratis geprüft. Ruby liefert ein Test-Framework in der Standardbibliothek: minitest.
Dein erster Test
Eine Testdatei erfordert minitest/autorun, erbt von Minitest::Test und
definiert Methoden, deren Namen mit test_ beginnen:
require 'minitest/autorun'
class SanityTest < Minitest::Test
def test_math_still_works
assert_equal 42, 40 + 2
end
end
Lauf die Datei direkt, und minitest findet und führt jede test_-Methode aus:
$ ruby sanity_test.rb
Run options: --seed 12345
# Running:
.
Finished in 0.001s, 1000 runs/s, 1000 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Jeder . ist ein bestandener Test; assert_equal(expected, actual) ist das
Arbeitspferd — es scheitert laut, wenn die beiden nicht übereinstimmen. Das ist
die ganze Schleife: behaupte, was wahr sein soll, lauf, achte auf die Punkte.
Die Logik testen, nicht das Netzwerk
Die testenswerten Teile sind die mit echter Logik — das Hashing aus Teil 6, die Extraktions-Regex aus Teil 5. Entscheidend: keiner braucht eine lebende Box. Sie sind reine Funktionen von Eingabe zu Ausgabe, also kann ein Test sie in Millisekunden festnageln:
require 'minitest/autorun'
require 'digest'
require 'base64'
def ofbiz_hash(word, salt)
Base64.urlsafe_encode64(Digest::SHA1.digest(salt + word), padding: false)
end
def parse_hash(body)
body[/currentPassword="(\$SHA\$[^"]+)"/, 1]
end
class BiznessTest < Minitest::Test
def test_hash_matches_known_pair
assert_equal 'uP0_QaVBpDWFeo8-dRzDqRwXQ2I', ofbiz_hash('monkeybizness', 'd')
end
def test_wrong_password_differs
refute_equal 'uP0_QaVBpDWFeo8-dRzDqRwXQ2I', ofbiz_hash('hunter2', 'd')
end
def test_parse_pulls_the_hash
line = 'currentPassword="$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I" userLoginId="admin"'
assert_equal '$SHA$d$uP0_QaVBpDWFeo8-dRzDqRwXQ2I', parse_hash(line)
end
end
$ ruby test_bizness.rb
...
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips
Drei wissenswerte Assertions: assert_equal (sie stimmen überein),
refute_equal (sie stimmen nicht — beweist, dass ein falsches Passwort einen
anderen Hash gibt) und assert_includes für „ist dieser Teilstring darin”. Es
gibt eine für jede Form der Erwartung.
Auf Testbarkeit hin entwerfen
Beachte, warum parse_hash testbar war: Es nimmt einen Response-Body als String
und gibt den Hash zurück — das Parsen ist vom Abrufen getrennt. Das ist der ganze
Trick. Eine Methode, die die Anfrage macht und die Antwort durchwühlt, kann
ohne ein lebendes Ziel nicht getestet werden; teil sie in zwei — eine Methode
ruft ab, eine andere parst den zurückgegebenen String —, und der Parser wird
isoliert prüfbar.
Tests zu schreiben schiebt dich leise zu dieser saubereren Form, was der halbe Grund ist, sich die Mühe zu machen. Das Werkzeug, das du testen kannst, ist meist das gut gebaute Werkzeug.
Erkenntnisse
- minitest liegt Ruby bei:
require 'minitest/autorun', erbe vonMinitest::Test, und jedetest_*-Methode läuft, wenn du die Datei ausführst. assert_equal,refute_equal,assert_includes(und Vettern) drücken aus, was wahr sein soll; eine gescheiterte Assertion meldet genau, was nicht passte.- Teste reine Logik — Hashing, Parsen —, weil sie kein Netzwerk braucht und die Teile festnagelt, die bei einer Änderung am ehesten brechen.
- Parsen vom Abrufen zu trennen macht Code testbar; nach Tests zu greifen stupst dich in dieses bessere Design.
Übungen
- Füge einen Test hinzu, dass
ofbiz_hashdeterministisch ist — dasselbe Wort und Salt erzeugen immer denselben Digest. - Schreib
parse_user(body), dasuserLoginIdherauszieht, dann einen Test, der behauptet, dass es für die Beispielzeile'admin'zurückgibt. - Füge eine
setup-Methode hinzu, die die Beispielzeile einmal in@linebaut, und schreib die Parse-Tests so um, dass sie@linenutzen, statt den String zu wiederholen.
Weiter — Teil 10: das Finale. Wir nehmen genau diese Kette und packen sie neu
als Metasploit-Modul — dasselbe Ruby, das du bereits kennst, eingelassen in
die Struktur des Frameworks, sodass use und run einfach funktionieren.