Frage

Ich habe mich über den neuesten Trend, Test Driven Development (TDD), informiert.Die meiste Entwicklung, die ich mache, erfolgt in C oder C++.Mir fällt auf, dass es einen sehr offensichtlichen Konflikt zwischen gängigen TDD-Praktiken und gängigen sicheren Codierungspraktiken gibt.Im Kern sagt Ihnen TDD, dass Sie keinen neuen Code für etwas schreiben sollten, für das Sie keinen fehlgeschlagenen Test haben.Für mich bedeutet das, dass ich keinen sicheren Code schreiben sollte, es sei denn, ich führe Unit-Tests durch, um zu sehen, ob mein Code sicher ist.

Das wirft zwei Probleme auf:

  1. Wie kann ich Unit-Tests effektiv schreiben, um auf Pufferüberläufe, Stapelüberläufe, Heap-Überläufe, Array-Indexfehler, Format-String-Fehler, ANSI- vs. Unicode- vs. MBCS-String-Größenkonflikte und eine sichere String-Behandlung (aus „Writing Secure Code“ von Howard und LeBlanc) zu testen? )?

  2. An welchem ​​Punkt der Standard-TDD-Praxis sollten diese Tests einbezogen werden, da ein Großteil der Sicherheit nicht funktionsfähig ist?

Überraschenderweise habe ich nur sehr wenig Forschung zu TDD und Sicherheit gefunden.Das meiste, worauf ich stoße, sind TDD-Papiere, in denen auf sehr hoher Ebene erwähnt wird, dass TDD „Ihren Code sicherer macht“.

Ich suche nach direkten Antworten auf die oben genannten Fragen, nach Recherchen, die sich darauf beziehen (ich habe bereits gesucht und nicht viel gefunden), oder nach einem Ort, an dem TDD-Guru leben, damit ich (virtuell) an ihre Tür klopfen kann und Sehen Sie, ob sie gute Antworten haben.

Danke!

BEARBEITEN:

Es ist das Thema Fuzzing aufgetaucht, das meiner Meinung nach (im Allgemeinen) eine großartige Lösung für dieses Problem darstellt.Dies wirft die Fragen auf:Passt Fuzzing in TDD?Wo im TDD-Prozess passt Fuzzing?

Auch parametrisierte Unit-Tests (möglicherweise automatisiert) sind mir in den Sinn gekommen.Dies könnte eine Möglichkeit sein, früher im Testprozess Fuzzing-ähnliche Ergebnisse zu erhalten.Ich bin mir auch nicht sicher, wo das genau in TDD passt.

EDIT 2:

Vielen Dank an alle für eure bisherigen Antworten.An dieser Stelle bin ich äußerst daran interessiert, wie wir parametrisierte Tests nutzen können, um als Pseudo-Fuzzer für unsere Funktionen zu dienen.Aber wie bestimmen wir, welche Tests zum Testen der Sicherheit geschrieben werden müssen?Und wie können wir sicher sein, dass wir den Angriffsraum ausreichend abdecken?

Es ist ein bekanntes Problem in der Softwaresicherheit, dass der Angreifer beim Schutz vor fünf Angriffsszenarien einfach nach einem sechsten Angriff Ausschau hält und diesen ausführt.Es ist ein sehr schwieriges Katz-und-Maus-Spiel.Verschafft uns TDD hier einen Vorteil?

War es hilfreich?

Lösung

Ja, TDD ist ein Tool/eine Technik, die dabei helfen kann, eine sichere Codierung zu gewährleisten.

Aber wie bei allen Dingen in dieser Branche:Gehen Sie davon aus, dass es sich um eine Wunderwaffe handelt, und Sie schießen sich selbst in den Fuß.

Unbekannte Bedrohungen

Wie Sie in Edit 2 angegeben haben:„Sie schützen vor 5 Angriffsszenarien, der Angreifer sucht einfach nach einem 6. Angriff und nutzt ihn.“TDD ist nicht wird Sie vor unbekannten Bedrohungen schützen.Naturgemäß müssen Sie wissen, was Sie testen möchten, um den Test überhaupt schreiben zu können.

Angenommen, Bedrohung Nummer 6 wird entdeckt (hoffentlich nicht aufgrund eines Verstoßes, sondern intern aufgrund eines anderen Tools/einer anderen Technik, die versucht, potenzielle Angriffsvektoren zu finden).

TDD hilft wie folgt:

  • Es können Tests geschrieben werden, um die Bedrohung zu überprüfen.
  • Es kann eine Lösung implementiert werden, um die Bedrohung zu blockieren, und es kann schnell bestätigt werden, dass sie funktioniert.
  • Und was noch wichtiger ist: Vorausgesetzt, dass alle anderen Tests weiterhin bestanden werden, können Sie schnell Folgendes überprüfen:
    • Alle anderen Sicherheitsmaßnahmen verhalten sich weiterhin korrekt.
    • Alle anderen Funktionen verhalten sich weiterhin korrekt.
  • Grundsätzlich trägt TDD dazu bei, eine schnelle Bearbeitungszeit von der Entdeckung einer Bedrohung bis zur Verfügbarkeit einer Lösung zu ermöglichen.
  • TDD bietet außerdem ein hohes Maß an Sicherheit, dass sich die neue Version korrekt verhält.

