SQL-Probleme in fremden Datenbanken finden
Wie sich Sherlock Holmes mit an Forensik angelehnten Methoden nähern würde
In meinem Beruf als IT-Berater mit Spezialisierung auf Microsoft-Datenbanken werde ich immer mal wieder zu "Feuerwehreinsätzen" beim Kunden gerufen. Diese Aufträge zeichnen sich dadurch aus, dass sie nicht wochenlang akribisch vorbereitet werden können – sondern man notfallmäßig ins kalte Wasser geworfen wird. Oft trifft man hierbei auf akute Probleme, die sich als durchaus existenzgefährdend für den betroffenen Betrieb herausstellen können:
- Bislang performante Abfragen sind plötzlich so langsam, dass sie ganze ETL-Strecken gefährden, weil die einzelnen Module zeitlich nicht mehr sauber ineinandergreifen.
- Zugriffe, die bisher funktioniert haben, sind trotz unveränderter Rechtestrukturen unerklärlicherweise plötzlich verboten.
- Mitarbeiter in Schlüsselfunktionen mit Spezialwissen fallen bspw. durch Krankheit plötzlich aus und sind nicht erreichbar.
- Ausgeschiedene Mitarbeiter haben ihr Werk nicht sauber übergeben und irgendeine Routine wird extrem selten, läuft nun zum ersten Mal seit dem Weggang und produziert einen unergründlichen Laufzeitfehler.
Derartige Beispiele lassen sich viele finden. Allen gemein ist die Tatsache, dass man sich nun als externer Berater, von dem oft Wunderdinge erwartet werden, dem Fehler wie ein Detektiv nähern muss.
Absicht oder Unwissen – beides potenziell (gleich) gefährlich
Im Leben eines Consultants lernt man viele Fehlerzustände kennen. Fast immer wird man beim Kunden mit den Worten "Wir haben nichts gemacht." begrüßt – dicht gefolgt von "Der Kollege, der das gebaut hat, ist schon im Ruhestand." Hier gibt es nun mehrere Möglichkeiten. Natürlich gibt es bspw. Updates des Betriebssystems oder des DBMS, welche unerwünschte Nebeneffekte haben. Auch bisher unentdeckte Hardwaredefekte können eine Ursache sein.
Wesentlich spannender sind aber die Fälle, in denen Entwickler oder DB-Admins aus Unwissenheit, aber in guter Absicht den Fehlerzustand selbst verursacht haben. Noch herausfordernder ist es, wenn der Fehler absichtlich platziert und möglichst noch verschleiert wurde. Immer wieder mal gibt es Mitarbeiter, die im Unfrieden ein Unternehmen verlassen und ihrem alten Arbeitgeber noch eine "nette Überraschung" hinterlassen. Um es ganz klar zu sagen: Derartige Manipulationen sind hochkritisch und in der Regel illegal. In beiden Fällen, egal ob absichtlich oder aus Unwissenheit, gilt es nun, per Reverse Engineering und diversen Testroutinen den Fehler einzukreisen und möglichst zu beseitigen.
Wenn man alles ausgeschlossen hat, muss das, was übrigbleibt, die Wahrheit sein
Bisher ist mir kein einziger Fall vorgekommen, bei dem man nicht mit gründlicher Analyse hinter das Geheimnis eines "Fehlverhaltens" von Datenbankcode kommen konnte. Wobei dieser Ausdruck schon ungenau ist, denn die Datenbank verhält sich stets so, wie sie programmiert wurde. Ein Eigenleben existiert nicht! Die Wahrheit liegt also definitiv im Code! Folglich kann man sich durch systematische Analyse Schritt für Schritt der Ursache annähern. Dabei zieht man den Kreis der Verdächtigen immer enger und fängt mit den simplen Ursachen und dem wahrscheinlichsten Vorkommen an. Wenn man nicht fündig wird, engt man den Suchradius weiter ein und geht zu den spezielleren Fällen.
Berufserfahrung und absolute Produktkenntnis auf Admin- und Entwicklerebene erleichtern dieses Vorgehen erheblich. Hat man dann schon die betrieblichen Standardursachen ("Festplatte voll?", "Datei durch anderen User explizit gesperrt?", …) ausgeschlossen, hilft es oft, sich in die Position eines Angreifers hineinzuversetzen:
- Wurden Informationen bewusst versteckt? Dann findet man sie in der Regel trotzdem in den Systemtabellen des DBMS.
- Tritt der Fehler nur mit dem aktuell angemeldeten Nutzer oder auch als <superadmin> auf? Vielleicht wurden ja nur ein paar Berechtigungen neu gesetzt.
- Gibt es Auffälligkeiten im Log des DBMS oder des Betriebssystems? Oft werden Vorgänge redundant, aber in unterschiedlicher Detailtiefe mitprotokolliert oder mutwillige Säuberungsaktionen werden (bspw. aufgrund der fehlenden Rechte) nur an einer Stelle vorgenommen.
Im obenstehenden Beispiel wurde eine Tabelle bewusst vor den Augen Nichteingeweihter versteckt. Dabei bleibt die sonstige Funktionalität vollständig erhalten. Wer den Tabellennamen nicht kennt, aber zumindest den Verdacht hat, dass es zusätzliche Objekte gibt, kann diese nur über eine Systemview oder an der "richtigen" Stelle im Objektexplorer finden:
Absichtliche und "unsichtbare" Manipulation
Bei einem Kunden hat ein DB-Entwickler Daten vor unbefugten Augen verstecken wollen, indem er die Tabellen mit scheinbar identischem Namen gedoppelt hat. Dies ist so natürlich nicht möglich. Tabellennamen müssen per Definition (zumindest innerhalb desselben Schemas) eindeutig sein. Doch wenn man die Objektnamen kreativ vergibt, erzeugt man zumindest optisch einen anderen Eindruck. Hier sind z. B. zwei Tabellen mit dem Namen [Stammdaten].[Artikel] vorhanden. Wer den Trick nicht kennt, kann aber zumindest über den Editor, also per Quellcode, nur eine davon abfragen. Kombiniert man dieses Vorgehen noch mit dem Trick zum Verstecken von oben, ist die Illusion fast perfekt…
Den Hintergrund der Idee kann man im CREATE-Code auf der rechten Seite sehen. Er zeigt, wie die zusätzliche Tabelle angelegt wurde. Im Objektnamen wurde hinter Artikel noch die Tastenkombination ALT+255 (Nummernblock) gedrückt – so entstand ein geschütztes Leerzeichen. Optisch sind die Tabellennamen nicht zu unterscheiden, für den SQL Server handelt es sich aber um zwei separat benannte Objekte.
Von derartiger "Security by Obscurity" ist dringend abzuraten! Von Datenbankproblemen mit solchen Spielereien abzulenken ist unsinnig und höchst unprofessionell. Der unpflegbare und fehleranfällige Code ist gegen versierte Spezialisten ohnehin nicht zu verteidigen.
Gut gedacht, aber schlecht gemacht
Besonders schwierig zu finden sind Fehler, die nicht als solche vom System zu erkennen sind und daher keinerlei Protokolleinträge erzeugen oder Fehlermeldungen generieren. Ist nicht die Syntax eines Befehls inkorrekt, so wird auch die Programmausführung nicht unterbrochen. Stehen dann noch alle angeforderten Ressourcen wie gewünscht zur Verfügung, wird eben genau das ausgeführt, was der Entwickler codiert hat. Als absolute Klassiker in diesem Bereich haben sich implizite Konvertierungen und Rangfolgen von Operationen die vorderen Plätze meiner Verdächtigenliste erkämpft.
Viele Entwickler wissen oder beachten nicht, dass ein DBMS bspw. an vielen Stellen zugunsten der Bedienbarkeit/Bequemlichkeit nicht auf Typengleichheit besteht. Das linke Beispiel oben wird nahezu in jeder meiner Sessions, in denen ich Interessierten diese Feinheiten beibringe, falsch eingeschätzt. Mindestens 90 Prozent der Teilnehmer tippen auf "5" als Ergebnisausgabe des SELECTS, da ja die Bedingung 1= 0 nie wahr ist und folglich der ELSE-Zweig der Bedingung ausgeführt wird. Allerdings vergessen sie die implizite Typumwandlung in einen Datumstyp, die der SQL Server hier ausführt. Dies gilt grundsätzlich auch für das rechte Beispiel. Hier kommt noch hinzu, dass der Datentyp DateTime (für das GetDate) höher priorisiert wird als der numerische Wert 5 – obwohl der ELSE-Zweig in diesem Beispiel nicht durchlaufen wird. Das Ergebnis ist also für beide Exemplare 1900-01-06 00:00:00.000 – probiere das ruhig mal selbst aus!
Die Wahrheit liegt auf dem Platz…
Dieser Fußballspruch lässt sich eins zu eins auf die Datenbankwelt übertragen. Selbst bei noch so kreativer Verschleierungstaktik lässt sich die Lösung definitiv im Quellcode finden. Allerdings braucht der Suchende ein ausgeprägtes Grundverständnis der Datenbankwelt und den Willen, sich in die Kreativideen der Gegenseite, sei sie nun bewusst oder versehentlich ins Projekt gekommen, einzudenken. Wer weiß, wo im System er Informationen vorfindet, ist nicht auf die leicht zu manipulierenden Tools angewiesen – sondern holt sich seine Daten unverfälscht direkt an der Quelle.