MySQL
Zufällige MySQL Abfrage
Ab und an möchte man eine bestimmte zufällige Anzahl von Ergebnissen aus einer MySQL Tabelle Abfragen.
Hier die entsprechende Syntax:
SELECT * from meine_tabelle ORDER BY RAND() LIMIT 25
MySQL Root Passwort vergessen
Manchen Kunden passiert es das Sie Ihr MySQL Root Passwort vergessen. Bei Plesk Systemen lässt sich dieses meistens aus der Datei /etc/psa/.psa_shadow ermitteln.
Man kann es aber auch einfach zurücksetzen:
Die my.cnf bearbeiten (/etc/my.cnf oder Debian: /etc/mysql/my.cnf) und schreibe unter der [mysqld]-Sektion:
skip-grant-tables
Und dann einfach folgende Befehle ausführen:
/etc/init.d/mysql restart
mysql -u root
UPDATE mysql.user SET Password=PASSWORD('Passwort') WHERE User='root';
exit
# "skip-grant-tables" Zeile aus der my.cnf entfernen
/etc/init.d/mysql restart
Aus Sicherheitsgründen sollte man anschließend die ~/.mysql_history entfernen bzw. die UPDATE Zeile löschen.
MySQL und Backticks `FELDNAME`
Ich denke viele sind schon über MySQL Queries gestolpert die so aussehen:
SELECT user_id, xyz FROM ...
Was ist daran falsch? Nunja richtig sollte die Anweisung so lauten:
SELECT `user_id`, `xyz` FROM ...
Das Problem ist das ohne Backticks die Abfrage zwar funktioniert, aber wenn man ausversehen ein MySQL Schlüsselwort erwischt es sehr merkwürdige Fehler erzeugt. Zum Beispiel wenn plötzlich der Query nach einem MySQL Update nicht mehr funktioniert, weil ein neues Schlüsselwort eingeführt wurde.
Die reservierten Wörter findet man übrigens hier: http://dev.mysql.com/doc/refman/5.5/en/reserved-words.html
Anwendungsoptimierung
Eine unserer Aufgaben im Linux Server Support ist es Kunden beim Optimieren Ihrer Seite(n)/Server zu unterstützen. Meistens betrifft dieses Servereinstellungen zu verfeinern. Manchmal müssen wir uns aber auch auf die Anwendungsebene begeben um zu analyisieren wo Performance verloren geht.
In dem jetztigen Fall ging es um ein VB Forum mit über 200.000 Seiten. MySQL und Apache waren schon wunderbar eingestimmt. Trotz der fast 200 Onlineuser waren die Seitenwerte unter 1 Sekunde. Eigentlich alles optimal. Aber der Betreiber merkte das der Googlebot immer länger zum Spidern brauchte.
Nach einer Analyse mit Jmeter viel mir auf das Beitragsseiten zum Vergleich zu anderen ca. 400ms länger also im Schnitt 800ms zum Laden brauchten. Im Schnitt hatte die Seite einen Wert von 319ms (nur dynamischer Content).
Ich denke viele wären über solche Werte schon sehr froh. Aber da die Googlebewertung einer Seite auch davon abhängt wie schnell Sie sich Spidern lässt und 400ms * 200.000 Seiten = ca. 22h schnellers crawlen bedeutet, hatten wir eine neue Herausforderung vor uns.
Mit der zur Hilfenahme von xdebug gelang es uns relativ schnell den Übeltäter mittels Profiling ausfindig zu machen. Und zwar setzt der Kunde zur Seo Optimierung VBSEO ein. Diese Extension machte in der vorhandenen Version sehr viel Gebrauch von preg_replace() (über 80.000 Aufrufe pro Seite). Des Weiteren viel uns auf das sich im APC Usercache gerade einmal 80 KByte an Daten befanden. Jetzt hätte man natürlich den Source Code durchforsten können, aber das ist für mich bei komplexeren Anwendungen immer nur die letzte Wahl.
Denn man kann so schnell die Updatefähigkeit verlieren und man sollte zunächst versuchen bei den Entwicklern nachzufragen. Offiziell gab es zwar noch kein Update aber eine inoffizielle Entwicklerversion. Diese sollte eigentlich das Cache Problem lösen, was leider nicht wirklich klappte, dafür aber waren die Beitragsseiten nun genauso schnell wie alle anderen.
UUID und Datenschutz
Wie in unserem Beitrag "Datenschutz mal richtig umsetzen" geschrieben sollte man aufsteigende (z.B. MySQL autoincrement) ID Primary Felder vermeiden. Wenn man sich ein wenig umschaut merkt man schnell das UUIDs diesen Zweck erfüllen könnten.
Zwar sind diese eigentlich aus einem ganz anderen Grund erfunden worden, aber warum das Rad neu erfinden wenn ein Quasi Standard auch den Zweck erfüllt. Nebenbei erhält man damit auch die Möglichkeit Datensätze unabhängig (offline) vom MySQL Server zu erzeugen. Denn man erzeugt ja nun selbst die Primary ID und wenn man sich an den UUID Standard hält sollten auch keine Konflikte auftreten.
Unter Cakephp geht das ganze auch sehr Schmerzfrei indem man einfach das ID Feld statt mit int(11) mit char(36) anlegt. Dann kümmert sich Cakephp selbständig um die Erzeugung der UUID Nummern.
Datenschutz mal richtig umsetzen
http://www.heise.de/security/meldung/Datenpanne-Unesco-entbloesst-Bewerber-im-Netz-1234573.html
Anlässlich dieses Vorfalls möchte ich einmal die technischen Gegenmaßnahmen erläutern die einen solchen Vorfall verhindert hätten. Also zunächst worum ging es genau.
Gegeben sei ein Login Bereich wo nur registrierte und eingeloggte Nutzer Zugriff haben. Nach dem Login kann der Nutzer seine Daten einsehen und ggf. bearbeiten. Der Aufruf seiner Daten erfolgt über folgende URL:
http://example.net/user.php?id=120
oder eine Seite die z.B. mod-rewrite nutzt:
http://example.net/user/120
Wie man sehen kann steht mit hoher Wahrscheinlichkeit die 120 für die Zuordnung zur ID des (MySQL) Datensatzes.
Natürlich kann im Hintergrund auch jede andere Art von persistenter Datenspeicherung erfolgen. Jetzt könnte man mit dieser ID ein wenig rumspielen und den Wert um eins erhöhen oder verringern:
http://example.net/user/121
http://example.net/user/119
Wenn die Anwendung nun nicht prüft ob die ID vom User überhaupt abgerufen werden darf, kann jemand relativ simpel alle Datensätze anderer User auslesen. Und genau das ist auch der Unesco passiert es wurde zwar Authentifiziert (Registrierung und Login) und evtl. sogar der Zugriff gruppentechnisch per ACL autorisiert, aber es wurde vergessen den Zugriff auf die einzelnen Datensätze des entsprechenden Nutzers einzuschränken.
Welche Lösungen gibt es so etwas zu verhindern?
1. Die ID als mehrstellige Zufallszahl ausführen
Das könnte dann so aussehen:
http://example.net/user/eachaeso1tie
http://example.net/user/aimizoh9oosu
Nun ist es für jemanden ohne interne Kenntnisse schwierig weitere IDs auf anhieb zu erraten. Aber auch nicht unmöglich. Denn eine Brute Force Attacke wäre auch hier noch erfolgreich. Auch muss man sich selbst um die Generierung der ID kümmern und dafür sorgen das keine Dubletten generiert werden. Ein weiterer kleinerer Nachteil liegt auch in der Performance. Allerdings kann man Brute Force Attacken sehr leicht erkennen und z.B. nach drei Fehlgeschlagenen abrufen den User sperren.
2. Ordentliche Rechteprüfung ob der Nutzer die Daten überhaupt abrufen darf
Schon etwas aufwendiger je nach zu Grunde liegender Architektur ist die saubere Prüfung ob der Datensatz dem Nutzer gehört.
Richtig sauber wird es dann wenn man auch hierbei fehlgeschlagene Abrufe loggt und somit ggf. komprimierte Logins aufdeckt und frühzeitig sperren kann.
Bei einem öffentlichen Dienst mit freier Registrierung ist es natürlich nur eine Frage der Zeit bis der Angreifer ein frisches Konto hat.
Der Grund warum so etwas immer wieder vorkommt ist wahrscheinlich die trügerischer Sicherheit das der Login schon autorisiert ist und der Kunde/Endnutzer nicht weiß wie schnell man seine Daten ausspähen kann. Bei Cakephp lassen sich diese Konzepte wunderbar z.B. in das entsprechende Model unterbringen/abstrahieren.
Langlaufende MySQL Query Abfragen killen
Wenn man das Maatkit für MySQL installiert hat, kann man relativ einfach MySQL Querys die zu lange laufen killen lassen.
mk-kill -u user -pmeinpasswort --busy-time 8 --kill --print
Wer erst einmal nur testen will:
mk-kill -u user -pmeinpasswort --busy-time 8 --print