Testbarer Code

Ich habe gelesen, dass TDD oft als Testmethodik missverstanden wird, obwohl es sich in Wirklichkeit eher um eine Designmethodik handelt.TDD verbessert das Design Ihres Codes und macht ihn mehr testbar.

Spezialisierte Tests

Ein wichtiges Merkmal von Testfällen ist ihre Fähigkeit, ohne Nebenwirkungen ausgeführt zu werden.Das bedeutet, dass Sie Tests in beliebiger Reihenfolge und beliebig oft ausführen können und sie niemals fehlschlagen sollten.Dadurch lassen sich eine Reihe anderer Aspekte eines Systems allein aufgrund der Testbarkeit einfacher testen.Zum Beispiel:Leistung, Speichernutzung.

Diese Tests werden in der Regel durch die Durchführung spezieller Prüfungen einer gesamten Testsuite implementiert – ohne direkte Auswirkungen auf die Suite selbst.

Ein ähnliches Sicherheitstestmodul könnte eine Testsuite überlagern und nach bekannten Sicherheitsproblemen suchen, z. B. nach sicheren Daten im Speicher, Pufferüberläufen oder jedem neuen Angriffsvektor, der bekannt wird.Eine solche Überlagerung hätte ein gewisses Maß an Vertrauen, da sie für alle überprüft wurde bekannte Funktionalität vom System.

Verbessertes Design

Eine der wichtigsten Designverbesserungen, die sich als Nebeneffekt von TDD ergeben, ist explizite Abhängigkeiten.Viele Systeme leiden unter der Last impliziter oder abgeleiteter Abhängigkeiten.Und diese würden Tests praktisch unmöglich machen.Infolgedessen sind TDD-Designs tendenziell modularer an den richtigen Stellen.Aus Sicherheitsgründen können Sie damit Dinge tun wie:

  • Testen Sie Komponenten, die Netzwerkdaten empfangen, ohne diese tatsächlich über das Netzwerk senden zu müssen.
  • Man kann Objekte leicht so modellieren, dass sie sich auf unerwartete/„unrealistische“ Weise verhalten, wie es in Angriffsszenarien vorkommen könnte.
  • Testen Sie Komponenten isoliert.
  • Oder mit jedem gewünschten Mix an Produktionskomponenten.

Unit-Tests

Es sollte beachtet werden, dass TDD eine stark lokalisierte Version (Unit-Tests) bevorzugt.Als Ergebnis können Sie Folgendes leicht testen:

  • SecureZeroMemory() würde ein Passwort korrekt aus dem RAM löschen.
  • Oder das GetSafeSQLParam() würde korrekt vor SQL-Injection schützen.

Es wird jedoch immer schwieriger zu überprüfen, ob alle Entwickler an allen erforderlichen Stellen die richtige Methode verwendet haben.
Ein Test zur Überprüfung einer neuen SQL-bezogenen Funktion würde bestätigen, dass die Funktion funktioniert – sie würde sowohl mit der „sicheren“ als auch mit der „unsicheren“ Version von GetSQLParam genauso gut funktionieren.

Aus diesem Grund sollten Sie andere Tools/Techniken nicht vernachlässigen, die zur „Gewährleistung einer sicheren Codierung“ verwendet werden können.

  • Codierungsstandards
  • Codeüberprüfungen
  • Testen

Andere Tipps

Ich werde zuerst Ihre zweite Frage beantworten.Ja, TDD-Funktionen können für nicht funktionale Anforderungen verwendet werden.Tatsächlich wird es oft als solches verwendet.Der häufigste Vorteil eines verbesserten modularen Designs ist zwar nicht funktionsfähig, wird aber von jedem gesehen, der TDD praktiziert.Weitere Beispiele, die ich mit TDD überprüft habe:plattformübergreifend, datenbankübergreifend und leistungsstark.

Für alle Ihre Tests müssen Sie möglicherweise den Code umstrukturieren, damit er testbar ist.Dies ist einer der größten Effekte von TDD – es verändert wirklich die Art und Weise, wie Sie Ihren Code strukturieren.Zunächst sieht es so aus, als würde dies das Design stören, doch schnell stellt man fest, dass das testbare Design besser ist.Ohnehin...

Fehler bei der String-Interpretation (Unicode vs.ANSI) lassen sich besonders gut mit TDD testen.Normalerweise ist es einfach, die schlechten und guten Eingaben aufzuzählen und Aussagen zu ihrer Interpretation zu treffen.Möglicherweise müssen Sie Ihren Code etwas umstrukturieren, um ihn „testbar“ zu machen.Damit meine ich Extraktionsmethoden, die den stringspezifischen Code isolieren.

