Was schiefgehen kann · Übung
Ein Angriff ist ein Test, der rot beginnt
Der dritte Angreifer aus Lektion 2 hat eine Kopie der Daten und sendet nie eine Anfrage. Diese Lektion zeigt, was diese Kopie
python pl_sec.py stolen-backup
pl_sec.py startet deinen eigenen Server auf 127.0.0.1, sendet ihm ein paar Anfragen und gibt aus, was passiert ist. Das ist die Ausgabe, bis auf die
pl_sec: stolen-backup, your server on 127.0.0.1 (a port the system chose)
--- two ordinary requests ---
ana logs in with her password POST /login -> 200
ana asks who she is, with a cookie GET /me -> 200 {"user": "ana"}
--- a copy of your server's state, as a thief would read it ---
books 2 books (not shown)
next_id 3
users 'ana': 'correct horse'
users 'ben': 'battery staple'
tokens 'tok-ana-writes-7Qx2': {'user': 'ana', 'can_write': True}
tokens 'tok-ben-reads-K9pz': {'user': 'ben', 'can_write': False}
sessions 'sJFKdwVWzmnyheWotZw-vA': {'user': 'ana'}
--- what the thief tries next: each stored value as that user's password ---
log in as ana with the value stored for ana -> 200 the thief is logged in
log in as ben with the value stored for ben -> 200 the thief is logged in
--- the code in reading_server.py that checks a password ---
if credentials is None or state["users"].get(credentials["user"]) != credentials["password"]:
--- result ---
2 of 2 stored values worked as a password
serve() returned after 5 connections; the server is closed
pl_sec: every thread has stopped
Lies das Protokoll
Der erste Block ist normale Benutzung: ana meldet sich an und fragt, wer sie ist. Er steht da, damit der Zustand eine aktive Session enthält.
Der zweite Block ist die Kopie. Sie enthält beide Passwörter, beide Tokens und anas Session-ID, alles lesbar. Im dritten Block tut der Dieb das Erste, was jeder versuchen würde: Er sendet jeden gespeicherten Wert als Passwort des jeweiligen Benutzers, und beide Anmeldungen antworten mit 200. Der vierte Block zitiert die Zeile deines Servers, die ein Passwort prüft.
Der Dieb im Protokoll meldet sich als ben an. Was brauchte er zuerst?
Zwei Befunde
F1 kommt aus den zwei Zeilen von users. Exposed: das Passwort jedes Benutzers. Attack: Jeder mit einer Kopie des Zustands liest die Passwörter und meldet sich als jeder Benutzer an. Fix: Speichere etwas, das beweist, dass ein Passwort richtig ist, ohne das Passwort zu sein. Kapitel 3 baut das.
F2 kommt aus der zitierten Zeile. Die Anmeldung vergleicht den Versuch mit dem gespeicherten Wert mit !=. Kapitel 2, Lektion 3 erklärt, warum dieser Vergleich ein eigener Befund ist. Halte vorerst seine vier Teile fest, damit die Zeile existiert. Exposed: wie viel von einem geratenen Geheimnis richtig ist. Attack: != kann bei einem schlechteren Versuch früher antworten. Fix: Vergleiche ein Geheimnis mit hmac.compare_digest. Test: none yet.
Vom Befund zum Test
Der vierte Teil eines Befunds ist sein Nachweis. In diesem Kurs ist ein Nachweis ein Angriffstest (attack test): ein Test, der den Angriff ausführt und zusichert, dass der Angriff abgelehnt wird. Solange die Schwachstelle da ist, schlägt der Test fehl. Sobald sie geschlossen ist, läuft der Test durch und hält Wache.
Für F1 hat der Test drei Schritte, die Form aus Vorbereiten, Ausführen und Prüfen, die du kennst, wobei das Ausführen ein Angriff ist:
Vorbereiten. Bau mit
reading_server.sample_state()einen Zustand auf. Melde dich dann als ana mit ihrem Passwort an und sichere zu, dass der Status 200 ist. Diese erste Zusicherung ist eine Vorbedingung. Sie sorgt dafür, dass der Test einen Server betrifft, auf dem anas Passwort funktioniert.Angreifen. Nimm die Kopie:
str(state)macht aus dem ganzen Zustand Text, wie es eine Sicherung täte.Die Ablehnung zusichern.
correct horsedarf in diesem Text nicht vorkommen.
Nimm die Kopie nach der Anmeldung. Ein Server könnte ein Passwort auch während der Bearbeitung der Anfrage preisgeben, zum Beispiel in eine Session, und eine früher genommene Kopie würde das übersehen.
tests/helpers.py erspart dir, Anfragen von Hand zu bauen. Lies jetzt make_request und basic. Der Rest der Datei ist für spätere Kapitel.
headers = {"Authorization": helpers.basic("ana", "correct horse")}
request = helpers.make_request("POST", "/login", headers=headers)
make_request baut das Anfrage-handle() bekommt, dieselbe basic(user, password) baut den Wert für Authorization mit Basic-Zugangsdaten.
Warum meldet sich der Test zuerst an und erwartet den Status 200?
Rot ist heute die richtige Farbe
Wenn der Test geschrieben ist, führ die Datei aus:
python -m pytest -q
Sie endet mit 1 failed, und pytest zeigt den Grund: 'correct horse' is contained here, gefolgt von dem Stück des Zustands, das es enthält. Dieses Fehlschlagen ist der Befund, wiederholbar gemacht. Jeder kann jetzt einen Befehl ausführen und F1 sehen.
Der Test bleibt rot bis Kapitel 3, Lektion 6, wo du änderst, wie Passwörter gespeichert werden. Der Kommentar oben in tests/test_attacks.py sagt das, und keine Prüfung vorher verlangt, dass dieser Test durchläuft.
Du führst deinen neuen Angriffstest gegen den Server in seinem jetzigen Zustand aus, und er läuft durch. Was sagt dir das?
Eine Grenze, klar gesagt: Dieser Test beweist, dass kein gespeicherter Wert das Passwort als Text ist. Ein Server, der das Passwort in
Warum über das Modul
Die Testdatei beginnt mit import reading_server, und dein Test ruft reading_server.sample_state() und reading_server.handle(...) auf. Lass es so und schreib nie from reading_server import handle.
Der Grund ist, wie dein Test geprüft wird. Check my work führt deinen Test gegen andere Server aus: einen schwachen, der Passwörter im Klartext speichert, einen korrigierten, der kein lesbares Passwort behält, und einen, der jede Anmeldung ablehnt, bei dem die Vorbedingung deinen Test fehlschlagen lassen muss. Es setzt sie ein, indem es reading_server.handle und reading_server.sample_state ersetzt. Ein Test, der auf dem schwachen Server fehlschlägt und auf dem korrigierten durchläuft, hat gezeigt, dass er die Schwachstelle sieht. Ein einzeln importierter Name würde weiter auf deinen Server zeigen, und der Austausch bliebe unbemerkt. Ein Zustands-Dictionary, das du von Hand baust, würde auch nicht zu den anderen Servern passen, deshalb beginnt der Test mit sample_state().
Deine Aufgabe
Schreib in
tests/test_attacks.pyunter dem Kommentar für diese Lektiontest_attack_backup_reveals_no_password()mit den drei Schritten oben.Führ
python -m pytest -qaus und sieh zu, wie er bei der letzten Zusicherung fehlschlägt.Öffne
SECURITY.mdund fülle die Zeilen F1 bis F4 aus. Nimm F1 und F2 aus dieser Lektion und F3 und F4 aus Lektion 3, gern in deinen eigenen Worten. Halte jede Zeile auf einer Zeile. Trag den Namen deines Tests in die Zelle Test von F1 ein, schreibnone yetin die anderen drei und lass jeden Status aufopen.
Klicke dann auf Check my work. Du verlässt dieses Kapitel mit vier festgehaltenen Befunden, von denen einer durch einen fehlschlagenden Test bewiesen ist. Kapitel 2 baut die zwei Werkzeuge, die die Behebungen brauchen.
Aufgabe
Schreib den ersten Angriffstest und halte vier Befunde fest.
Schreib in tests/test_attacks.py die test_attack_backup_reveals_no_password():
Bau mit
reading_server.sample_state()einen Zustand auf.Sende über
reading_server.handle(...)POST /loginfür ana mit ihrem Passwortcorrect horseund sichere zu, dass der Status 200 ist.helpers.basic("ana", "correct horse")baut denfür Authorization, undhelpers.make_request("POST", "/login", headers=...)baut die Anfrage.Mach dann mit
str(state)aus dem ganzen Zustand Text und sichere zu, dasscorrect horsenicht darin vorkommt.
Führ im python -m pytest -q aus: Der Test muss fehlschlagen. Er bleibt rot bis Kapitel 3, Lektion 6.
Fülle dann die Zeilen F1 bis F4 von SECURITY.md aus: F1 und F2 aus dem, was python pl_sec.py stolen-backup ausgegeben hat, F3 und F4 aus Lektion 3. Halte eine Zeile pro Befund, trag den Namen deines Tests in die Zelle Test von F1 ein und lass jeden Status auf open.
Klicke dann auf Check my work. Es führt deinen Test viermal aus, jedes Mal gegen einen anderen Server, den es an die Stelle von reading_server.handle und reading_server.sample_state setzt: einen, dessen Zustand Passwörter im Klartext enthält, wo dein Test fehlschlagen muss; einen, der anas Passwort akzeptiert und keine lesbare Kopie davon behält, wo dein Test durchlaufen muss; einen, der das Passwort bei der Anmeldung in die SECURITY.md: Die Zeilen F1 bis F4 brauchen Text in ihren Zellen Exposed, Attack und Fix, und F1 braucht den Namen deines Tests. Die Formulierung deiner Befunde wird nicht bewertet, und nichts aus deinem Terminal wird bewertet.