Benny: Sortierung über URL und den ORDER-Befehl

Hallo,

mein Problem bzw. meine Frage betreffen Datenbankabfragen in PHP und MySQL.

Mit diesem Befehl

  
SELECT * FROM buecher WHERE autor='".mysql_real_escape_string($_GET['filter'])."' ORDER BY titel ASC";  

und der URL xyz.php?filter=autorname

kann ich Daten aus meiner Datenbank auslesen und über die URL beeinflussen. Damit erzähle ich ja nichts Neues vermute ich :-).

Ich würde jetzt, bei einer anderen Abfrage, gerne die Sortierung über die URL und ORDER "abwickeln", allerdings finde ich hierzu keine Lösung. Ich meine theoretisch

  
SELECT * FROM buecher ORDER BY //HIER DER FILTER, ABER WELCHER BEFEHL?// ASC";  

Könnt Ihr mir helfen? Vielen Dank!

  1. hi,

    Mit diesem Befehl

    SELECT * FROM buecher WHERE autor='".mysql_real_escape_string($_GET['filter'])."' ORDER BY titel ASC";

      
    Wird` $_GET['filter']`{:.language-php} vor der Abfrage noch [validiert](http://community.de.selfhtml.org/my/zitatesammlung/zitat1392)?  
    Alles, was User eingeben ist Böse, schon den Aufruf deiner Seite musst du als Angriff betrachten und dementsprechend reagieren.  
      
    [Funktionen für den Umgang mit Usern](http://forum.de.selfhtml.org/archiv/2008/8/t175711/#m1155768).  
      
    
    > Ich würde jetzt, bei einer anderen Abfrage, gerne die Sortierung über die URL und ORDER "abwickeln", allerdings finde ich hierzu keine Lösung. Ich meine theoretisch  
    > ~~~php
      
    
    > SELECT * FROM buecher ORDER BY //HIER DER FILTER, ABER WELCHER BEFEHL?// ASC"; 
    
    

    Nach welchen Kriterien soll denn ge„ORDER“t werden? Du müsstest ja Theoretisch noch einen Query an deine URI dranhängen, die du dann auch ausliest.

    xyz.php?filter=autorname;orby=wasauchimmer

      
     SELECT * FROM buecher ORDER BY '".$_GET['orby']."' ASC"; 
    

    $_GET['orby'] wird natürlich mit der gleichen Paranoia wie alle $Usereingaben behandelt.

    holla holla

    --
    Alle Angaben ohne Gewehr.
    Hey, wenn's dir nicht gefällt, mach neu ...
    1. Hi,

      das ist keine öffentliche Seite, sondern dient nur meinen Zwecken, insofern wird da kein User etwas eingeben. Ich hoffe doch, dass da keiner fündig wird und danach "schnüffelt".

      Ich habe jetzt diesen Schnipsel

      SELECT * FROM buecher ORDER BY '".$_GET['orby']."' ASC";

        
      eingebaut und mit  
        
      xyz.php?orby=nachname  
        
      aufgerufen, allerdings funktioniert das nicht. Habe ich das noch 'was übersehen oder vergessen?  
        
      Danke!
      
      1. hi,

        das ist keine öffentliche Seite, sondern dient nur meinen Zwecken, insofern wird da kein User etwas eingeben. Ich hoffe doch, dass da keiner fündig wird und danach "schnüffelt".

        Wenn die Seite Online ist, empfiehlt sich immer ein Schutzmechanismus, in so einem Fall wäre .htaccess angebracht.

        SELECT * FROM buecher ORDER BY '".$_GET['orby']."' ASC";

        xyz.php?orby=nachname
        aufgerufen, allerdings funktioniert das nicht. Habe ich das noch 'was übersehen oder vergessen?

        Was mir gerade aufgefallen ist, die ORDER Klausel benötigt keine ', also

        "SELECT * FROM buecher ORDER BY ".$_GET['orby']." ASC";

        Müsste eigentlich hinhauen, wenn es die Tabelle nachname gibt.

        holla holla

        --
        Alle Angaben ohne Gewehr.
        Hey, wenn's dir nicht gefällt, mach neu ...
        1. hi,

          "SELECT * FROM buecher ORDER BY ".$_GET['orby']." ASC";

          Was du auf jedenfall tun solltest ist die Endgültige Abfrage immer mit mysql_real_escape durchführen.

          Sonst hast du vielleicht irgendwann mal sowas hier ;

          "SELECT * FROM buecher WHERE Verlag = 'O'Reilly'"

          holla holla

          --
          Alle Angaben ohne Gewehr.
          Hey, wenn's dir nicht gefällt, mach neu ...
          1. Moin!

            "SELECT * FROM buecher ORDER BY ".$_GET['orby']." ASC";

            Was du auf jedenfall tun solltest ist die Endgültige Abfrage immer mit mysql_real_escape durchführen.

            Du produzierst hier gerade höheren Unsinn.

            Auf der einen Seite forderst du bei einem ziemlich gut durch mysql_real_escape_string() abgesicherten Query noch irgendeine weitergehende Validierung.

            Dann aber baust du dem Fragesteller einen Query mit nacktem GET-Parameter zusammen, bei dem man prinzipbedingt keinerlei Escaping einbauen kann, und gehst mit keiner Silbe darauf ein, dass du da gerade das perfekte Einfallstor für SQL-Injection gebaut hast.

            Sonst hast du vielleicht irgendwann mal sowas hier ;

            "SELECT * FROM buecher WHERE Verlag = 'O'Reilly'"

            Dieser Angriff kann nicht stattfinden, da im Query des OP mysql_real_escape_string() für diesen Teil ja verwendet wird.

            Die Angabe hinter "ORDER BY" muss allerdings kein Text sein, den man escapen könnte, sondern eine existierende Tabellenspalte. Hier sind also besondere Maßnahmen notwendig, man kann nicht einfach einen beliebig manipulierbaren GET- (oder POST-) Parameter verwenden und direkt in den Query einbauen!

            Man muss verhindern, dass irgendetwas anderes als eine der erlaubten, existierenden Spaltennamen in den Query hineinkommt. Das erreicht man am besten dadurch, dass man sich eine Wertetabelle in einem Array ablegt, und als gewünschten Parameter dann nur die jeweiligen Array-Indices verwendet. Das auf- oder absteigende Sortieren könnte man dorthinein ebenso integrieren.

            $sortieren = array(1 =>"autor ASC", 2 =>"autor DESC", /*weitere Einträge für andere Spalten*/);

            Der Parameter zum Sortieren enthält dann nur eine Zahl, deren Existenz im Array man prüft, und in den Query wandert dann der Inhalt des Arrayfeldes, nicht irgendein vom Benutzer manipulierter String.

            Das wäre dann in verständlicher Langform:

            if (isset($_GET['order']) && isset($sortieren[$_GET['order']])) {  
              $sql = "SELECT * FROM tabelle ORDER BY ".$sortieren[$_GET['order']];  
            }  
            else  
            {  
              $sql = "SELECT * FROM tabelle"; // keine Sortierung - man könnte auch eine Standardsortierung angeben  
            }
            

            Diese Grundidee ist beliebig ausbaubar. Man kann sich nettere, beschreibendere Indices für das Array ausdenken anstelle der Zahlen, man könnte drüber nachdenken, Spalte und Sortierrichtung getrennt voneinander zu halten, man könnte den Vorgang der Prüfung auf Existenz der gewünschten Spalte auch dynamisch live in der Datenbank erledigen (z.B. mit SELECT * FROM TABELLE LIMIT 1, und dann die gelieferten Keys von mysql_fetch_assoc() angucken), wenn man eher etwas wie ein Admin-Interface baut.

            Um eine höchst sorgfältige Prüfung dessen, was außerhalb der einfachen Anführungszeichen '' in den SQL-Query hineinkommt, kommt man aber nicht herum. Nur Stringdaten sind verhältnismäßig einfach übergebbar, indem man sie einfach nur escaped.

            - Sven Rautenberg

            --
            "Love your nation - respect the others."
            1. Da sage ich nur vielen Dank! Was für eine komplizierte Materie, wenn man doch noch sehr am Anfang steht!

              Danke und Gruß

              1. Noch mal grundsätzlich (sorry nochmals, ich bin noch recht frisch in der Materie)...

                Wenn ich einen "Aufruf" über GET mache, besteht die Gefahr, dass der Besucher der Seite Mißbrauch betreibt. Gibt es denn eine Art "Liste" von Möglichkeiten, mit denen man sich absichern kann?

                Danke, Gruß

                1. echo $begrüßung;

                  Wenn ich einen "Aufruf" über GET mache, besteht die Gefahr, dass der Besucher der Seite Mißbrauch betreibt. Gibt es denn eine Art "Liste" von Möglichkeiten, mit denen man sich absichern kann?

                  Nein, nicht wirklich. Je nach Anwendungsfall muss man da gezielt vorgehen. Es kommt darauf an, was du mit der Benutzereingabe vorhast. Und das gilt im Prinzip für alle Werte, nicht nur für Benutzereingaben. Wann immer du einen Wert in einem bestimmten Kontext ausgibst ist dieser dem Kontext entsprechend zu behandeln. Soweit der allgemeingültige Lehrsatz. Die Schwierigkeit besteht nun darin, den richtigen Kontext zu erkennen. Beispielsweise ist mysql_real_escape_string() kein Allheilmittel für Werte, die in ein MySQL-Statement eingebracht werden sollen, wie du in diesem Faden gesehen hast. Neben dem Kontext "String im SQL-Statement" gibt es noch den Kontext "MySQL-Identifiers" und "nacktes SQL", die jeweils getrennt betrachtet werden müssen. Die Behandlungsregeln für Indentifiers sind einfach zu implementieren. Für Einfügungen in den restlichen SQL-Teil muss man individuelle Lösungen finden.

                  Kontextwechsel finden aber auch an anderen Stellen statt, beispielsweise beim Einfügen von Werten in HTML, XML, URLs, Javascript, CSV, sonstige Dateien. Manchmal sind sie auch geschachtelt anzutreffen, zum Beispiel: Wert in URL in HTML.

                  Zusätzlich zur kontextgerechten Behandlung sollte man die Eingabewerte auf gültigen Inhalt prüfen oder zwangsbehandeln. Wenn man eine Integerzahl erwartet kann man den Eingabewert durch intval() schicken und hat nun garantiert eine (und im Fehlerfall eine 0). Wenn man einen Wert erwartet, den man in eine Mail-Header-Zeile einfügen will und man findet darin einen Zeilenumbruch ist das garantiert kein gültiger Wert. Usw. usf. Letztlich hilft dir nur Erfahrung, ein waches Auge und ein Hang zur Penibilität.

                  echo "$verabschiedung $name";

                2. Moin!

                  Wenn ich einen "Aufruf" über GET mache, besteht die Gefahr, dass der Besucher der Seite Mißbrauch betreibt.

                  Das siehst du falsch. Schon indem du eine Seite im Web hast, besteht die Gefaht, dass der Besucher der Seite Mißbrauch betreibt. Er könnte es zumindest versuchen.

                  Dass die Parameter, auf die dein Skript unterschiedlich reagieren soll, sich in der URL befinden, erleichtert zwar das daran herumspielen, aber das funktioniert genauso auch mit POST-Formularen, oder den Inhalten von Cookies.

                  Gibt es denn eine Art "Liste" von Möglichkeiten, mit denen man sich absichern kann?

                  Ich tendiere dazu, "nein" zu sagen. Da jedes Skript anders programmiert sein kann, können auch jeweils vollkommen individuelle Lücken drin stecken.

                  Allerdings gibt es durchaus typische Fehler, die sehr häufig gemacht werden und deshalb auch sehr häufig ausgenutzt werden, um Unsinn zu machen.

                  Eines dieser Dinge ist die Fragestellung: Was bedeuten für das Programm die Daten in einer Variablen?

                  Mögliche Antworten wären: 1. reiner Text, 2. zwingend eine Zahl, 3. formatierender Code wie z.B. HTML oder 4. Programmcode (oder Stücke davon) wie z.B. PHP oder SQL.

                  Die zweite Fragestellung ist dann: In welchem Kontext wird der Variableninhalt verwendet?

                  Wenn ich reinen Text in einer Datei oder Seite vom Typ "text/plain" ausgebe, ändert sich der Kontext nicht. Wenn ich reinen Text in einer HTML-Umgebung ausgebe, ändert sich der Kontext: Plötzlich haben einige Zeichen eine andere Bedeutung und müssen umgewandelt werden, damit der in der HTML-Umgebung angezeigte Text auch identisch bleibt.

                  Dasselbe gilt beim Einbau einer Benutzereingabe in SQL: Der zunächst "reine Text" kommt jetzt in den Kontext "Datenstring im SQL-Query". Auch dort bedeuten einige Zeichen jetzt etwas anderes (beispielsweise Anführungsstriche innerhalb des Textes - die sollen den String ja nicht beenden, sondern als "hier ist ein Anführungsstrich" durchgehen), und müssen deshalb umgewandelt werden.

                  Und auch für Inhaltstyp 4 hat dieser Thread schon ein Beispiel: Die Angabe, wie zu sortieren ist, ist ein Stück Programmcode, nämlich SQL. Auch der kann nicht einfach unbesehen durchgereicht werden! Mein Vorschlag, diese Stücke aus einer vorgefertigten Tabelle zu entnehmen, sorgt dafür, dass garantiert nur Programmstücke benutzt werden, die du in diese Tabelle hineingeschrieben hast. Denn bei Programmstücken, die man als Angreifer frei formulieren kann, gibt es viel zu viele Freiheiten, den zu verursachenden Schaden durch geschicktes Verstecken unsichtbar zu machen. Zugegeben, beim SQL von MySQL sind die Möglichkeiten nicht so groß, weil MySQL selbst nicht so wahnsinnig gut programmierbar ist, und insbesondere der PHP-Befehl mysql_query() nicht die notwendigen Mehrfachkommandos durchreichen kann. Aber du kannst dir sicher vorstellen, dass es auch Leute gibt, die die Idee haben, ein Stückchen PHP-Code vom Benutzer entgegenzunehmen und dann mit eval() auszuführen. Und das ist wirklich eine extrem blöde Idee.

                  Um zu lernen, was man tun oder besser lassen sollte, bleibt dir kaum etwas anderes übrig, als dich selbst zu informieren, in Foren mitzulesen (diese Themen werden immer wieder auftauchen), deinen Code auch mal Dritten zum Durchgucken zur Verfügung zu stellen und auf Ratschläge zu hören, und letztendlich deine eigenen Erfahrungen zu sammeln. Ich bin sicher, es gibt auch etliche gute Blogs - aber wenn ich dir jetzt z.B. Stefan Essers Blog http://www.suspekt.org/ empfehle, überfordert dich das vielleicht noch etwas.

                  - Sven Rautenberg

                  --
                  "Love your nation - respect the others."
            2. hi,

              "SELECT * FROM buecher ORDER BY ".$_GET['orby']." ASC";
              Was du auf jedenfall tun solltest ist die Endgültige Abfrage immer mit mysql_real_escape durchführen.
              Du produzierst hier gerade höheren Unsinn.

              Du hast mich vermutlich nur falsch verstanden.

              Auf der einen Seite forderst du bei einem ziemlich gut durch mysql_real_escape_string() abgesicherten Query noch irgendeine weitergehende Validierung.

              Nönönö, ich habe nichts gefordert und auch noch keine Validierung vorgenommen, wenn OP direkt mit $_GET arbeiten möchte, dann sollte er wenigstens mysql_real_escape_string verwenden, dass war gemeint.
              Wenn $_GET schon vorher Validiert wird, ist klar, das man es nicht doppelt bearbeiten muss.

              Wenn $_GET['orby'] unbehandelt bleibt,
              "SELECT * FROM buecher ORDER BY ".mysql_real_escape_string($_GET['orby'])." ASC";

              Dann aber baust du dem Fragesteller einen Query mit nacktem GET-Parameter zusammen, bei dem man prinzipbedingt keinerlei Escaping einbauen kann, und gehst mit keiner Silbe darauf ein, dass du da gerade das perfekte Einfallstor für SQL-Injection gebaut hast.

              OP ist die Funktion mysql_real_escape_string nicht Fremd, siehe Ausgangsposting, dass die Eingaben Validiert werden sollten hatte ich in meinem ersten Posting schon erwähnt, da OP dass Script aber nur für eigene Anwendungen benötigt, reicht mMn auch ein mysql_real_escape_string.

              "SELECT * FROM buecher WHERE Verlag = 'O'Reilly'"
              Dieser Angriff kann nicht stattfinden, da im Query des OP mysql_real_escape_string() für diesen Teil ja verwendet wird.

              Ja, das war nur ein Beispiel für den Fall, das $_GET eben unmaskiert an das SELECT übergeben wird,

               xyz.php?verlag=o'reilly  
                
               SELECT * FROM buecher ORDER BY '".$_GET['verlag']."' ASC";
              

              Die Angabe hinter "ORDER BY" muss allerdings kein Text sein, den man escapen könnte, sondern eine existierende Tabellenspalte. Hier sind also besondere Maßnahmen notwendig, man kann nicht einfach einen beliebig manipulierbaren GET- (oder POST-) Parameter verwenden und direkt in den Query einbauen!

              Da stimme ich zu, da war ich wirklich zu voreilig und hab da garnicht dran gedacht.

              Man muss verhindern, dass irgendetwas anderes als eine der erlaubten, existierenden Spaltennamen in den Query hineinkommt.

              Ganz meine rede, wenn auch in einem anderen Kontext.

              holla holla

              --
              Alle Angaben ohne Gewehr.
              Hey, wenn's dir nicht gefällt, mach neu ...
              1. echo $begrüßung;

                Wenn $_GET['orby'] unbehandelt bleibt,
                "SELECT * FROM buecher ORDER BY ".mysql_real_escape_string($_GET['orby'])." ASC";

                Das ist die Stelle mit der Sicherheitslücke trotz mysql_real_escape_string(). Diese Funktion ist für Strings geeignet, die in '' oder "" eingefasst sind. Einen solchen hast du hier aber gar nicht vorliegen. Wenn du »0 UNION SELECT * FROM mysql.users« eingibst, hat mysql_real_escape_string() nichts zu tun, weil keine von ihr behandelten Zeichen vorkommen. Dein SQL-Statement sieht nun aber so aus: »SELECT * FROM buecher ORDER BY 0 UNION SELECT * FROM mysql.users«. Man muss nun lediglich noch dafür sorgen, dass die Spaltenanzahl des UNIONierten Select-Ergebnisses die gleiche ist wie die des ersten und ... siehst du jetzt das Problem an der Geschichte?

                Die Angabe hinter "ORDER BY" muss allerdings kein Text sein, den man escapen könnte, sondern eine existierende Tabellenspalte.

                Der Teil ab "sondern" ist so nicht richtig. Normalerweise muss es eine Spalte aus der SELECT-Klausel sein oder dessen Alias oder dessen Position von 1 und links an gezählt. Zumindest unter MySQL lässt sich aber auch eine am Ergebnis unbeteiligte Spalte der Tabelle angeben und außerdem auch noch die Funktion RAND(), um das Ergebnis zufällig gemischt zu erhalten.

                echo "$verabschiedung $name";

                1. hi,

                  Wenn $_GET['orby'] unbehandelt bleibt,
                  "SELECT * FROM buecher ORDER BY ".mysql_real_escape_string($_GET['orby'])." ASC";

                  Das ist die Stelle mit der Sicherheitslücke trotz mysql_real_escape_string().

                  Das wusste ich natürlich nicht.

                  Dein SQL-Statement sieht nun aber so aus: »SELECT * FROM buecher ORDER BY 0 UNION SELECT * FROM mysql.users«. Man muss nun lediglich noch dafür sorgen, dass die Spaltenanzahl des UNIONierten Select-Ergebnisses die gleiche ist wie die des ersten und ... siehst du jetzt das Problem an der Geschichte?

                  Wie ist es in diesen Zusammenhang mit Prepared Statements? Schliessen PS auch diese Lücke oder muss man auch trotz PS auf irgendwas achten?

                  Ich Filtere ja eh alles, was reinkommt, wäre aber trotzdem Interessant zu wissen.

                  holla holla

                  --
                  Alle Angaben ohne Gewehr.
                  Hey, wenn's dir nicht gefällt, mach neu ...
                  1. Moin!

                    Dein SQL-Statement sieht nun aber so aus: »SELECT * FROM buecher ORDER BY 0 UNION SELECT * FROM mysql.users«. Man muss nun lediglich noch dafür sorgen, dass die Spaltenanzahl des UNIONierten Select-Ergebnisses die gleiche ist wie die des ersten und ... siehst du jetzt das Problem an der Geschichte?

                    Wie ist es in diesen Zusammenhang mit Prepared Statements? Schliessen PS auch diese Lücke oder muss man auch trotz PS auf irgendwas achten?

                    Prepared Statements erlauben es, Query und Daten über getrennte Kanäle zum DBMS zu leiten. Die Sortierreihenfolge ist aus meiner Sicht aber kein Datum, sondern Teil des Querys. Also hilft das nicht viel.

                    Abgesehen davon hatte ich bislang den Eindruck, Prepared Statements sind vor allem dann sinnvoll, wenn die Absicht besteht, mehr als einen gleichartigen Query durchzuführen. Also beispielsweise massenhaft INSERTs oder UPDATEs.

                    Ich Filtere ja eh alles, was reinkommt, wäre aber trotzdem Interessant zu wissen.

                    Filtern ist toll. Nur: Was bleibt hängen, und was kommt durch deinen Filter durch?

                    - Sven Rautenberg

                    --
                    "Love your nation - respect the others."
                    1. echo $begrüßung;

                      Prepared Statements erlauben es, Query und Daten über getrennte Kanäle zum DBMS zu leiten. Die Sortierreihenfolge ist aus meiner Sicht aber kein Datum, sondern Teil des Querys. Also hilft das nicht viel.

                      Du anwortest so zögerlich. Es geht definitiv nicht, weil das ? bei »ORDER BY ?« quasi[1] nach »ORDER BY 'wert'« aufgelöst wird und nicht nach »ORDER BY wert« oder »ORDER BY wert«. Es kommt also ein String raus und kein Spaltenidentifier. Ein Prepared Statement ist zur Laufzeit unveränderlich. Keine Befehlsbestandteile, keine Identifier, nur Werte lassen sich an das P.S. binden.

                      Abgesehen davon hatte ich bislang den Eindruck, Prepared Statements sind vor allem dann sinnvoll, wenn die Absicht besteht, mehr als einen gleichartigen Query durchzuführen. Also beispielsweise massenhaft INSERTs oder UPDATEs.

                      Ja, der Vorteil des Nur-Einmal-Parsens ist nur bei mehrmaliger Verwendung innerhalb eines Script vorhanden. Übrig bleibt lediglich der Sicherheitseffekt des Nicht-Quotieren- und -Maskieren-Müssens unter Hinzufügung des Aufwandes für das P.S.-Handling zumal das unter MySQL/PHP mit dem Variablen-Binding auch noch etwas unhübsch gelöst ist.

                      [1] "quasi" deshalb, weil eine solche Auflösung bei echten Prepared Statements nicht stattfindet. Bei Datenbankabstraktionen, die P.S. nur nachbilden, wird man das jedoch in der Form finden.

                      echo "$verabschiedung $name";

                    2. hi,

                      Die Sortierreihenfolge ist aus meiner Sicht aber kein Datum, sondern Teil des Querys. Also hilft das nicht viel.

                      Ja, das stimmt auch wieder.
                      Stimmt schon, hier wäre ein Array mit allen erwarteten Werten wohl das Sinnvollste.

                      Abgesehen davon hatte ich bislang den Eindruck, Prepared Statements sind vor allem dann sinnvoll, wenn die Absicht besteht, mehr als einen gleichartigen Query durchzuführen. Also beispielsweise massenhaft INSERTs oder UPDATEs.

                      Ich nutze Prepared Statements derzeit wegen der erhofften Sicherheit.

                      Filtern ist toll. Nur: Was bleibt hängen, und was kommt durch deinen Filter durch?

                      Alles was ich für Sinnvoll halte kommt durch, wenn die Möglichkeit besteht, nutze ich ein Array, dass alle erlaubten Zeichen, Wörter oder Werte bereithält, die ich erwarte.

                      Ein Beispiel aus meiner Seite:

                        
                        /**  
                          * $_SERVER['REQUEST_URI'] von $_GET-Variablen befreien um Aktuellen Path zu haben  
                        */  
                        $mein_server_request = preg_replace('|\?.+$|', '', $_SERVER['REQUEST_URI']);  
                        
                        $query_request_check =  
                        (  
                           (!preg_match("#http|include|<|>|%3C|%3E|%22|ftp|:|script#", $_SERVER['REQUEST_URI']))  
                          AND  
                           (strlen($_SERVER['REQUEST_URI']) > 0 && strlen($_SERVER['REQUEST_URI']) < 90)  
                        )  
                           ? $mein_server_request  
                           : 'Start' ;
                      

                      Das Ergebnis jage ich dann mittels Prepared Statement durch meine Datenbank.

                      holla holla

                      --
                      Alle Angaben ohne Gewehr.
                      Hey, wenn's dir nicht gefällt, mach neu ...
                      1. Moin!

                        Abgesehen davon hatte ich bislang den Eindruck, Prepared Statements sind vor allem dann sinnvoll, wenn die Absicht besteht, mehr als einen gleichartigen Query durchzuführen. Also beispielsweise massenhaft INSERTs oder UPDATEs.

                        Ich nutze Prepared Statements derzeit wegen der erhofften Sicherheit.

                        Sie bringen dir vielleicht ein winziges Plus an Sicherheit wegen der Unmöglichkeit, durch Unachtsamkeit das Escaping zu vergessen. Ansonsten ist vom Standpunkt der Sicherheit ein Prepared Statement vollkommen identisch zu einem zusammengebauten, escapeten Query.

                        Filtern ist toll. Nur: Was bleibt hängen, und was kommt durch deinen Filter durch?

                        Alles was ich für Sinnvoll halte kommt durch, wenn die Möglichkeit besteht, nutze ich ein Array, dass alle erlaubten Zeichen, Wörter oder Werte bereithält, die ich erwarte.

                        Die Theorie, nur durchzulassen, was man kennt und erwartet, ist gut.

                        Leider sieht deine Praxis anders aus: Du filterst Dinge, von denen du glaubst, dass sie vermutlich schlecht sind, und lässt den Rest durch!

                        Ein Beispiel aus meiner Seite:

                        /**
                            * $_SERVER['REQUEST_URI'] von $_GET-Variablen befreien um Aktuellen Path zu haben
                          */
                          $mein_server_request = preg_replace('|?.+$|', '', $_SERVER['REQUEST_URI']);

                        [link:http://blog.php-security.org/archives/76-Holes-in-most-preg_match-filters.html]

                        $query_request_check =
                          (
                             (!preg_match("#http|include|<|>|%3C|%3E|%22|ftp|:|script#", $_SERVER['REQUEST_URI']))

                        Hier hast du eine "nette" Bad-Word-Liste. Dumm nur, dass du auf vermutlich genauso wirksame Bestandteile wie "SCRIPT" oder "INCLUDE" in Upper Case nicht reagierst.

                        AND
                             (strlen($_SERVER['REQUEST_URI']) > 0 && strlen($_SERVER['REQUEST_URI']) < 90)
                          )
                             ? $mein_server_request
                             : 'Start' ;

                          
                        Insgesamt scheint mir, dass du hier ein Konstrukt zusammengebastelt hast, dass du selbst nicht mit einem Satz erklären kannst, und dessen Funktion daher auch immer weiter im Dunklen verschwindet.  
                          
                        Mir persönlich wäre jedenfalls nicht wohl bei der Überlegung, was man eventuell auf deiner Site so anstellen könnte. Das zu beurteilen ist aber nicht möglich, da der restliche Code fehlt.  
                          
                        Meine Faustregel ist da immer: Bevor ich irgendwas selbst parse, verlasse ich mich lieber auf die vorgefertigten Funktionen der benutzten Programmiersprache, die mir die benötigten Daten u.U. mundgerecht servieren.  
                          
                        
                        > Das Ergebnis jage ich dann mittels Prepared Statement durch meine Datenbank.  
                          
                        Das dürfte dann das kleinste Problem darstellen.  
                          
                         - Sven Rautenberg
                        
                        -- 
                        "Love your nation - respect the others."
                        
                        1. hi,

                          Ich nutze Prepared Statements derzeit wegen der erhofften Sicherheit.

                          Sie bringen dir vielleicht ein winziges Plus an Sicherheit wegen der Unmöglichkeit, durch Unachtsamkeit das Escaping zu vergessen. Ansonsten ist vom Standpunkt der Sicherheit ein Prepared Statement vollkommen identisch zu einem zusammengebauten, escapeten Query.

                          Ich habe gelesen, dass Prepared Statements auch schneller bearbeitet werden, dass ist auch einer der gründe ; oder ist das auch eher zu vernachlässigen?

                          Hier hast du eine "nette" Bad-Word-Liste. Dumm nur, dass du auf vermutlich genauso wirksame Bestandteile wie "SCRIPT" oder "INCLUDE" in Upper Case nicht reagierst.

                          Gibt es bei Regex einen Trick, das Case-Sensitive zu machen?

                          Insgesamt scheint mir, dass du hier ein Konstrukt zusammengebastelt hast, dass du selbst nicht mit einem Satz erklären kannst,

                          Doch ; erst wird der Server['Request'] auf unerwünschte Zeichen durchsucht und eine Maximale länge festgelegt, in meinem Fall 90 Zeichen, wenn alles Ok ist ist die resultierende Variable der Wert, der durch das SELECT gejagt wird.

                          Die weitere Verarbeitung im Script passiert dann Intern, also keine Usereingaben oder sonstiges.

                          Mir persönlich wäre jedenfalls nicht wohl bei der Überlegung, was man eventuell auf deiner Site so anstellen könnte. Das zu beurteilen ist aber nicht möglich, da der restliche Code fehlt.

                          Wenn du lust und Zeit hast, kannst du ja mal durchschauen, ich habe es aufs wesentliche gekürzt.
                          http://dj-tut.de/z_test/mySite.php

                          Meine Faustregel ist da immer: Bevor ich irgendwas selbst parse, verlasse ich mich lieber auf die vorgefertigten Funktionen der benutzten Programmiersprache, die mir die benötigten Daten u.U. mundgerecht servieren.

                          Ich ging davon aus, dass Prepared Statements die benötigte Sicherheit garantieren, welche vorgefertigten Funktionen gibt es denn noch für MySQL?

                          holla holla

                          --
                          Alle Angaben ohne Gewehr.
                          Hey, wenn's dir nicht gefällt, mach neu ...
                          1. Hi,

                            Ich habe gelesen, dass Prepared Statements auch schneller bearbeitet werden, dass ist auch einer der gründe ; oder ist das auch eher zu vernachlässigen?

                            Wie dedlfix schon anmerkte, muessen nach dem einmaligen Aufbereiten des Statements bei nachfolgenden Statements gleichen Aufbaus (innerhalb der gleichen Scriptinstanz) nur noch die Nutzdaten zum DB-Server uebertragen werden - im Gegensatz zu "normalen" Statements, die jedes Mal auf's Neue geparst werden muessen, auch wenn der Befehlsanteil identisch bleibt, und sich nur die Daten aendern.

                            Hier hast du eine "nette" Bad-Word-Liste. Dumm nur, dass du auf vermutlich genauso wirksame Bestandteile wie "SCRIPT" oder "INCLUDE" in Upper Case nicht reagierst.

                            Gibt es bei Regex einen Trick, das Case-Sensitive zu machen?

                            1. Du meinst case insensitive,
                            2. RTFM - http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

                            MfG ChrisB

                            --
                            „This is the author's opinion, not necessarily that of Starbucks.“
                            1. hi,

                              Wie dedlfix schon anmerkte, muessen nach dem einmaligen Aufbereiten des Statements bei nachfolgenden Statements gleichen Aufbaus (innerhalb der gleichen Scriptinstanz) nur noch die Nutzdaten zum DB-Server uebertragen werden -

                              Da bin ich irgendwie noch nicht durchgestiegen, wie ich diese Funktion nutzen kann bzw. bei INSERTS ist es mir klar, bei der anzeige von Inhalten nicht.

                              1. RTFM - http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

                              Sorry, kurz nach dem absenden ist mir das als erstes durch den Kopf geschossen, Danke.

                              Hab bei Gelegenheit das ganze auch gleich mal getestet,
                              http://dj-tut.de/z_test/?starte=test
                              http://dj-tut.de/SCRIPT

                              Neue RegExp:

                              (!preg_match("#http|include|<|>|%3C|%3E|%22|\'|ftp|:|script|select|order|distinct|delete|drop|insert|update#i", $_SERVER['REQUEST_URI']))

                              --
                              Eine Frage hätte ich noch zum Thema Sicheheit in Punkto PHP:
                              Wenn ich in der php.ini „session.use_trans_sid = 1“ aktiviere, werden bei deaktivierten Cookies die Session an die URL aller Links auf meiner Seite gehangen, muss ich bei der Nutzung dieser Funktion auf irgendwas achten?

                              Ich hab so eine art Styleswitch für Handhelds auf meiner Seite, die mit Sessions arbeitet, wenn Cookies deaktiviert sind geht dass ganze natürlich nicht, daher die frage.

                              Ich habe es Testweise bei mir mal aktiviert, http://dj-tut.de/, kann ich es so lassen oder gibt es bei der Nutzung von session.use_trans_sid irgendwas zu beachten?

                              holla holla

                              --
                              Alle Angaben ohne Gewehr.
                              Hey, wenn's dir nicht gefällt, mach neu ...
                              1. Hi,

                                Eine Frage hätte ich noch zum Thema Sicheheit in Punkto PHP:
                                Wenn ich in der php.ini „session.use_trans_sid = 1“ aktiviere, werden bei deaktivierten Cookies die Session an die URL aller Links auf meiner Seite gehangen, muss ich bei der Nutzung dieser Funktion auf irgendwas achten?

                                Wo die Session-ID ueberall angehaengt wird, ist von der Konfiguration abhaengig (vor allem vom Wert der Direktive url_rewriter.tags).

                                Angehaengt wird sie bei aktiviertem use_trans_sid natuerlich automatisch beim ersten Start der Session - da kann PHP schliesslich noch nicht wissen, ob der Client Cookies akzeptieren wird.
                                Schickt der Client dann beim naechsten Request einen Cookie mit der Session-ID mit, wird davon ausgegangen, dass er das auch bei den naechsten Requests tun wird - dann wird auf's Uebertragen der SID per GET/POST verzichtet; wenn nicht, dann wird es weiterhin gemacht.

                                Das Problem bei der Uebertragung der SID per GET ist, dass sie damit leichter in fremde Haende gelangen kann.
                                Wenn du bspw. ein Bild von meiner Seite auf deiner eingebunden hast, dann ist die Wahrscheinlichkeit gross, dass die aktuell vom Nutzer aufgerufene Adresse deiner Seite, http://example.com/blubb.php?session_id=123456...789, als Referrer an meinen Server uebertragen wird. Wenn ich das jetzt zeitnah auswerten wuerde, koennte ich damit die Session deines Nutzers "stehlen".

                                Dem wird manchmal entgegenzuwirken versucht, in dem man die Session an eine IP-Adresse "bindet" (bspw. die anfragende IP-Adresse beim Start in der Session speichern, und bei nachfolgenden Requests vergleichen).
                                Das ist aber auch nicht wirklich optimal, da es auch Szenarios gibt, in denen die IP-Adresse deines Nutzers tatsaechlich wechselt (bspw. von AOL-Zugaengen wird behauptet, dass die ueber zahlreiche Proxies mit unterschiedlichen IPs gehen).

                                Ich hab so eine art Styleswitch für Handhelds auf meiner Seite, die mit Sessions arbeitet, wenn Cookies deaktiviert sind geht dass ganze natürlich nicht, daher die frage.

                                Also kein wirklich wichtiges Feature - wenn es mangels Cookie-Akzeptanz nicht funktionieren wuerde, waere das auch kein unbedingter Beinbruch.

                                Allerdings ist das andererseits auch keine sicherheitskritische Sache - also selbst wenn du als Fallback die Uebertragung der SID per GET nutzt, und die Session sollte mal entfuehrt werden, kann man damit ja nichts wirklich relevantes anstellen.

                                Ich habe es Testweise bei mir mal aktiviert, http://dj-tut.de/, kann ich es so lassen oder gibt es bei der Nutzung von session.use_trans_sid irgendwas zu beachten?

                                Na ja, wenn ein SuMa-Bot vorbeikommt, und sich dabei die Links mit Session-ID anschaut und indiziert, ist das natuerlich auch bloed. Deshalb versuchen manche Leute, Bot-Requests zu identifizieren, und bei solchen dann gar keine Session zu starten.

                                MfG ChrisB

                                --
                                „This is the author's opinion, not necessarily that of Starbucks.“
                                1. hi,

                                  Wo die Session-ID ueberall angehaengt wird, ist von der Konfiguration abhaengig (vor allem vom Wert der Direktive url_rewriter.tags).

                                  Das hatte ich schon geprüft, bei mir (1und1) wird die SID nur an Interne Links weiter gereicht, also keine Links, die mit http:// beginnen.

                                  Angehaengt wird sie bei aktiviertem use_trans_sid natuerlich automatisch beim ersten Start der Session - da kann PHP schliesslich noch nicht wissen, ob der Client Cookies akzeptieren wird.

                                  Das hatte ich nicht gewusst ; beim Versuch die Seite über validator.w3.org zu validieren ist mir das aufgefallen, da wurde immer ein <input type="hidden"> Feld bemängelt, dass die SID automatisch generiert hatte.

                                  Wenn du bspw. ein Bild von meiner Seite auf deiner eingebunden hast, dann ist die Wahrscheinlichkeit gross, dass die aktuell vom Nutzer aufgerufene Adresse deiner Seite, http://example.com/blubb.php?session_id=123456...789, als Referrer an meinen Server uebertragen wird. Wenn ich das jetzt zeitnah auswerten wuerde, koennte ich damit die Session deines Nutzers "stehlen".

                                  Danke für diesen Hinweis, diesen Punkt hatte ich nie so recht verstanden.

                                  Also kein wirklich wichtiges Feature - wenn es mangels Cookie-Akzeptanz nicht funktionieren wuerde, waere das auch kein unbedingter Beinbruch.

                                  Das habe ich jetzt auch wieder deaktiviert, einen großen nutzen hat der Styleswitch ja nicht wirklich.

                                  Na ja, wenn ein SuMa-Bot vorbeikommt, und sich dabei die Links mit Session-ID anschaut und indiziert, ist das natuerlich auch bloed.

                                  Das hatte ich Gestern in meinen Logs gesehen und die use_trans_sid wieder abgeschaltet, ich hab mir so viel Mühe gegeben, meine URI sauber zu halten, dass sollen sie auch bleiben.

                                  Deshalb versuchen manche Leute, Bot-Requests zu identifizieren, und bei solchen dann gar keine Session zu starten.

                                  Das wäre eine Option, so wie es aussieht, kann ich ja session.use_trans_sid auch zur Laufzeit des Scriptes starten.

                                  Danke für die Hilfe.

                                  holla holla

                                  --
                                  Alle Angaben ohne Gewehr.
                                  Hey, wenn's dir nicht gefällt, mach neu ...
                                  1. Hi,

                                    Angehaengt wird sie bei aktiviertem use_trans_sid natuerlich automatisch beim ersten Start der Session - da kann PHP schliesslich noch nicht wissen, ob der Client Cookies akzeptieren wird.

                                    Das hatte ich nicht gewusst ; beim Versuch die Seite über validator.w3.org zu validieren ist mir das aufgefallen, da wurde immer ein <input type="hidden"> Feld bemängelt, dass die SID automatisch generiert hatte.

                                    Der Validator "meckert" darueber bei Verwendung eines Strict-Doctypes, weil in solchem keine inline-Formularelemente als direkte Kinder von form erlaubt sind - da muss erst mal noch ein block-Element drumherum.

                                    Dazu bietet das Manual auch eine Loesungsmoeglichkeit an:
                                    "Note: If you want HTML/XHTML strict conformity, remove the form entry [from url_rewriter.tags] and use the <fieldset> tags around your form fields."

                                    MfG ChrisB

                                    --
                                    „This is the author's opinion, not necessarily that of Starbucks.“
                                    1. hi,

                                      Der Validator "meckert" darueber bei Verwendung eines Strict-Doctypes, weil in solchem keine inline-Formularelemente als direkte Kinder von form erlaubt sind - da muss erst mal noch ein block-Element drumherum.

                                      Ich habe es leider nicht so Exakt Formulieren können ;)

                                      Dazu bietet das Manual auch eine Loesungsmoeglichkeit an:
                                      "Note: If you want HTML/XHTML strict conformity, remove the form entry [from url_rewriter.tags] and use the <fieldset> tags around your form fields."

                                      Irgendwas scheine ich falsch zu machen, ich habe es jetzt mit allen erdenklichen variationen probiert, nichts funktioniert.

                                      .tpl

                                       <form action="" method="post"><fieldset>  
                                        { $styleSwitchButton }  
                                       </fieldset></form> 
                                      

                                      Ausgabe

                                       <form action="" method="post"><input type="hidden" name="SessioNGIN" value="3d27e..." /><fieldset><input type="hidden" name="SessioNGIN" value="3d27e..." />  
                                       </fieldset></form>
                                      

                                      Bei mir werden immer 2 input type hidden erzeugt, ich verstehe aber nicht, warum das so ist.

                                      Andere Konstallation:

                                      .tpl

                                       <fieldset><form action="" method="post" id="select_media">  
                                        { $styleSwitchButton }  
                                       </form> </fieldset>
                                      

                                      Ausgabe

                                       <fieldset><input type="hidden" name="SessioNGIN" value="3d27e..." /><form action="" method="post" id="select_media"><input type="hidden" name="SessioNGIN" value="3d27e..." />  
                                       </form> </fieldset>
                                      

                                      Ich habe ledigilich in der php.ini session.use_trans_sid = 1 aktiviert, warum wird das input Feld zweimal erzeugt?

                                      holla holla

                                      --
                                      Alle Angaben ohne Gewehr.
                                      Hey, wenn's dir nicht gefällt, mach neu ...
                                      1. Hi,

                                        Bei mir werden immer 2 input type hidden erzeugt, ich verstehe aber nicht, warum das so ist.

                                        Ich habe ledigilich in der php.ini session.use_trans_sid = 1 aktiviert, warum wird das input Feld zweimal erzeugt?

                                        Weil du das, was ich aus dem Manual zitiert hatte, nicht beachtet hast:

                                        "Note: If you want HTML/XHTML strict conformity, remove the form entry [from url_rewriter.tags] and use the <fieldset> tags around your form fields."

                                        MfG ChrisB

                                        --
                                        „This is the author's opinion, not necessarily that of Starbucks.“
                                        1. hi,

                                          Weil du das, was ich aus dem Manual zitiert hatte, nicht beachtet hast:

                                          "Note: If you want HTML/XHTML strict conformity, remove the form entry [from url_rewriter.tags] and use the <fieldset> tags around your form fields."

                                          Ich hatte es falsch verstanden, ich hab das fieldset in meinem Template removed statt in der php.ini ;)

                                          Danke für den Hinweis.

                                          Jetzt muss ich mich nur noch entscheiden, ob es sich lohnt, für so eine kleine Funktionalität meine URI zu verschandeln.

                                          holla holla

                                          --
                                          Alle Angaben ohne Gewehr.
                                          Hey, wenn's dir nicht gefällt, mach neu ...
                                          1. hi,

                                            Jetzt muss ich mich nur noch entscheiden, ob es sich lohnt, für so eine kleine Funktionalität meine URI zu verschandeln.

                                            Ich hab mir jetzt eine kleine Abfrage gebaut, die prüft, ob User_Agent ein Bot ist, ging mir jetzt Spontan durch den Kopf, jetzt suche ich verbesserungstipps.

                                             $userAgents = array(  
                                                                 'Bundestrojaner_1.4', /* Mein User_Agent */  
                                                                 'W3C_Validator/1.591',  
                                                                 'msnbot-media/1.0 (+http://search.msn.com/msnbot.htm)',  
                                                                 'Mediapartners-Google',  
                                                                 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)',  
                                                                 'Mozilla/5.0 (compatible; ScoutJet; +http://www.scoutjet.com/)',  
                                                                 'ia_archiver (+http://www.alexa.com/site/help/webmasters; crawler@alexa.com)',  
                                                                 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'  
                                                                 );  
                                              
                                              if (!in_array($_SERVER['HTTP_USER_AGENT'], $userAgents))  
                                              {  
                                                ini_set("session.use_trans_sid",1);  
                                              }
                                            

                                            Nachteil ist, ich muss alle Bots eintragen, die in irgendeiner Form wichtig sind, gibt es da irgendwas fertiges, das mir die Arbeit abnimmt?

                                            holla holla

                                            --
                                            Alle Angaben ohne Gewehr.
                                            Hey, wenn's dir nicht gefällt, mach neu ...
                          2. Moin!

                            Ich nutze Prepared Statements derzeit wegen der erhofften Sicherheit.

                            Leider ja aber nicht konsequent, wie man an deinem Code sieht. Du benutzt einmal für die Abfrage eines einzigen Datensatzes ein Prepared Statement, und dann für die Abfrage eines weiteren Datensatzes einen normalen Query.

                            Ich habe gelesen, dass Prepared Statements auch schneller bearbeitet werden, dass ist auch einer der gründe ; oder ist das auch eher zu vernachlässigen?

                            Das ist zu vernachlässigen, wenn du den Query nur ein einziges Mal durchführst. Der Geschwindigkeitsvorteil kommt zum Tragen, wenn du z.B. ein SELECT vorbereitest, mit Platzhalter im WHERE, und dann eine Folge von vielleicht hundert Aufrufen (d.h. hundert SELECTs) startest, jeweils mit unterschiedlichen Werten für den WHERE-Teil.

                            Obendrein ist deine Anwendung von mysqli extrem unterschiedlich. Du mixt - wie erwähnt - Prepared Statements und normale Querys. Dann auch noch die (viel elegantere) objektorientierte Methode mit der prozeduralen (mysqli_real_escape_string-Aufruf mittendrin).

                            Abgesehen davon ist dein Programmierstil alles andere als schön. Zu kritisieren aus meiner Sicht ist:

                            Variablenkopiererei! DAS ist wirklich ÜBEL! Im Skriptverlauf entstehen bei dir unzählige Variablen, werden ineinander kopiert, weisen eventuell - aber nicht unbedingt immer, das hängt von den Verzweigungen in diversen IFs ab - den identischen Inhalt auf, werden irgendwann einfach nicht mehr benutzt, aber nicht entsorgt - und man muss Angst haben (ich habe sie zumindest), dass durch irgendeinen dummen Zufall, Vertipper, oder Fehler diese Variablen später doch noch mal herangezogen werden und irgendwelche negativen Konsequenzen verursachen.

                            Beispiele für solche unsicheren, weil sinnlos verwendeten Variablen sind:
                            $mein_server_request, $query_request_check, $site_exists, $aktuelle_gruppe, $aktuelle_link.

                            Weiterhin fragst du Dinge aus der DB ab, die du nirgends benutzt:
                            $haupt_gruppe_1, $url_link

                            Was mich zu der Ansicht treibt, dass deine ZWEI Querys auch prima in EINEN Query gepaßt hätten.

                            Die Krönung der sinnlosen Kopiererei aber ist der Transfer des wunderschönen assoziativen Arrays, welches aus der zweiten DB-Abfrage kommt, in tausend Einzelvariablen, nur um dann vor dem finalen Transfer der Inhalte in das Smarty-Template diese Einzelvariablen wieder in ein komplettes assoziatives Array zu stecken.

                            Gewiß, auf dem Weg von DB-Abfrage zu Template kommen noch ein paar zusätzliche Werte hinzu oder werden berechnet, aber dafür braucht man keine Einzelvariablen, das geht alles auch mit dem assoziativen Array!

                            Hier hast du eine "nette" Bad-Word-Liste. Dumm nur, dass du auf vermutlich genauso wirksame Bestandteile wie "SCRIPT" oder "INCLUDE" in Upper Case nicht reagierst.

                            Ungeklärt bleibt, warum diese Badwordliste existiert. Der Code zeigt eindeutig, dass die REQUEST_URI direkt als Abfragekriterium in der Datenbank dient - allerdings muss ich gestehen, dass dieser Wert auch mehrfach wieder aus der Datenbank zurückgelesen, wieder als Parameter in Querys reingesteckt und wieder ausgelesen wird, so dass man die Spur der Herkunft leicht aus den Augen verlieren kann.

                            Umso wichtiger, dass die Herkunftsinformation nicht verloren geht - was sie derzeit definitiv tut.

                            Die weitere Verarbeitung im Script passiert dann Intern, also keine Usereingaben oder sonstiges.

                            Ich kann in deinem Skript nicht erkennen, dass es dort eine eindeutig definierte Grenze gibt, die exakt trennt, welcher Variableninhalt "böse vom User" ist, und welcher als "per Definition gut, weil validiert/aus Tabelle entnommen/etc." gilt.

                            Solch eine Grenze spiegelt sich in der Regel in der Benennung der Variablen wider. Ich persönlich finde, dass alles, was in den $_EGCPS-Variablen steht, diese nie ohne irgendeine Schutzfunktion verlassen sollte, und dass beispielsweise Formularinhalte, die eine Validierung durchlaufen haben, genauso gesammelt als Array in einer anderen Variablen (oder als Eigenschaft eines filternden Objektes) zur Verfügung stehen sollten.

                            Meine Faustregel ist da immer: Bevor ich irgendwas selbst parse, verlasse ich mich lieber auf die vorgefertigten Funktionen der benutzten Programmiersprache, die mir die benötigten Daten u.U. mundgerecht servieren.

                            Ich ging davon aus, dass Prepared Statements die benötigte Sicherheit garantieren, welche vorgefertigten Funktionen gibt es denn noch für MySQL?

                            Ich bezog mich auf das URL-Parsen und würde im Zweifel den Inhalt von $_GET & Co. vorziehen.

                            - Sven Rautenberg

                            --
                            "Love your nation - respect the others."
                            1. hi,

                              erstmal vielen Dank für die Ausführliche Kritik!

                              Leider ja aber nicht konsequent, wie man an deinem Code sieht. Du benutzt einmal für die Abfrage eines einzigen Datensatzes ein Prepared Statement, und dann für die Abfrage eines weiteren Datensatzes einen normalen Query.

                              Das hatte ich so gelöst, weil dass zweite SELECT keine „Usereingaben“ benötigt, dass meinte ich mit „Script arbeitet Intern weiter“, also nur mit Existierenden Daten, die ich aus der DB hole.
                              So ist sichergestellt, dass die benötigten Daten auch existieren und richtig sind.

                              Der Geschwindigkeitsvorteil kommt zum Tragen, wenn du z.B. ein SELECT vorbereitest, mit Platzhalter im WHERE, und dann eine Folge von vielleicht hundert Aufrufen (d.h. hundert SELECTs) startest, jeweils mit unterschiedlichen Werten für den WHERE-Teil.

                              Danke, jetzt habe ich die Logik dahinter endlich verstanden, ich hatte viel darüber gelesen aber nie richtig verstanden.

                              Obendrein ist deine Anwendung von mysqli extrem unterschiedlich. Du mixt - wie erwähnt - Prepared Statements und normale Querys. Dann auch noch die (viel elegantere) objektorientierte Methode mit der prozeduralen (mysqli_real_escape_string-Aufruf mittendrin).

                              Ja, bin doch noch Anfänger, ich bin froh, dass mein CMS überhaupt funktioniert, so wie ich es wollte (Update-Funktion und neue Seiten anlegen).
                              Die ganze umkopiererei der Variablen musste ich machen, damit meine URI lesbar bleibt, also ohne $_GET verwenden zu müssen.
                              Der Preis dafür ist halt so ein schrecklicher Code.
                              Aber ich glaube ich schreibe es Komplett um auf $_GET, das ist viel einfacher.

                              Variablenkopiererei! DAS ist wirklich ÜBEL! Im Skriptverlauf entstehen bei dir unzählige Variablen, werden ineinander kopiert, weisen eventuell - aber nicht unbedingt immer, das hängt von den Verzweigungen in diversen IFs ab - den identischen Inhalt auf, werden irgendwann einfach nicht mehr benutzt

                              Nein, die werden Script-intern verwendet, es fehlen ja noch dutzende Funktionen und Programme, die ausgelagert sind und kleine Aufgaben erledigen.
                              Beispielsweise für den Aufbau der Menus und Footermenu.
                              Insgesamt ist mein Code ein Paar tausend Zeilen lang, dass möchte ich hier niemandem zumuten, zumal, wie du schon sagtest, mein Code ist voll mit Anfängerfehlern.
                              Meine grösste Hürde ist zurzeit die Navigation der Seite, dass kriege ich einfach nicht gebacken, diese zwingt mich auch, den Umweg mit den umkopierten Variablen zu gehen.
                              http://dj-tut.de/z_test/meinMenu.php
                              Nested Sets habe ich viermal versucht und nicht verstanden geschweige denn umgesetzt bekommen.

                              Beispiele für solche unsicheren, weil sinnlos verwendeten Variablen sind:
                              $mein_server_request, $query_request_check, $site_exists, $aktuelle_gruppe, $aktuelle_link.

                              Bis auf $query_request_check werden alle Variablen im weiteren Scriptverlauf verwendet, insbesondere benötige ich $site_exists, um z. B. auf Error 404 reagieren zu können. Das liegt aber an meiner .htaccess, da habe ich mit drei Zeilen veranlasst, das jede aufgerufene URI existiert.
                              Wenn ich nicht auf die Existenz der Seite Prüfe, kann man jede X-beliebige URI aufrufen und bekommt immer ein header Status 200 Ok.

                              Weiterhin fragst du Dinge aus der DB ab, die du nirgends benutzt:
                              $haupt_gruppe_1, $url_link

                              Das stimmt, ich kriege die Abfrage nicht anders hin.

                              Die Krönung der sinnlosen Kopiererei aber ist der Transfer des wunderschönen assoziativen Arrays, welches aus der zweiten DB-Abfrage kommt, in tausend Einzelvariablen, nur um dann vor dem finalen Transfer der Inhalte in das Smarty-Template diese Einzelvariablen wieder in ein komplettes assoziatives Array zu stecken.

                              FULL ACK! Ich habe mich mit diesem CMS definitiv komplett übernommen  :)
                              Das schlimme ist, das es funktioniert, daher kann ich nicht aufhören, weiter drauf aufzubauen.

                              Ungeklärt bleibt, warum diese Badwordliste existiert. Der Code zeigt eindeutig, dass die REQUEST_URI direkt als Abfragekriterium in der Datenbank dient - allerdings muss ich gestehen, dass dieser Wert auch mehrfach wieder aus der Datenbank zurückgelesen, wieder als Parameter in Querys reingesteckt und wieder ausgelesen wird, so dass man die Spur der Herkunft leicht aus den Augen verlieren kann.

                              Die Badwordliste kommt nur einmal zum tragen, nämlich wenn die Aktuell aufgerufene Seite abgefragt wird, der Gesamte weitere Scriptverlauf basiert auf $site_exists, also bestehenden Daten aus der Datenbank.

                              Ich ging davon aus, dass Prepared Statements die benötigte Sicherheit garantieren, welche vorgefertigten Funktionen gibt es denn noch für MySQL?
                              Ich bezog mich auf das URL-Parsen und würde im Zweifel den Inhalt von $_GET & Co. vorziehen.

                              Ich werde versuchen, mein CMS umzuschreiben und mit $_GET zu arbeiten, das dumme an $_GET ist nur die URI, die dann so verunstaltet aussieht.

                              holla holla

                              --
                              Alle Angaben ohne Gewehr.
                              Hey, wenn's dir nicht gefällt, mach neu ...
                            2. hi,

                              Danke nochmal für deine Anregungen, die haben mich dazu verleitet, mein CMS ein wenig zu überarbeiten, bei der Gelegenheit habe ich noch meine Datenbank verfeinert und das Navigationsscript überarbeitet.

                              http://dj-tut.de/z_test/neuesCMS.php

                              Variablenkopiererei! DAS ist wirklich ÜBEL!

                              Da werde ich derzeit nicht drumherum kommen, da ich irgendwie auf Error 404 reagieren muss, ansonstenm konnte ich schon eine Menge Code einsparen.

                              Gewiß, auf dem Weg von DB-Abfrage zu Template kommen noch ein paar zusätzliche Werte hinzu oder werden berechnet, aber dafür braucht man keine Einzelvariablen, das geht alles auch mit dem assoziativen Array!

                              Das verstehe ich nicht ganz, welches Array kann ich für das Smarty-Template nutzen?
                              Für Smarty muss ich ja jede Variable einmal initialisieren, mit einem if / else aus der DB abfrage ist das ja dann schon erledigt.

                              holla holla

                              --
                              Alle Angaben ohne Gewehr.
                              Hey, wenn's dir nicht gefällt, mach neu ...
                              1. Moin!

                                Danke nochmal für deine Anregungen, die haben mich dazu verleitet, mein CMS ein wenig zu überarbeiten, bei der Gelegenheit habe ich noch meine Datenbank verfeinert und das Navigationsscript überarbeitet.

                                http://dj-tut.de/z_test/neuesCMS.php

                                Ein paar Kritikpunkte:

                                  $query_request_check =  
                                  (  
                                   (!preg_match("#http|include|<|>|%3C|%3E|%22|'|ftp|:|script|select|order|distinct|delete|drop|insert|update#i", $_SERVER['REQUEST_URI']))  
                                   AND  
                                   (strlen($_SERVER['REQUEST_URI']) > 0 && strlen($_SERVER['REQUEST_URI']) < 90)  
                                  )  
                                    ? $query_request_check  
                                    : die('was wird das?') ;  
                                
                                

                                Effektiv benötigst du hier keinen ternären Operator, sondern ein echtes IF: Wenn die Testbedingung für "bösen Inhalt" zutrifft, willst du das Skript mit die() abtöten und stoppen. Hier den ternären Operator zu verwenden verschleiert diese Tatsache. Der ternäre Operatoe (boolean_expression?value_1:value2) ist KEIN Ersatz und KEINE Kurzform für IF/ELSE, sondern eine Form von bedingter Wertzuweisung. Du benötigst (solltest es zumindest) immer eine Variable, die in Abhängigkeit einer Bedingung entweder den einen oder anderen Wert erhalten soll.

                                $meinMenuKey = explode('/', $_SERVER['REQUEST_URI']);  
                                // ...  
                                $SubmenuAbfrage = "  
                                SELECT  
                                        title_url, url_link  
                                FROM  
                                        mein_sub_menu  
                                WHERE  
                                        parentID = '".$meinMenuKey[1]."'  
                                ";  
                                
                                

                                Da hast du dir jetzt die klassische SQL-Injection geproggt. ESCAPING! IMMER! EGAL WO DER WERT HERKOMMT! Dass die Request-URI andernorts irgendeiner Prüfung unterzogen wird, die ggf. zum Skriptabbruch führt, ist bei dieser Betrachtung vollkommen egal. Sicherheit in Skripten bedeutet immer, dass man typische Muster, die Fehlerquellen und Sicherheitslücken bedeuten, vermeidet - und beim Code-Review erkennt.

                                Daher sowas tun:

                                  $seiten_abfrage = "  
                                SELECT  
                                        group_name, date,  
                                //...  
                                FROM  
                                        datdate, head, ueberschrift, seiten_intern, marginalien  
                                WHERE  
                                        datdate.group_name     = '".mysqli_real_escape_string($dat_verbindung, $aufgerufene_url)."'  
                                //...  
                                LIMIT 1  
                                ";  
                                  
                                  if ($mySite = $dat_verbindung->query($seiten_abfrage))  
                                //...  
                                
                                

                                Das Escaping ist dort vorhanden. Allerdings nicht in OOP. Stattdessen wählst du die unkomfortable Methode über den Funktionsaufruf, dem du zusätzlich noch die DB-Connection mit übergeben musst.

                                Warum nicht sowas: $sql = "SELECT .... WHERE feld = '".$dat_verbindung->real_escape_string($aufgerufene_url)."' ...";

                                Variablenkopiererei! DAS ist wirklich ÜBEL!

                                Da werde ich derzeit nicht drumherum kommen, da ich irgendwie auf Error 404 reagieren muss, ansonstenm konnte ich schon eine Menge Code einsparen.

                                Glaube ich nicht.

                                Error 404 weißt du eigentlich schon direkt zu Beginn, wenn du die Seite nicht findest, die verlangt wird. Zu diesem Zeitpunkt hast du also "keinen Content", bzw. ggf. den Content der in der DB gespeicherten 404-Seite, plus die Info "404-Status in den Header tun". Das ist im Prinzip genau derselbe Ablauf, wie das Auffinden einer existierenden Seite mit 200-Status, zu dem du ebenfalls den Content in der DB findest, und am Ende eben nur mit 200-Status rausschickst.

                                Egal, ob der 404-Seiteninhalt aus der DB kommt, oder statisch definiert ist, den ganzen restlichen Klumpatsch wie Navi wirst du bei beiden Seitenfällen generieren müssen.

                                Gewiß, auf dem Weg von DB-Abfrage zu Template kommen noch ein paar zusätzliche Werte hinzu oder werden berechnet, aber dafür braucht man keine Einzelvariablen, das geht alles auch mit dem assoziativen Array!

                                Das verstehe ich nicht ganz, welches Array kann ich für das Smarty-Template nutzen?

                                Wenn du in Smarty diverse Variablen benutzt, z.B. {$titel}, {$headline} und {$content}, dann kannst du diesen drei Variablen in einem Rutsch ihre Inhalte verpassen, indem du mit $smarty->assign($wertearray) arbeitest, und das Wertearray sieht dabei so aus: array("titel"=>"Ein Titel", "headline" => "Tolle Überschrift", "content" => "Lorem ipsum...");

                                Solch ein Array kommt auch direkt aus der Datenbank, wenn du z.B. "SELECT titel, headline, content FROM tabelle" abfragst und mit $db->fetch_assoc() abholst.

                                Für Smarty muss ich ja jede Variable einmal initialisieren, mit einem if / else aus der DB abfrage ist das ja dann schon erledigt.

                                Smarty bietet verschiedene Möglichkeiten, wie es mit nicht zugewiesenen Variablen im Template umgeht. Standard ist, sie durch Leerstring zu ersetzen.

                                - Sven Rautenberg

                                --
                                "Love your nation - respect the others."
                                1. hi,

                                  Effektiv benötigst du hier keinen ternären Operator, sondern ein echtes IF: Wenn die Testbedingung für "bösen Inhalt" zutrifft, willst du das Skript mit die() abtöten und stoppen. Hier den ternären Operator zu verwenden verschleiert diese Tatsache.

                                  Im Grunde aber doch auch richtig oder? Es funktioniert ja so, wie es soll.

                                  Ich habe es jetzt geändert:

                                    $query_request_check = preg_replace('|\?.+$|', '', $_SERVER['REQUEST_URI']);  
                                    
                                    if (  (!preg_match("#http|include|<|>|%3C|%3E|%22|\'|ftp|:|script|select|order|distinct|delete|drop|insert|update#i", $_SERVER['REQUEST_URI']))  
                                      AND (strlen($_SERVER['REQUEST_URI']) > 0 && strlen($_SERVER['REQUEST_URI']) < 90) )  
                                    {  
                                      $aufgerufene_url = array_pop (explode( "/", $query_request_check) );  
                                    }  
                                    else  
                                    {  
                                      $aufgerufene_url = '';  
                                      die('was wird das?');  
                                    }
                                  

                                  Da hast du dir jetzt die klassische SQL-Injection geproggt. ESCAPING! IMMER! EGAL WO DER WERT HERKOMMT! Dass die Request-URI andernorts irgendeiner Prüfung unterzogen wird, die ggf. zum Skriptabbruch führt, ist bei dieser Betrachtung vollkommen egal.

                                  Das war nur vorläufig zu Testzwecken, aber Danke, dass du mich drauf hinweist, hätte ich wirklich vergessen können.

                                  Warum nicht sowas: $sql = "SELECT .... WHERE feld = '".$dat_verbindung->real_escape_string($aufgerufene_url)."' ...";

                                  Das kannte ich nicht, Danke für den hinweis.

                                  Error 404 weißt du eigentlich schon direkt zu Beginn, wenn du die Seite nicht findest, die verlangt wird

                                  Da habe ich jetzt auch eine Idee zu, siehe unten.

                                  Solch ein Array kommt auch direkt aus der Datenbank, wenn du z.B. "SELECT titel, headline, content FROM tabelle" abfragst und mit $db->fetch_assoc() abholst.

                                  Das habe ich jetzt ein wenig geändert, meinst du sowas in der Art?

                                    if ($mySite = $dat_verbindung->query($seiten_abfrage))  
                                    {  
                                      while ($siteRow = $mySite->fetch_assoc())  
                                      {  
                                         $smartyArray = array  
                                                            (  
                                                              'titel_name'     => $siteRow['title_tag'],  
                                                              'my_description' => $siteRow['description'],  
                                                              'my_keywords'    => $siteRow['meta_tags'],  
                                                              'my_body_id'     => $siteRow['body_id'],  
                                                              'my_headline'    => $siteRow['inhalt_ueberschrift'],  
                                                              'my_content'     => My_bb_Object($siteRow['inhalt_text']),  
                                                              'my_marginalien' => My_marginalien_bb_Object($siteRow['marginalien']),  
                                                            );  
                                      }  
                                    }  
                                    
                                    $smarty = new Smarty;  
                                    
                                    if (!empty($smartyArray))  
                                    {  
                                      $smarty->assign  
                                      (  
                                         $smartyArray  
                                      );  
                                    }  
                                    else  
                                    {  
                                      // Error 404  
                                    }
                                  

                                  Smarty bietet verschiedene Möglichkeiten, wie es mit nicht zugewiesenen Variablen im Template umgeht. Standard ist, sie durch Leerstring zu ersetzen.

                                  Das hatte ich wohl nicht getestet, ich ging davon aus, das Smarty sich wie PHP verhält.

                                  holla holla

                                  --
                                  Alle Angaben ohne Gewehr.
                                  Hey, wenn's dir nicht gefällt, mach neu ...
                                  1. Moin!

                                    Effektiv benötigst du hier keinen ternären Operator, sondern ein echtes IF: Wenn die Testbedingung für "bösen Inhalt" zutrifft, willst du das Skript mit die() abtöten und stoppen. Hier den ternären Operator zu verwenden verschleiert diese Tatsache.

                                    Im Grunde aber doch auch richtig oder? Es funktioniert ja so, wie es soll.

                                    "Funzt doch" ist kein Argument. Funktionieren kann viel. Programmieren ist aber mehr als nur das Herstellen der gewünschten Funktion. Es geht auch immer darum, dass man später auch wieder versteht, was man da programmiert hat.

                                    Dieses Verständnis wird extrem erleichtert, wenn man für die gleichen Dinge immer die gleichen Strukturen und Muster benutzt. Denn in Mustererkennung ist unser menschliches Auge sehr gut. Was gleich aussieht, ist auch gleich - diesen Vorteil sollte man sich auch beim Betrachten von Programmcode zunutze machen, denn angucken wird man den Code viel häufiger, als man ihn schreiben wird.

                                    Ich habe es jetzt geändert:

                                    $query_request_check = preg_replace('|?.+$|', '', $_SERVER['REQUEST_URI']);

                                    if (  (!preg_match("#http|include|<|>|%3C|%3E|%22|'|ftp|:|script|select|order|distinct|delete|drop|insert|update#i", $_SERVER['REQUEST_URI']))
                                        AND (strlen($_SERVER['REQUEST_URI']) > 0 && strlen($_SERVER['REQUEST_URI']) < 90) )
                                      {
                                        $aufgerufene_url = array_pop (explode( "/", $query_request_check) );
                                      }
                                      else
                                      {
                                        $aufgerufene_url = '';
                                        die('was wird das?');
                                      }

                                      
                                    Sieht besser aus.  
                                      
                                    
                                    > > Warum nicht sowas: `$sql = "SELECT .... WHERE feld = '".$dat_verbindung->real_escape_string($aufgerufene_url)."' ...";`{:.language-php}  
                                    >   
                                    > Das kannte ich nicht, Danke für den hinweis.  
                                      
                                    Das ist teilweise nicht deine Schuld, die PHP-Doku zu mysqli ist, wie ich gerade entdeckt habe, nicht in allen Fällen konsistent. Da werden in den OOP-Beispielen unabsichtlich mal prozedurale Aufrufe benutzt, obwohl es auch objektorientiert ginge.  
                                      
                                    
                                    > > Solch ein Array kommt auch direkt aus der Datenbank, wenn du z.B. "`SELECT titel, headline, content FROM tabelle`{:.language-sql}" abfragst und mit $db->fetch\_assoc() abholst.  
                                    >   
                                    > Das habe ich jetzt ein wenig geändert, meinst du sowas in der Art?  
                                      
                                    Ich habs mal geändert in das, was ich mir vorstelle:  
                                      
                                    ~~~php
                                      
                                    $seiten_abfrage = "SELECT title_tag as titel_name, description as my_description, meta_tags as my_keywords, body_id as my_body_id, inhalt_ueberschrift as my_headline, inhalt_text as my_content, marginalien as my_marginalien FROM ....";  
                                    // Der Query liefert dir durch die Verwendung von Aliasnamen in fetch_assoc jetzt die "korrekten", direkt verwendbaren Variablennamen, wie sie im Smarty-Template benutzt werden.  
                                      
                                    $smarty = new Smarty; // Da du zweifelsfrei ohnehin ein Template benötigst, sollte dieses Objekt eigentlich schon direkt zu Beginn des Skripts erzeugt werden, nicht erst so spät.  
                                      
                                    if ($mySite = $dat_verbindung->query($seiten_abfrage))  
                                      {  
                                        if (($siteRow = $mySite->fetch_assoc())!== false) // Da du nur einen einzigen Datensatz abfragst, ist while hier überflüssig. In den Query gehörte stattdessen ans Ende ein "LIMIT 1".  
                                        {  
                                           $siteRow['my_content'] = My_bb_Object($siteRow['my_content']); // Inhalt BB-formatieren.  
                                           $siteRow['my_marginalien'] = My_marginalien_bb_Object($siteRow['my_marginalien']); // dito für Marginalien  
                                           $smarty->assign($siteRow);  
                                        }  
                                        else  
                                        {  
                                          // Error 404  
                                        }  
                                      }  
                                    
                                    

                                    Alternativ könntest du auch die Einzelvariablenvariante von assign nutzen:

                                    $smarty->assign("templatevar", $wertvariable);

                                    Damit kannst du, auch nachträglich überschreibend, jeden bereits gesetzten Wert einzeln übergeben. Das ist u.U. besser, als sich erst umständlich ein Array zu konstruieren.

                                    Übrigens siehst du, dass es ziemlich aufwendig und umständlich ist, die gewählten Spaltennamen in deiner Tabelle für die Ausgabe in Smarty alle umzubenennen. Es wäre deutlich schlauer gewesen, entweder die Spaltennamen identisch zu den Templatevariablen zu wählen, oder umgekehrt die Templatevariablen identisch zu den Datenbankspaltennamen zu wählen.

                                    Das ist nämlich wieder so ein Muster: Gleiche Namen bedeuten gleiches - sowohl im Template, als auch in der DB. Es ist viel anstrengender, wenn man im Kopf immer diese Namenswechsel behalten muss. Bei manchen Namen mag das weniger anstrengend sein (z.B. description == my_description), aber es ist auch sehr fehleranfällig, wenn man unabsichtlich das "my_" vergisst oder hinzufügt. Genauso nervig ist aber die Namensverbindung von inhalt_ueberschrift zu my_headline.

                                    - Sven Rautenberg

                                    --
                                    "Love your nation - respect the others."
                                    1. high,

                                      Dieses Verständnis wird extrem erleichtert, wenn man für die gleichen Dinge immer die gleichen Strukturen und Muster benutzt. Denn in Mustererkennung ist unser menschliches Auge sehr gut. Was gleich aussieht, ist auch gleich - diesen Vorteil sollte man sich auch beim Betrachten von Programmcode zunutze machen, denn angucken wird man den Code viel häufiger, als man ihn schreiben wird.

                                      Das stimmt, dass muss ich mir auch langsam angewöhnen ; ich hatte Anfangs noch Angst, immer die gleichen Variablennamen zu verwenden.

                                      if ($mySite = $dat_verbindung->query($seiten_abfrage))

                                      {
                                          if (($siteRow = $mySite->fetch_assoc())!== false) // hier musste ich um ein = kürzen
                                          {
                                             $siteRow['my_content'] = My_bb_Object($siteRow['my_content']); // Inhalt BB-formatieren.
                                             $siteRow['my_marginalien'] = My_marginalien_bb_Object($siteRow['my_marginalien']); // dito für Marginalien
                                             $smarty->assign($siteRow);
                                          }
                                          else
                                          {
                                            // Error 404
                                          }
                                        }

                                        
                                      Da muss man ja erstmal drauf kommen, jetzt verstehe ich endlich auch, wofür die ALIAS sind.  
                                        
                                      
                                      > `$smarty->assign("templatevar", $wertvariable);`{:.language-php}  
                                      > Damit kannst du, auch nachträglich überschreibend, jeden bereits gesetzten Wert einzeln übergeben. Das ist u.U. besser, als sich erst umständlich ein Array zu konstruieren.  
                                        
                                      Das ist jetzt mein Allheilmittel ;)  
                                        
                                      
                                      > Es wäre deutlich schlauer gewesen, entweder die Spaltennamen identisch zu den Templatevariablen zu wählen, oder umgekehrt die Templatevariablen identisch zu den Datenbankspaltennamen zu wählen.  
                                        
                                      Da hatte ich wie gesagt Angst, dass es vielleicht zu Problemen führen könnte, ich habe jetzt das Smarty Template komplett angepasst.  
                                        
                                      Das neue Script ist jetzt um mehr als das dreifache kürzer als vorher, dadurch hoffe ich mal auch schneller in der Ausführung.  
                                        
                                      [Das neue Script](http://dj-tut.de/z_test/cmsCopy.php)  
                                      [im Einsatz](http://dj-tut.de/)  ;)  
                                        
                                      Bis auf die FAQ ist das jetzt so ziemlich dass, was ich von vornherein geplant hatte, aber nicht hingekriegt hatte.  
                                        
                                      holla holla  
                                      
                                      -- 
                                      Alle Angaben ohne Gewehr.  
                                        
                                        
                                      [I Have a Dream](http://www.myvideo.de/watch/2503116/I_have_a_dream_Will_I_AM_feat_Common)