Der Artikel hat die Bringschuld auf seiner Seite, er sollte nachweisen, dass das dort vermittelte Loginsystem bestimmte Sicherheitsanforderungen erfüllt und nicht umgekehrt.
Wenn ich mir die Bearbeitungshistorie ansehe, dann wurde offensichtlich bei Bekanntwerden von Mängeln nachgearbeitet. Mehr kann zumindest ich nicht verlangen. Inwiefern ein Artikel in unserem Wiki Deiner Forderung gerecht werden kann, "bestimmte Sicherheitsanforderungen" zu erfüllen, kann ich nicht einschätzen, da mir hierzu schlicht das Wissen und die Erfahrung dazu fehlen. Nach wie vor möchte ich allerdings die Frage stellen, wie hoch man hier die Messlatte für das Wiki wirklich hängen kann und sollte.
Das ist eine wichtige Kernfrage, die ich gerne vertiefen möchte. Ich verstehe uns als Erstanlaufstelle für angehende EntwicklerInnen, für SchülerInnen, Azubis, Studierende und für den oder die gemeine Hobby- und Amateur-ProgrammiererIn. Wir bekennen uns also ganz klar zu einer unerfahrenen Zielgruppe. Auf der anderen Seite ist Softwaresicherheit ein hochkompliziertes und heikles Thema. Wir können, wie du auch schon sagtest, das Thema nicht erschöpfend in einem Wiki-Artikel ausarbeiten. Die Frage bleibt also, was möchten wir dem Leser mit auf den Weg geben? Für mich sind das die folgenden Schwerpunkte.
- Wir müssen die Komplexität des Themas eindringlich vermitteln. Ein Loginsystem spielt in einer anderen Liga als ein Tic-Tac-Toe-Spiel. Wir sollten also gleich zu Beginn mit der unbequemen Wahrheit rausrücken, dass der oder die LeserIn am Ende des Artikels unmöglich in der Lage sein wird, ein einsatzbereites Loginsystem zu entwickeln. Es braucht Jahre, dass nötige Know-How dafür anzuhäufen. Wir können aber Alternativen benennen, und das heißt: nicht selber zu machen, sondern ein bereits erprobtes System einzusetzen. Das bringt mich zum nächsten Punkt:
- Es ist wichtig greifbare und objektiv erfassbare Maßstäbe zu benennen, um die Indizien sicherer bzw. unsicherer Software zu erkennen. Sicherheit ist keine Sache blinden Vertrauens, auch nicht blinden Selbstvertrauens. Man muss sich unabdingbar Vergewissheit verschaffen, dass das Programm das Richtige tut. Joseph Weizenbaum, ehem. MIT-Professor, hat das mal in schöne Worte gefasst:
A computer will do what you tell it to do, but that may be much different from what you had in mind.
Vielleicht habe ich die Diskussionen um diesen speziellen Artikel nicht genau genug verfolgt, jedoch sind mir keine Forumshinweise erinnerlich, die mit formalen Methoden sicher belegt hätten, dass der Artikel unzweifelhaft unsichere Vorgehensweisen anböte.
So funktionieren formale Verfahren auch nicht. Andersherum wird ein Schuh draus: Tests können die Absenz von bestimmten Fehlern nachweisen. Eine Standardvorgehensweise ist zum Beispiel, beim Bekanntwerden eines Bugs, erst einen Test zu entwickeln, der in der betroffenen Software-Version fehlschlägt. Anschließend wird ein Patch entwickelt, welches den Testfall löst. Die Zend-EntwicklerInnen machen das sehr diszipliniert.
Ich hätte kein Problem damit, wenn es einen formalen Nachweis für eine bestehende Unsicherheit gäbe. Einen formalen Nachweis für die Sicherheit der angebotenen Lösung im Artikel als Bestandteil desselben zu verlangen, halte ich aus meiner Sicht für überzogen!
Es ist an dieser Stelle auch nicht sinnvoll, dem Leser zu vermitteln, wie er selber solche Formalismen entwickelt. Wichtiger ist es, die Bedeutung dieser Art technischer Verifikation zu betonen und ihn darauf hinzuweisen, dass gebräuchliche Authentifizierungssysteme starken Gebrauch davon machen. Von da aus sollte er zu der Selbsteinsicht gelangen, dass Selbermachen keine Option ist.
Und wer führt den formalen Nachweis, dass die Einstufung korrekt ist? Ich kann das nicht, das übersteigt bei weitem meine Fähigkeiten und mein Wissen.
DAS! Das ist genau die Einsicht, zu der ein Leser des Wiki-Artikels gelangen sollte. Damit möchte ich auch die zwischenmenschliche Debatte hier abschließen: Wir müssen eine Kultur etablieren, in der es nichts Schlimmes ist, sich zu fehlenden Fachkenntnissen zu bekennen und Scheitern nicht als etwas rein Negatives empfunden wird. Ich gestehe auch ein, mit meinem Handeln zu Beginn, nicht uneingeschränkt in diesem Sinne gearbeitet zu haben. Das nehme ich aus dieser Diskussion mit und hoffe, dass sich diese Eskalation nicht wiederholen wird.
- Eine klare Trennung zwischen Schnittstellen- und Anwendungscode. Dazu gehört auch eine formale Schnittstellenbeschreibung. PHP bietet dafür ein inzwischen recht robustes Typsystem und gute Testing-Frameworks. Gluecode, der die Schnittstelle mit der Beispielanwendung koppelt, sollte möglichst bündig ausfallen, wenn er zu lang wird, ist das ein Zeichen dafür, dass die Schnittstelle nicht die richtigen Abstraktionen bietet.
Ich habe gefühlte 37% davon verstanden oder zumindest nachvollziehen können. In meinen Projekten verwende ich PHP ohne jede Art von Framework. Ein Framework kenne ich nur von JavaScript, wo ich mit jQuery gewisse Erfahrungen machen konnte. Allerdings kenne ich das Einbinden von Klassen in PHP. Mit einer eingebundenen Klasse kann man dann Funktionalitäten nutzen, die man sich nicht selbst geschrieben hat.
Ein Testing-Framework ist mit jQuery nicht vergleichbar. Es ist ein Entwickler-Werkzeug, ein Meta-Programm, um die Entwicklung von Programmen sicherer zu gestalten und dem Entwickler mehr Gewissheit über die Qualität seines Produkts zu verschaffen. Ein Software-Test stellt eine Behauptung über ein Programm, indem es bestimmte Problem-Instanzen mit bekannten Lösungen definiert. Wird der Test ausgeführt, startet er das Programm mit den bekannten Problem-Instanzen als Eingabe und überprüft, ob die Ausgabe den erwarteten Lösungen entspricht. Nehmen wir zum Beispiel an, dass wir eine squareRoot-Funktion in PHP implementieren wollten:
// squareRoot.php
function squareRoot($x) {
return sqrt($x);
}
Nun, wollen wir wissen, ob die Funktion das tut, was wir von ihr erwarten. Wir definieren dafür Unit-Tests:
// test.php
require "squareRoot.php";
$in = 4;
$expected = [2,-2];
$actual = squareRoot($in);
if ($actual !== $expected) {
echo "Test fehlgeschagen. Erwarteter Wert squareRoot($in) entspricht nicht $expected";
} else {
echo "Test bestanden: squareRoot($in) entspricht $expected";
}
Dieser Test schlägt bereits fehl, weil wir bei der Implementierung vergessen haben, dass die Wurzel einer positiven Zahl, mehrere Lösungen hat. Wir können die Implementierung also anpassen:
function ($x) {
return [sqrt($x), -sqrt($x)];
}
Damit sollte dieser Test nun bestanden werden. Testen wir weiter? Was ist, wenn wir eine negative Zahl als Eingabe übergeben. Die Funktion sollte uns dann eine komplexe Zahl als Lösung liefern, für -1 zum Beispiel die komplexe Zahl i. Angenommen, wir haben eine Datei "ComplexNumber.php", die uns Basis-Funktionalität für das Rechnen mit komplexen Zahlen zur Verfügung stellt, dann können den folgenden Test schreiben:
// test.php
require "squareRoot.php";
require "ComplexNumber.php";
$in = -1;
$expected = \ComplexNumber\I;
$actual = squareRoot($in);
if ($actual !== $expected) {
echo "Test fehlgeschagen. Erwarteter Wert squareRoot($in) entspricht nicht $expected";
} else {
echo "Test bestanden: squareRoot($in) entspricht $expected";
}
Der Test wird wieder fehlschlagen. Wir können nun wieder hingehen und die Implementierung entsprechend anpassen. Testing-Frameworks bieten uns konventionelle Methoden, um Testfälle zu definieren und um das Testen in unseren Arbeitsfluss zu integrieren. Viele agile Entwicklerteams nutzen Continuous-Integration-Serivces, um sich Push-Benachrichtigungen einzuholen, falls ein Testcase plötzlich fehlschlägt.
Die "klare Trennung zwischen Schnittstellen- und Anwendungscode" halte ich auch für eine Quadratur des Kreises. Ideen wie Smarty verwenden PHP in HTML, um PHP als Template-Sprache zu benutzen. In meinen Projekten verwende ich HTML-Dokumente, die von gewissen Methoden mit gewissen Inhalten befüllt werden. Wie sollte man also trennen? Und das Ganze bitte im Kontext eines Tutorials für Anfänger und Fortgeschrittene - nicht für Profis! Profis sind ohnehin in gewissen Projekten eingebunden, die alle ihre eigenen Vorschriften und Vorgehensweisen haben.
- Das System muss auf seine Kernaufgaben beschränkt werden. Schichten, die bspw. nur der Abwärtskompatibilität dienen, sollten sich nicht darin wiederfinden.
Aha... Schichten... Meinst Du Code, der das Fehlen von Funktionalitäten kompensiert, wenn veraltete PHP-Versionen eingesetzt werden?
Genau das meinte ich mit Kompatibilitätsschicht. Die Trennung ist zunächst mal konzeptionell, aber spiegelt sich häufig auch physisch in der Organisation des Codes wieder. Ich meine hier inbesondere die konzeptionelle Unterscheidung von Code nach seinen Aufgabenbereichen. Eine veraltete PHP-Version, die keine Sicherheitsupdates mehr erhält, darf und kann auch gar nicht die Grundlage für ein sicheres Software-System sein. Die Kette ist nur so stark wie ihr schwächstes Glied. Sichere Software-Systeme müssen deshalb ständig überwachen, ob all ihre Abhängigkeiten noch aktuell sind. Dafür benutzt man Paket-Verwaltungsysteme. Mit Composer kann man zum Beispiel mit composer outdated
herausfinden, für welche Pakete Updates existieren.
Ups Schreiblimit erreicht, für den zweiten Teil meiner Antwort hier entlang, bitte.