Bei Pufferüberläufen ist es ebenfalls recht einfach zu testen, sicherzustellen, dass Routinen ordnungsgemäß reagieren, wenn zu viele Daten bereitgestellt werden.Schreiben Sie einfach einen Test und senden Sie ihnen zu viele Daten.Behaupten Sie, dass sie getan haben, was Sie erwartet haben.Einige Pufferüberläufe und Stapelüberläufe sind jedoch etwas schwieriger.Sie müssen in der Lage sein, dies zu bewirken, aber Sie müssen es auch herausfinden wie man erkennt, ob sie passiert sind.Dies kann so einfach sein wie das Zuweisen von Puffern mit zusätzlichen Bytes und das Überprüfen, dass sich diese Bytes während der Tests nicht ändern ...Oder es können andere kreative Techniken sein.

Ich bin mir jedoch nicht sicher, ob es eine einfache Antwort gibt.Das Testen erfordert Kreativität, Disziplin und Engagement, lohnt sich aber in der Regel.

  • Isolieren Sie das Verhalten, das Sie testen müssen
  • Stellen Sie sicher, dass Sie das Problem erkennen können
  • wissen, was im Fehlerfall passieren soll
  • Schreiben Sie den Test und sehen Sie, dass er fehlschlägt

Hoffe das hilft

TDD ist der beste Weg, ein sicheres System aufzubauen.Die gesamte von Microsoft entwickelte Software ist unscharf und dies ist wohl der Hauptgrund für die dramatische Reduzierung der gefundenen Schwachstellen.Ich empfehle dringend, das zu verwenden Pfirsichrahmen für diesen Zweck.Ich persönlich habe Peach mit großem Erfolg beim Auffinden von Pufferüberläufen eingesetzt.

Peach-Pit-Dateien bieten eine Möglichkeit, die von Ihrer Anwendung verwendeten Daten zu beschreiben.Sie können auswählen, welche Schnittstelle Sie testen möchten.Liest Ihre Anwendung Dateien?Hat es einen offenen Port?Nachdem Sie Peach mitgeteilt haben, wie die Eingabe aussieht und wie Sie mit Ihrer Anwendung kommunizieren sollen, können Sie sie loslassen und ich kenne alle fiesen Eingaben, die Ihre Anwendung zum Kotzen bringen.

Damit alles läuft, hat Peach eine tolle testing harness, Wenn Ihre Anwendung abstürzt, erkennt Peach dies, da ein Debugger angeschlossen ist.Wenn Ihre Anwendung abstürzt, startet Peach sie neu und testet weiter.Peach kann alle Abstürze kategorisieren und die Core-Dumps mit den Eingaben abgleichen, die zum Absturz der Anwendung verwendet wurden.

Parametrisierte Tests

Während wir bei meiner Arbeit keine Pufferüberlauftests durchführen, haben wir doch die Vorstellung von Vorlagentests.Diese Tests sind so parametrisiert, dass sie die spezifischen Daten für den Fall erfordern, den wir testen möchten.Anschließend verwenden wir Metaprogrammierung, um die echten Tests dynamisch zu erstellen, indem wir die Parameter für jeden Fall auf die Vorlage anwenden.Dies hat den Vorteil, dass es deterministisch ist und als Teil unserer automatisierten Testsuite ausgeführt wird.

Meine TDD-Praxis

Wir führen bei meiner Arbeit eine abnahmetestgesteuerte Entwicklung durch.Die meisten unserer Tests ähneln nahezu Full-Stack-Funktionstests.Der Grund dafür ist, dass wir es für wertvoller hielten, das Verhalten benutzergesteuerter Aktionen zu testen und sicherzustellen.Wir verwenden Techniken wie die dynamische Testgenerierung aus parametrisierten Tests, um mit minimalem Aufwand eine größere Abdeckung zu erreichen.Wir tun dies für ASCII vs. UTF8, API-Konventionen und bekannte Variantentests.

Das Thema Fuzzing ist aufgetaucht, was meiner Meinung nach ein großartiger Ansatz für dieses Problem ist (im Allgemeinen).Dies wirft die Fragen auf:Passt Fuzzing in TDD?Wo im TDD-Prozess passt Fuzzing?

Ich glaube, dass es ganz gut passen könnte!Es gibt Fuzzer wie Amerikanischer Fuzzy-Lop die skriptfähig sind und sich selbständig an Änderungen im I/O-Format anpassen.In diesem speziellen Fall könnten Sie Integrieren Sie es mit Travis CI, speichern Sie die von Ihnen verwendeten Eingabetestfälle und führen Sie Regressionstests für diese durch.

Ich könnte diese Antwort erweitern, wenn Sie Fragen zu Einzelheiten in den Kommentaren haben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top