Gespi: MySql-Zugriff auf bestimmteTabellen aus bestimmten PHP-Klassen

Hallo!

Ich suche nach einer Möglichkeit die Datenbanktransaktionen die eine bestimmte Klasse durchführen darf auf festgelegte Tabellen zu beschränken.

Beispiel:
In dem Beispiel gehe ich davon aus, dass in der Datenbank 2 Tabellen sind, "tblFoo" und "tblBar". Ich möchte den Zugriff auf "tblBar" für weitere Zugriffe aus dieser Klasse sperren.

  
class foobar {  
  
    protected $arrRegisteredTables = array();  
  
    public function __construct(){  
        // Array mit Tabellen auf die die Klasse zugriff hat  
        $this->arrRegisteredTables = array('tblFoo');  
  
        // das soll funktionieren  
        $resDatabaseResult = DB::prepare("SELECT * FROM `tblFoo`")->execute();  
  
        // das soll nicht funktionieren weil der Zugriff auf  
        // "tblBar" verweigert wurde (Fehler o. Exception)  
        $resDatabaseResult = DB::prepare("SELECT * FROM `tblBar`")->execute();  
    }  
}  

Könnte mir bitte jemand einen Tipp geben wie ich das am besten umsetzen kann?

Grüße, Gespi

  1. Ich denke du müsstest hiere einen Wrapper um die Prepare funktion bauen mit deiner Check_klasse also irgendwie so

    try {
    $resDatabaseResult = DB::prepare_and_validate(this, "SELECT * FROM tblBar")->prepare();
    } catch (Exceptio $e) {

    }

    Die prepare_and_validate methode gibst du zb in eine abstract klasse rein die du vererbst, dort überprüfst du dann ob in der query also im String die Tabelle ist - falls ja returnst du einfach return prepare($query);
    else wirfst due ine exception.

    Die Überprüfung kannst du mit Regular expressions wahrscheinlich lösen.

    lg

    1. try {
      $resDatabaseResult = DB::prepare_and_validate(this, "SELECT * FROM tblBar")->execute();
      } catch (Exceptio $e) {

      execute sollte das heißen.

    2. Ich denke du müsstest hiere einen Wrapper um die Prepare funktion bauen mit deiner Check_klasse also irgendwie so [..]

      Danke, sowas in der Richtung habe ich gesucht. Der Ansatz gefällt mir ganz gut und ich muss jetzt wohl mal schauen wie ich die Statements am besten zerlegen kann. Das dürfte sehr kompliziert werden.
      Ich habe gehofft, das MySql dafür etwas entsprechendes zur Verfügung stellt.

      Warum ich das machen will ist ganz einfach. Wenn eine externe Klasse einen unerlaubten Zugriff zulässt, durch SQL-Injection oder XSS, dann möchte ich aus Sicherheitsgründen den Zugriff soweit eingrenzen, dass sie nur die Tabellen aufrufen darf, die für einen Zugriff "registriert" wurden.

      Grüße, Gesti

      1. Tach!

        Ich habe gehofft, das MySql dafür etwas entsprechendes zur Verfügung stellt.

        MySQL hat ein sehr detailliert konfigurierbares Rechtemenagement.

        Warum ich das machen will ist ganz einfach. Wenn eine externe Klasse einen unerlaubten Zugriff zulässt, durch SQL-Injection oder XSS, dann möchte ich aus Sicherheitsgründen den Zugriff soweit eingrenzen, dass sie nur die Tabellen aufrufen darf, die für einen Zugriff "registriert" wurden.

        Wenn Fremdcode egal welcher Art Zugriff auf deine Daten hat, brauchst du dir um SQL-Injection keine Sorgen mehr zu machen. Man kann sie dann auch mit direkt selbst erstellten Statements ändern. Und XSS hat mit Datenbanken nichts zu tun. Über Code in deiner Anwendung kannst du Zugriffe auf das DBMS nicht generell verhindern. Wenn du sie fehlerfrei programmiert hast, kannst du lediglich verhindern, dass _über_deine_Anwedung_ jemand ungewünschten Datenzugriff hat - solange er den Code nicht ändern kann.

        dedlfix.

      2. Ich denke du müsstest hiere einen Wrapper um die Prepare funktion bauen mit deiner Check_klasse also irgendwie so [..]

        Danke, sowas in der Richtung habe ich gesucht. Der Ansatz gefällt mir ganz gut und ich muss jetzt wohl mal schauen wie ich die Statements am besten zerlegen kann. Das dürfte sehr kompliziert werden.
        Ich habe gehofft, das MySql dafür etwas entsprechendes zur Verfügung stellt.

        Wenn dir der Ansatz weiterhilft (inwiefern auch immer) das checken selbst über regex kommt drauf an wie komplet deine abfragen sind

        $valid_tables = "tableA|tableB|tableC"  
          
        if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$valid_tables."\s(.+)#im", $query)) {  
          
        }  
          
        
        
        1. $valid_tables = "tableA|tableB|tableC"

          if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$valid_tables."\s(.+)#im", $query)) {

          }

            
          Klammern vergessen oben:  
            
          ~~~php
          $valid_tables = "(tableA|tableB|tableC)"  
            
            
          
          
        2. Moin!

          $valid_tables = "tableA|tableB|tableC"

          if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$valid_tables."\s(.+)#im", $query)) {

          }

            
          Ein schönes Beispiel, wie man eine SQL-Injection mit einer Regex-Injection versuchen kann zu verhindern...  
            
           - Sven Rautenberg
          
          1. $valid_tables = "tableA|tableB|tableC"

            if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$valid_tables."\s(.+)#im", $query)) {

            }

            
            >   
            > Ein schönes Beispiel, wie man eine SQL-Injection mit einer Regex-Injection versuchen kann zu verhindern...  
              
            Ich bin ein wenig verwirrt weil du doch in deinem anderen post meintest, dass das unsinnig sei.  
            An und für sich, ist das eigentlich genau das wonach ich gesucht habe. Obwohl ich, nach deinen anderen posts, nun nicht mehr sicher bin was ich eigentlich suche.  
              
            Gesti
            
          2. Moin!

            $valid_tables = "tableA|tableB|tableC"

            if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$valid_tables."\s(.+)#im", $query)) {

            }

            
            >   
            > Ein schönes Beispiel, wie man eine SQL-Injection mit einer Regex-Injection versuchen kann zu verhindern...  
            >   
            >  - Sven Rautenberg  
              
            Beispiel bitte.  
              
            Abgesehen davon gibt es gegen SQL Injection neben Prepared Statements noch weitere 3k methoden um die zu verhindern, daran sollte es nicht scheitern, die regular expression dient nur dazu um zu erkennen ob der tabellenname im string vorkommt, wenn die query dann über prepared statements oder durch andere checks durchläuft wie zb (Sql injection detection by structure matching über die ANSI SQL Syntax oder Sl injection detection by removing attribute values) sollte es daran nicht scheitern.
            
            1. Die Abfrage gibt niemals true zurück. Ich gehe mal davon aus, dass die regExp nicht stimmt.

              Gesti

              1. Moin!

                Die Abfrage gibt niemals true zurück. Ich gehe mal davon aus, dass die regExp nicht stimmt.

                Nein, die Regex wird dynamisch mit einer Variablen befüllt, welche "ausführbaren" Regex-Code enthält. Das ist qualitativ dasselbe, wie wenn eine Variable ausführbaren SQL-Code enthält.

                Wer befüllt die SQL-Variable? Der Programmierer. Wer ist verantwortlich für fehlendes Escaping oder Nichtverwendung von Prepared Statements? Der Programmierer.

                Wer befüllt die Regex-Variable? Der Programmierer. Wer ist verantwortlich für fehlerhaft formulierte Regex-Ausdrücke in der Variablen? Der Programmierer.

                Du willst einen simplen Schutz gegen SQL-Injection. Wenn es den gäbe, wäre er schon lange in Datenbanken implementiert. Wenn er auf dem Client mit Leichtigkeit realisierbar wäre, wäre schon längst die Funktion "secure_mysql_query()" erfunden. Ist beides nicht der Fall, weil es keinen simplen Schutz gibt.

                Der aufwendige Schutz ist, sich sein SQL-Statement programmatisch zusammenbauen zu lassen. Die dazu notwendigen SQL-Generatoren sind in ORM-Frameworks wie Doctrine oder Propel enthalten. Die willst du nicht selbst programmieren - du traust dir ja schon beim simplen Schreiben von mehrheitlich statischem SQL nicht selbst über den Weg.

                - Sven Rautenberg

                1. Nein, die Regex wird dynamisch mit einer Variablen befüllt, welche "ausführbaren" Regex-Code enthält. Das ist qualitativ dasselbe, wie wenn eine Variable ausführbaren SQL-Code enthält.

                  Also...

                    
                  $strStatement = "SELECT * FROM `bar`"  
                  $strRegTbl = "foo";  
                  if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$strRegTbl."\s(.+)#im", $strStatement)) {  
                      // wird nicht ausgeführt  
                      var_dump($strRegTbl);  
                  }  
                  
                  

                  irgendwas scheint da wohl nicht zu stimmen, denn egal welche Tabelle ich "registriere", die Bedingung der Abfrage wird nie erfüllt.

                  Gesti

                  1. Ich hab dir oben eine korrigierte gepostet, es haben paar sachen gefehlt schau dir bitte die andere mit dem abc, def beispiel an.

                    lg

                    1. Ich hab dir oben eine korrigierte gepostet, es haben paar sachen gefehlt schau dir bitte die andere mit dem abc, def beispiel an.

                      lg

                      Ich sag halt dazu du musst wissen welche strukturellen abfragen du hast, die version:

                      if (preg_match("#^(SELECT|UPDATE|INSERT)\s(\*\s)?(INTO|FROM)?\s?(abc|def)(.*)$#im", $query)) {

                      ist jetzt noch sehr allgemeint mit select * ... zb. wenn du bestimmte attribute abfragen willst musst du die abfrage noch verallgemeinern.
                      Dafür würde ich aber dann SELECT/UPDATE/INSERT voneinander trennen für und jede eine eigene Regex schreiben also eine nur für select, eine nur mit update und eine für insert

                      if (preg_match("#^SELECT\s(.+)\sFROM\s(abc|def)(.*)$#i", $query)) {

                      etc..

                  2. Moin!

                    Nein, die Regex wird dynamisch mit einer Variablen befüllt, welche "ausführbaren" Regex-Code enthält. Das ist qualitativ dasselbe, wie wenn eine Variable ausführbaren SQL-Code enthält.

                    Also...

                    $strStatement = "SELECT * FROM bar"
                    $strRegTbl = "foo";
                    if (preg_match("#(SELECT|UPDATE|INSERT)\s(INTO|FROM)?".$strRegTbl."\s(.+)#im", $strStatement)) {
                        // wird nicht ausgeführt
                        var_dump($strRegTbl);
                    }

                    
                    >   
                    > irgendwas scheint da wohl nicht zu stimmen, denn egal welche Tabelle ich "registriere", die Bedingung der Abfrage wird nie erfüllt.  
                      
                    Wo sind die Backticks um "bar" herum im Regex?  
                      
                    Der Ansatz bringt allerdings nichts, siehe <https://forum.selfhtml.org/?t=211646&m=1444610>  
                      
                     - Sven Rautenberg
                    
                    1. Hallo!

                      • Sven Rautenberg [..]

                      Zunächst einmal bin ich einwenig verwirrt.
                      Wenn ich einer Klasse Zugriffsrechte auf bestimmte Tabellen vergeben möchte, halte ich das zunächst erstmal nicht für "irrsinning" sondern innerhalb eines bestimmten framesets sogar für sinnvoll.

                      In dem Fall hat das wohl auch eher weniger mit dem Schutz vor SQL-Injection zu tun. Da habe ich mich wohl sehr ungeschickt ausgedrückt. In dem Fall geht es eher um die allgemeine Zugriffsberechtigung auf bestimmte Tabellen.

                      Ich löse es jetzt so, dass ich eine eigene select-Funktion schreibe welche vor der prepare-Funktion aufgerufen werden muss.

                      Danke an ms88aut für den Denkanstoß der mir gefehlt hat!

                      1. Tach!

                        Wenn ich einer Klasse Zugriffsrechte auf bestimmte Tabellen vergeben möchte, halte ich das zunächst erstmal nicht für "irrsinning" sondern innerhalb eines bestimmten framesets sogar für sinnvoll.

                        Du kannst die Zugriffe auf das DBMS nicht innerhalb deiner Anwendung einschränken, egal ob Klasse oder irgendeine andere Codestruktur. Jeder, der in der Lage ist, in deinem System selbst geschriebenen Code auszuführen, und an die Zugangsdaten zum DBMS herankommt, kann beliebig Code zum Zugriff auf das DBMS schreiben, ohne deinen Code zu berücksichtigen oder gezwungen sein, nur über ihn zu gehen. Ihn schränken lediglich die mit diesen Zugangsdaten verbundenen und im DBMS vergebenen Rechte ein.

                        Das ganze Vorhaben ist also grundsätzlich nicht zielführend. Es sei denn, du schreibst eine Art Sandbox in PHP, die noch dazu ausbruchsicher ist.

                        In dem Fall hat das wohl auch eher weniger mit dem Schutz vor SQL-Injection zu tun. Da habe ich mich wohl sehr ungeschickt ausgedrückt. In dem Fall geht es eher um die allgemeine Zugriffsberechtigung auf bestimmte Tabellen.

                        Was nützt es, wenn du lediglich den Beginn eines SQL-Statements prüfst und hintendran beliebiger weiterer Code angehängt werden kann? Da bremst den Ausführenden lediglich die in PHP und MySQL enthaltene Einschränkung, keine zwei (und mehr) Statements in einem Aufruf zu gestatten. Wohlgmerkt "bremst", nicht "hindert", denn zum Beispiel SELECTs kann man wunderbar mit UNION erweitern, DELETEs kann man zu Multi-Table-Delets ausbauen, mit UPDATE kann man über eine zu allgemeine Bedingung alle Datensätze gleichsetzen und wenn man INSERT mit SELECT erweitert, kann man deine Tabelle/Festplatte zum Überlaufen bringen.

                        Ich löse es jetzt so, dass ich eine eigene select-Funktion schreibe welche vor der prepare-Funktion aufgerufen werden muss.

                        Darin müsstest du dir einen ausgewachsenen SQL-Codeparser schreiben. Das ist für den angestrebten Nutzen ein zu hoher Aufwand, der noch dazu das eingangs erwähnte grundlegende Problem nicht lösen kann.

                        dedlfix.

                      2. Moin!

                        Hallo!

                        • Sven Rautenberg [..]

                        Zunächst einmal bin ich einwenig verwirrt.
                        Wenn ich einer Klasse Zugriffsrechte auf bestimmte Tabellen vergeben möchte, halte ich das zunächst erstmal nicht für "irrsinning" sondern innerhalb eines bestimmten framesets sogar für sinnvoll.

                        Deswegen habe ich ja schon ganz zu Beginn gefragt, warum du das tun willst...

                        In dem Fall hat das wohl auch eher weniger mit dem Schutz vor SQL-Injection zu tun. Da habe ich mich wohl sehr ungeschickt ausgedrückt. In dem Fall geht es eher um die allgemeine Zugriffsberechtigung auf bestimmte Tabellen.

                        Das war deine erste Äußerung zu dem Grund deines Handelns.

                        Und wenn die propagierte Lösung ein "Nimm reguläre Ausdrücke" ist, dann lautet die Antwort darauf "Fail!". Weil das nicht funktionieren kann.

                        Was Zugriffsberechtigungen angeht, sieht die Welt anders aus. Aber auch dort sind reguläre Ausdrücke nicht wirklich brauchbar, aus denselben gründen, wie sie bei SQL-Injection nicht brauchbar sind.

                        Gegen welches Szenario willst du dich schützen? Ein interner Programmierer wird, wenn er Code schreibe, die mit der Datenbank Kontakt aufnimmt, vermutlich nicht freiwillig beliebig wilde Tabellen befragen, sondern nur diejenigen, die für seine Aufgabe relevant sind. Mit anderen Worten: Wenn er drei Tabellen braucht, fragt er drei Tabellen ab. Wenn dazu notwendig ist, diese drei Tabellen noch irgendwo anzumelden, damit dein Prüfmechanismus den Zugriff erlaubt, wird er diese Information mit Copy&Paste auch dort noch eintragen.

                        Sollte er später feststellen, dass er weitere zwei Tabellen braucht, wird er die zwei Tabellen nachtragen, und dein Prüfmechanismus wird es erlauben.

                        Und was bringt das dann? Soweit erstmal nichts, außer einem überflüssigen Aufwand für den Programmierer, der diese Prüfroutine bedienen muss.

                        Der einfachere Weg wäre für ihn, den Query einfach direkt abzuschicken, ohne deinen Code zu benutzen. Kannst du das verhindern? Vermutlich nicht.

                        Ich löse es jetzt so, dass ich eine eigene select-Funktion schreibe welche vor der prepare-Funktion aufgerufen werden muss.

                        Wie schon gefragt: Was soll diese Funktion tun?

                        Danke an ms88aut für den Denkanstoß der mir gefehlt hat!

                        Ein Denkanstoß, der das gewünschte Ziel verfehlt, weil das Ziel nicht formuliert war, ist ein schlechter Denkanstoss.

                        - Sven Rautenberg

              2. Das hier ist getestet - man kann ja statt der variablen die tabellen direkt angeben muss man halt für jede klasse eigene checkfuntion bauen:

                  
                <?php  
                  
                $query = "SELECT * FROM sds"; // gibt 0 zurück  
                $query = "SELECT * FROM abc"; // gibt 1 zurück  
                $query = "SELECT * FROM def"; // gibt 1 zurück  
                $query = "UPDATE abc"; // gibt 1 zurück  
                  
                if (preg_match("#^(SELECT|UPDATE|INSERT)\s(\*\s)?(INTO|FROM)?\s?(abc|def)(.*)$#im", $query)) {  
                	echo "1";  
                }  
                else echo "0";  
                  
                  
                ?>
                
                1. Moin!

                  Das hier ist getestet - man kann ja statt der variablen die tabellen direkt angeben muss man halt für jede klasse eigene checkfuntion bauen:

                  <?php

                  $query = "SELECT * FROM sds"; // gibt 0 zurück
                  $query = "SELECT * FROM abc"; // gibt 1 zurück
                  $query = "SELECT * FROM def"; // gibt 1 zurück
                  $query = "UPDATE abc"; // gibt 1 zurück

                  if (preg_match("#^(SELECT|UPDATE|INSERT)\s(*\s)?(INTO|FROM)?\s?(abc|def)(.*)$#im", $query)) {
                  echo "1";
                  }
                  else echo "0";

                  ?>

                    
                  Der Ansatz ist Irrsinn. Wenn du im REGEX vorformulierst, dass IMMER "SELECT \* FROM" kommen muss, gefolgt von bestimmten Zeichen als Tabellenname, gefolgt von beliebigen Zeichen, dann hast du mit exakt diesen beliebigen Zeichen am Ende des Querys die SQL-Injection-Lücke!  
                    
                  $query = "SELECT \* FROM abc UNION SELECT \* FROM anywhere"; // gibt 1 zurück #FAIL  
                    
                  Noch Fragen?  
                    
                   - Sven Rautenberg
                  
      3. Moin!

        Warum ich das machen will ist ganz einfach. Wenn eine externe Klasse einen unerlaubten Zugriff zulässt, durch SQL-Injection oder XSS, dann möchte ich aus Sicherheitsgründen den Zugriff soweit eingrenzen, dass sie nur die Tabellen aufrufen darf, die für einen Zugriff "registriert" wurden.

        Dann benötigst du PHP-seitig einen SQL-Parser, der die im Statement benutzten Tabellen erkennt und mit deiner Vorgabe vergleichen kann - und der ist dann hoffentlich gegen SQL-Injection nicht anfällig.

        Sprich: Dein Vorhaben ist so, wie du es aufgefasst hast, unsinnig. Wenn du Angst hast, dass man dir in SQL was einschmuggeln kann, dann nutze eine Bibliothek, die dir das SQL selbst aus den Daten und Tabelleninfos zusammensetzt. Sowas gibts und nennt sich ORM. Da greifst du dann mit einem objektorientierten Interface auf deine Daten zu, und musst dich um SQL nicht mehr kümmern. Man sollte annehmen, dass SQL-Injections dadurch unmöglich werden, weil du ja gar kein SQL mehr benutzt, sondern benutzen lässt.

        - Sven Rautenberg

  2. Moin!

    class foobar {

    protected $arrRegisteredTables = array();

    public function __construct(){
            // Array mit Tabellen auf die die Klasse zugriff hat
            $this->arrRegisteredTables = array('tblFoo');

    // das soll funktionieren
            $resDatabaseResult = DB::prepare("SELECT * FROM tblFoo")->execute();

    // das soll nicht funktionieren weil der Zugriff auf
            // "tblBar" verweigert wurde (Fehler o. Exception)
            $resDatabaseResult = DB::prepare("SELECT * FROM tblBar")->execute();
        }
    }

    
    >   
    > Könnte mir bitte jemand einen Tipp geben wie ich das am besten umsetzen kann?  
      
    Die Frage ist, warum du es umsetzen willst oder musst?  
      
    Die generelle Antwort wäre: Erzeuge pro Zugriffseinschränkung/-abgrenzung einen passenden DB-User und verwende diesen für die Datenbankzugriffe der jeweiligen Klasse. Die Datenbank wird dann die Ausführung des SQL-Statements verweigern.  
      
      
      
     - Sven Rautenberg
    
    1. hi,

      Die Frage ist, warum du es umsetzen willst oder musst?

      Das frage ich mich auch ;)

      Die generelle Antwort wäre: Erzeuge pro Zugriffseinschränkung/-abgrenzung einen passenden DB-User und verwende diesen für die Datenbankzugriffe der jeweiligen Klasse. Die Datenbank wird dann die Ausführung des SQL-Statements verweigern.

      Full ack! Noch genereller: Soweit 'oben' wie möglich die Aufteilung machen, also DB's nach Usern (wie Sven schrieb) oder DB's nach Mandanten oder DB's nach Verwendungszweck...

      Meine Meinung zu Access-Geschichten: Die haben im Code gar nichts verloren.

      Hotti

      1. Tach!

        Die generelle Antwort wäre: Erzeuge pro Zugriffseinschränkung/-abgrenzung einen passenden DB-User und verwende diesen für die Datenbankzugriffe der jeweiligen Klasse. Die Datenbank wird dann die Ausführung des SQL-Statements verweigern.

        Full ack! Noch genereller: Soweit 'oben' wie möglich die Aufteilung machen, also DB's nach Usern (wie Sven schrieb) oder DB's nach Mandanten oder DB's nach Verwendungszweck...

        So pauschal vorgeschlagen und ohne den Anwendungsfall genauer zu kennen, halte ich das für keine gute Idee. Warum muss man sich zum Beispiel als Admin mit zwei DBs das Leben schwer machen, wenn man über Tabellenrechte die Nutzer auseinanderhalten kann. Vorausgesetzt es handelt sich nicht um zwei völlig verschiedene Anwendungen. Aber das wird ja wohl nicht gegeben zu sein, wenn der Zugriff über denselben Anwendungscode erfolgen soll.

        dedlfix.

    2. Die generelle Antwort wäre: Erzeuge pro Zugriffseinschränkung/-abgrenzung einen passenden DB-User und verwende diesen für die Datenbankzugriffe der jeweiligen Klasse. Die Datenbank wird dann die Ausführung des SQL-Statements verweigern.

      Wie kann ich das "on-the-fly" realisieren?
      Die Registrierung des Users sollte in dem Fall nur temporär und über das PHP-Script umzusetzen sein.

      Grüße, Gesti