Ein bisschen was zur Sicherheit (nicht nur) des SQL Servers 2014

In Redmond nichts Neues
Kommentare

Aus Sicherheitssicht gibt es beim SQL Server 2014 nur wenig Neues. Aber ein Thema ist ja leider immer aktuell: SQL Injection und wie man sie verhindert. Vor allem um letzteres geht es in diesem Artikel, die Neuerungen bei der Sicherheit gibt es quasi als Zugabe.

Fangen wir mit den sicherheitsrelevanten Änderungen im SQL Server 2014 an. Möchten Sie die Kurzform? Es gibt drei neue Berechtigungen als „Security Enhancements“ für die Database Engine und das war es auch schon. Wenn man mal davon absieht, dass hoffentlich wieder viele Programmierfehler und damit potenzielle Schwachstellen aus den Vorversionen nicht in den neuen SQL Server übernommen wurden und Schutzmaßnahmen wie ASLR und DEP weitgehender genutzt werden. Aber das ist seit der Einführung des Security Development Lifecycles eine Selbstverständlichkeit und eigentlich keiner besonderen Erwähnung wert.

Backups mit Verschlüsselung und in die Cloud

Es gibt natürlich auch Verbesserungen, die Microsoft zwar nicht direkt als „Security Enhancements“ aufführt, die die Sicherheit aber trotzdem erhöhen. Die wichtigste: Backups werden verschlüsselt. Zur Verfügung stehen AES mit 128, 192 und 256 Byte Schlüssellänge und Triple DES. Angesichts der NSA-Enthüllungen sollten Sie hier AES-256 wählen. Die Verschlüsselung ist natürlich besonders wichtig, wenn Sie eine weitere neue Funktion des SQL Servers 2014 nutzen: die Backupspeicherung in Windows Azure. Denn Sie wissen ja: In der Cloud wird geklaut. Und vorher schnüffeln NSA und Co. gerne noch ein bisschen in den Daten rum, wenn es ihnen nicht durch eine gute Verschlüsselung verwehrt wird.


Windows Developer 7.14 „SQL Server für die Zukunft“

Wenn Sie mehr zum Thema SQL Server 2014 erfahren möchten, können wir Ihnen die neuste Ausgabe des Windows Developer empfehlen. Windows Developer 7.14 „SQL Server 2014 für die Zukunft“ – ab 6. Juni 2014 im Kiosk!

Drei neue Berechtigungen

Kommen wir noch kurz zu den offiziellen „Security Enhancements“, den drei neuen Berechtigungen auf Serverlevel. „Die CONNECT ANY DATABASE Permission“ dient dazu, einem Benutzerkonto die Verbindung zu allen bereits existierenden Datenbanken sowie allen in der Zukunft neu angelegten Datenbanken zu erlauben. Außer dem Recht zur Verbindung werden keine weiteren Rechte zugewiesen. Um einem Auditing-Prozess das Betrachten aller Daten oder Datenbank-States einer SQL-Server-Instanz zu erlauben, kann diese Berechtigung mit den Berechtigungen „SELECT ALL USER SECURABLES“ (siehe unten) oder „VIEW SERVER STATE“ kombiniert werden. Die „IMPERSONATE ANY LOGIN Permission“ erlaubt Middle-Tier-Prozessen, sich bei der Verbindung mit einer Datenbank als der Benutzeraccount auszugeben (impersonate), der sich mit ihm verbunden hat. Wird die Berechtigung verweigert, kann ein hoch privilegiertes Benutzerkonto wie zum Beispiel eines mit der Berechtigung „CONTROL SERVER“ daran gehindert werden, sich als anderes Benutzerkonto auszugeben. Die „SELECT ALL USER SECURABLES Permission“ erlaubt es einem Benutzerkonto wie etwa einem Auditor, Daten in allen Datenbanken zu sehen, mit denen ein Benutzer sich verbinden kann.

Alles eine Frage der Rechte …

Diese drei neuen Berechtigungen kann sicher der eine oder andere von Ihnen gebrauchen. Eigentlich kann man ja nie genug Rechte haben und am einfachsten arbeitet es sich natürlich als Super-Duper-Hyper-Admin – einschließlich der Lizenz zum Töten der gesamten Datenbank, wenn mal etwas schief geht. Deutlich sicherer ist es aber, wenn jeder Benutzer nur genau die Berechtigungen erhält, die er wirklich für die Erledigung seiner Aufgaben braucht. Im Falle eines Angriffs auf diesen Benutzer beschränkt das die möglichen Folgen, im Idealfall so weit, dass gar kein tatsächlicher Schaden entsteht. Mein Lieblingsbeispiel für solch einen Fall ist eine Webanwendung, die nur Daten ausgibt. Wenn die Anwendung nur lesend auf die sowieso für die Veröffentlichung bestimmten Tabellen der Datenbank zugreifen kann, entsteht im Fall einer SQL-Injection-Schwachstelle kein wirklicher Schaden: Der Angreifer kann zwar alle Daten abfragen, auf die der angegriffene Benutzer Zugriff hat, das sind aber nur die sowieso zur Veröffentlichung bestimmten Daten. Er erfährt also nichts, was nicht andere durch die normale Nutzung der Webanwendung auch erfahren könnten. Gefährlich wird es erst, wenn der Zugriff auf nicht für die Allgemeinheit bestimmte Daten möglich ist. Oder wenn die Anwendung, die eigentlich nur lesen soll, auch Schreibrechte hat, denn dann kann der Angreifer die Datenbank manipulieren und zum Beispiel Code für Drive-by-Infektionen einfügen. Einige Zeit lang war das ein beliebter Zeitvertreib der Cyberkriminellen, vor allem Massenangriffe auf ASP.NET-Anwendungen kamen häufig vor. Wenn auch zum Teil mit nur mäßigem Erfolg für die Cyberkriminellen.

Aber die Verwendung der minimal nötigen Rechte ist ja einer der letzten Schritte bei der Installation und dem Betrieb des SQL Servers. Die Sicherheit fängt schon viel früher an. Microsoft hat „Security Considerations for a SQL Server Installation“ veröffentlicht, die ganz zu Recht schon von der Installation beginnen. Da Sie das als Entwickler nur am Rande betrifft, habe ich diese Schritte in den Kasten „Die Adminseite der Sicherheit“ ausgelagert. Als Entwickler sollten Sie sich im Allgemeinen darauf verlassen können, dass der Server sicher betrieben wird. Und wenn nicht, können Sie es sehr wahrscheinlich auch nicht ändern.

Die Adminseite der Sicherheit Schon bevor der SQL Server installiert wird, kann man einiges für seine Sicherheit tun. Zum Beispiel für seine physikalische Sicherheit sorgen. Was in der Cloud oder im Falle eines von einem ISP gemieteten oder in Colocation gehosteten Servers kein Problem ist, stellt kleine und mittlere Unternehmen teilweise schon vor gewisse Herausforderungen. Den berühmten „Server in der Besenkammer“ gibt es wirklich, besonders sicher ist das natürlich nicht. Es wäre doch ziemlich peinlich, wenn der Admin eines Morgens zur Arbeit kommt und an Stelle seines Datenbankservers nur einen Beamten der Spurensicherung auf der Suche nach dem verschwundenen Server vorfindet.

Server (nicht nur der für die Datenbank) gehören in Räume, zu denen nur autorisierte Personen Zutritt haben. Fragen nach dem Brand- und Diebstahlschutz und Ähnliches sind auch nicht zu vernachlässigen. Immerhin enthält der Datenbankserver im Allgemeinen die wichtigsten Daten des Unternehmens.

Nachdem der Server physikalisch geschützt wurde, muss er mit dem lokalen Netzwerk verbunden werden, oft auch mit dem Internet. Je nachdem, von wo aus er zugänglich sein muss, gehört er entweder komplett ins Intranet oder in die demilitarisierte Zone zwischen Intra- und Internet. Oder in das Rechenzentrum eines ISPs.

Nachdem Standort und Netzwerkanbindung geklärt sind, muss noch für regelmäßige Backups gesorgt werden. Und die müssen an einem anderen Ort aufbewahrt werden, möglichst etwas weiter weg als nur im Nebengebäude. In der Hinsicht ist die Idee eines Backups in Windows Azure zwar gar keine schlechte Idee, ich persönlich würde vertrauliche Daten aber niemals irgendwo in einer Cloud speichern, auch nicht verschlüsselt.

Am besten ist es natürlich, wenn man das Backup nie braucht. Die Verwendung eines sicheren Dateisystems ist dabei sehr hilfreich. Verwenden Sie am besten das stabilere NTFS anstelle von FAT und nutzen Sie die vorhandenen Schutzmaßnahmen wie die Zugriffskontrolllisten (ACL) und das Encrypting File System (EFS).

Firewall und Co. gehören dazu Natürlich muss der Server durch eine Firewall geschützt werden. Je nach Einsatzzweck kommt die z. B. einfach nur zwischen Internet und Server. In Multi-Tier-Umgebungen können mehrere Firewalls zur Aufteilung des Netzwerks in abgesicherte Subnetze verwendet werden. Im Allgemeinen kann man bei der Konfiguration nach dem Grundsatz „Erst mal alles verbieten, dann das Notwendige erlauben“ vorgehen. Zusätzlichen Schutz bieten Intrusion-Detection-/Prevention-Systeme.

Die Aufteilung und Isolation der benötigten Dienste reduziert die Gefahr, dass ein kompromittierter Dienst weitere Dienste angreifen kann. Dazu können separate SQL-Server-Dienste unter separaten Windows-Konten gestartet werden. Dass die verwendeten Benutzerkonten möglichst geringe Rechte haben sollten, dürfte selbstverständlich sein.

Jedes System wird sehr viel sicherer, wenn alle nicht benötigten Protokolle wie etwa NetBIOS und SMB ausgeschaltet werden – was nicht da ist, kann nicht angegriffen werden und muss auch nicht aufwändig geschützt werden.

Nach der Installation: Benutzerkonten und Authentifizierung Nachdem der SQL Server installiert wurde, gilt es, ihn sicher zu konfigurieren. Die SQL-Server-Dienste müssen mit den niedrigsten möglichsten Rechten ausgeführt werden. Für die Verbindung mit dem Server sollte die Windows- oder Kerberos-Authentifizierung verwendet werden. Dass die verwendeten Passwörter möglichst stark sein müssen, ist wohl selbstverständlich. Vor allem für das sa-Konto muss natürlich ein starkes Passwort verwendet werden, aber auch bei den weniger privilegierten Benutzern sollten Sie keine Abstriche zulassen. Die Passwortstärke und das Ablaufdatum des Passworts können über eine Passwort-Policy überwacht werden. 

SQL Injection, alt und doch immer aktuell

SQL Injection war bereits das Thema im Windows Developer 2.2013. Wenn Sie sich für mögliche Angriffe interessieren, finden Sie dort einige Beispiele. Hier soll es um die Abwehr der Angriffe gehen. Die Aufgabe klingt eigentlich ganz einfach: Die Angreifer versuchen, die SQL-Abfragen Ihres Programms zu manipulieren, und Sie müssen „nur“ dafür sorgen, dass diese Manipulationen nicht möglich sind.

Möglichkeit 1: Eingaben auf Zulässigkeit prüfen

Die einfachste Möglichkeit zur Verhinderung von SQL Injection besteht darin, gar keine vom Benutzer und damit einem potenziellen Angreifer manipulierbaren Parameter für die SQL-Abfragen zu verwenden. Im Allgemeinen ist das aber nicht möglich. Nehmen wir als Beispiel eine Webanwendung, die Daten über eine numerische ID auswählt. Das könnte beispielsweise so aussehen: http:// anwendung.example/pfad/zum/skript/zeigeartikel.asp?id=123.

Die ID stammt aus einer Linkliste auf der Seite, die die vorhandenen Artikel aufführt. Der Benutzer gibt diese ID also nirgendwo ein, ein Angreifer könnte sie aber trotzdem manipulieren und z. B. http: //anwendung.example/pfad/zum/skript/zeigeartikel.asp?id=123′ OR ‚1‘=’1′– an den Server senden, der daraufhin alle vorhandenen Artikel ausgibt.

Sie wissen ja: Traue nie dem Client, denn alles was von außen kommt, kann manipuliert sein. In diesem Fall ist die Abwehr des Angriffs aber einfach: Der Parameter id darf nur Zahlen enthalten, kann also einfach in den passenden Wertebereich umgewandelt werden. Andere Parameter können mithilfe regulärer Ausdrücke auf Zulässigkeit geprüft werden. Ein gutes Beispiel dafür sind E-Mail-Adressen: Sie lassen sich sehr gut auf Einhaltung der Regeln für den Aufbau einer E-Mail-Adresse prüfen und können keine gefährlichen Zeichen wie ‚ oder ; enthalten.

Teilweise kann auch eine Whitelist zulässiger und als sicher bekannter Eingaben verwendet werden, um unerwünschte Eingaben auszufiltern: Was nicht erlaubt ist, wird ignoriert. So etwas funktioniert aber nicht bei freien Texteingaben wie Feldern für Suchanfragen, Adressen oder ganz allgemein Texten. Das bekannteste gefährliche Zeichen ist das einzelne Quote-Zeichen ‚, und das können Sie nicht einfach ausfiltern. Das würde eine SQL Injection zwar deutlich schwieriger machen, aber auch z. B. Tim O’Reilly verärgern, wenn der sich registrieren will und die Anwendung aus seinem Namen OReilly macht.

Möglichkeit 2: Eingaben escapen

Die einfachste Lösung für diesen Fall ist das Maskieren (Escapen, Quoten) der kritischen Sonderzeichen: Damit die Datenbank das ‚ aus dem Beispiel nicht auswertet, wird es durch das Davorstellen eines zweiten ‚ maskiert. Beim Maskieren müssen Sie aber aufpassen, ob Sie evtl. bereits maskierte Parameter verarbeiten. Sonst besteht die Gefahr, dass die bereits maskierten Zeichen erneut maskiert werden. Im schlimmsten (zum Glück meist theoretischen) Fall würde dann falsch maskiert, sodass ein eingeschleustes Sonderzeichen am Ende unmaskiert übrig bleibt und der Angriff erfolgreich ist. Meist führt das falsche Maskieren aber „nur“ zu merkwürdigen Ausgaben wie O“““““Reilly. Maskieren ist auch kein Allheilmittel gegen SQL Injection. Wird das ‚ maskiert, wären immer noch Angriffe wie zum Beispiel

1; Drop Table Admin --

möglich. Außer dem Quote-Zeichen ‚ sind auch das Semikolon ; (trennt SQL-Abfragen) und doppelte Bindestriche — (leiten Kommentare ein) gefährlich. Alles Zeichen, die durchaus auch in normalen Texten, etwa in einem Forum oder Gästebuch, vorkommen können. Und das sind nicht die einzigen kritischen Zeichen, es gibt noch etliche mehr, ebenso wie Strings, die man ungern in SQL-Abfragen sehen möchte (wie „Drop Table Admin“).

Maskieren ist also nur ein zweifelhafter Schutz. Außerdem entspricht es einem Blacklist-Ansatz. Gelingt es einem Angreifer hingegen, SQL-Injection-Code zu konstruieren, der ohne die maskierten Zeichen auskommt, wird der ausgeführt. Und wenn in einer neuen Datenbankversion neue kritische Zeichen dazukommen, muss die Maskierfunktion daran angepasst werden. Ansonsten können die neuen Zeichen sofort für Angriffe verwendet werden, da die Schutzfunktion sie nicht erkennt.

Möglichkeit 3: Prepared Statements mit Parameter-Binding

Einen (und zwar den einzigen) zuverlässigen Schutz vor SQL Injection bieten parametrisierte Aufrufe (Parameterized Queries) von Prepared Statements oder Stored Procedures. Der Aufbau des SQL-Statements wird dabei in zwei Schritte aufgespalten:

  1. Die Struktur des SQL-Statements wird definiert, für alle Eingaben werden Platzhalter eingesetzt.
  2. Der Inhalt der Platzhalter wird spezifiziert.

Die im zweiten Schritt erzeugten Daten haben keine Möglichkeit, die im ersten Schritt definierte Struktur des Statements zu ändern. Nachdem die Struktur des Statements definiert wurde, werden alle für die Platzhalter übergebenen Daten als Literal und nicht als Teil der Statementstruktur betrachtet. Schleust ein Angreifer SQL-Code ein, wird er nicht ausgeführt. Am einfachsten lässt sich der Einsatz von Prepared Statements an einem Beispiel erklären (Listing 1).

SqlConnection conn = new SqlConnection(connectionString);
// Prepared Statement vorbereiten
string sql = "SELECT benutzer_ID FROM benutzer_tabelle WHERE benutzer_name = @benutzername AND passwort = @passwort";
SqlCommand cmd = new SqlCommand(sql, conn); 
// Parameterobjekte erzeugen und Werte hinzufügen 
cmd.Parameters.AddWithValue("@benutzername", benutzername.Text); 
cmd.Parameters.AddWithValue("@passwort", passwort.Text); 
// Fertige SQL-Abfrage ausführen
conn.Open(); 
SqlDataReader reader = cmd.ExecuteReader();

Die Variable connectionString enthält die Verbindungsdaten für die Datenbank. Dem SQL-Befehl cmd wird das vorbereitete SQL-Statement samt der beiden Parameter @benutzername und @passwort übergeben. Danach werden die Parameter mit den Werten der beiden Variablen benutzername und passwort gefüllt und der SQL-Befehl wird ausgeführt. Die Parameter @benutzername und @passwort werden als Literal und nicht als ausführbarer SQL-Code betrachtet. Schleust ein Angreifer darüber SQL-Code ein, wird er nicht ausgeführt.

Parameter beeinflussen

Bei der Verwendung von AddWithValue() werden Typ und ggf. Länge der Parameter von der Datenbank festgelegt. Das funktioniert eigentlich immer, mitunter wird aber nicht die bestmögliche Lösung verwendet. Möchten Sie selbst bestimmen, welcher Typ und ggf. welche Länge verwendet wird, geht das durch die Verwendung von Add() anstelle von AddWithValue() (Listing 2).

SqlConnection conn = new SqlConnection(connectionString);
// Prepared Statement vorbereiten
string sql = "SELECT benutzer_ID FROM benutzer_tabelle WHERE benutzer_name = @benutzername AND passwort = @passwort";
SqlCommand cmd = new SqlCommand(sql, conn); 
// Parameterobjekte erzeugen (so spezifisch wie möglich)
cmd.Parameters.Add("@benutzername", SqlDbType.VarChar, 12);
cmd.Parameters.Add("@passwort", SqlDbType.VarChar, 12);
// Parameterwerte hinzufügen
cmd.Parameters["@benutzername"].Value = benutzername.Text;
cmd.Parameters["@passwort"].Value = passwort.Text;
// Fertige SQL-Abfrage ausführen
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();

Die Eingaben werden auf ihren Typ und ihre Länge hin geprüft. Beide Werte können nicht länger als zwölf Zeichen sein. Werden längere Strings über die Variablen benutzername und/oder passwort eingegeben, wird eine Exception ausgelöst. Schleust ein Angreifer kürzeren SQL-Code ein, wird der wie im Fall von AddWithValue() in Listing 1 nicht ausgeführt, da die Parameter als Literal behandelt werden.

Wichtig: Parametrisierter Aufruf

Die Prepared Statements sind nur sicher vor SQL Injection, wenn sie parametrisiert aufgerufen werden. Übergeben Sie die Benutzereingabe direkt, wird enthaltener SQL-Code erkannt und natürlich auch ausgeführt (Listing 3).

SqlConnection conn = new SqlConnection(connectionString);
// Prepared Statement vorbereiten
string sql = "SELECT benutzer_ID FROM benutzer_tabelle WHERE benutzer_name = '" + benutzername.Text  + "' AND passwort = '" + passwort.Text + "'";
// Parameterobjekte erzeugen und Werte hinzufügen entfällt
// Die SQL-Abfrage kann sofort ausgeführt werden
conn.Open(); 
SqlDataReader reader = cmd.ExecuteReader();

Damit sparen Sie sich die Zuweisung der Parameter – und erhalten eine wunderschöne SQL-Injection-Schwachstelle. Zwei oder vier eingesparte Zeilen gegen eine Datenbank unter der Kontrolle eines Angreifers ist ein sehr schlechter Tausch.

Stored Procedures – das Gleiche in anderer Farbe

Anstelle von Prepared Statements können Sie auch Stored Procedures verwenden. Listing 4 zeigt dasselbe wie Listing 2, nur angepasst an die Nutzung der Stored Procedure BenutzernamePasswortAbfrage. Wichtig ist der parametrisierte Aufruf. Verwendet eine Stored Procedure die Benutzereingaben direkt oder verbinden Sie SQL-Abfrage und Benutzereingaben zu einem einzigen String, ist auch wieder SQL Injection möglich.

SqlConnection conn = new SqlConnection(connectionString);
// Stored Procedure auswählen
var sql = "BenutzernamePasswortAbfrage";
SqlCommand cmd = new SqlCommand(sql, conn); 
cmd.CommandType = CommandType.StoredProcedure;
// Parameterobjekte erzeugen (so spezifisch wie möglich)
cmd.Parameters.Add("@benutzername", SqlDbType.VarChar, 12);
cmd.Parameters.Add("@passwort", SqlDbType.VarChar, 12);
// Parameterwerte hinzufügen
cmd.Parameters["@benutzername"].Value = benutzername.Text;
cmd.Parameters["@passwort"].Value = passwort.Text;
// Fertige SQL-Abfrage ausführen
conn.Open();	
SqlDataReader reader = cmd.ExecuteReader();

Defense in Depth

Falls es trotz aller Schutzmaßnahmen doch einmal zu SQL Injection kommt, helfen einige einfache Maßnahmen, die Auswirkungen zu minimieren:

  • Der Zugriff auf die Datenbank darf nur mit den absolut notwendigen Rechten erfolgen. Werden z. B. nur Daten gelesen, muss der verwendete Benutzer sich nur mit der Datenbank verbinden und die betreffenden Tabellen lesen können. Weitere Lese- oder gar Schreibzugriffe sind nicht nötig und würden im Falle eines Angriffs nur den Schaden vergrößern.
  • Fehlermeldungen sind nützlich – für Entwickler und Angreifer. Einen Benutzer interessiert es nicht, welcher Fehler genau aufgetreten ist und wie die betroffene SQL-Abfrage aussieht. Geben Sie also keine ausführlichen Fehlermeldungen an die Benutzer aus. Denen ist viel mehr mit einem Hinweis geholfen, wie sie sich nun verhalten sollen. Fehlermeldungen sind viel besser im Logfile aufgehoben, wo Sie sie bei Bedarf analysieren können.
  • Sensible Daten sollten nur verschlüsselt gespeichert werden. Da der Schlüssel für den Zugriff auf die Daten benötigt wird, muss er auf dem Server vorhanden sein. Bei einer Kompromittierung des Servers kann der Angreifer die Daten also entschlüsseln. Bei einem reinen SQL-Injection-Angriff kann der Angreifer mit den Daten aber nichts anfangen.
  • Passwörter müssen immer als Hash-Wert gespeichert werden. Am besten verwenden Sie eine der dafür speziell entwickelten Hash-Funktionen, da normale Hash-Funktionen im Falle von Passwörtern meist keine ausreichende Sicherheit bieten [Eilers, Carsten: „Passwörter speichern, aber richtig!“, in PHP Magazin 6.2013].

Fazit

Im Bereich der Sicherheit gibt es wenig Neues im SQL Server 2014. Drei neue Berechtigungen helfen, den möglichen Missbrauch von Rechten einzugrenzen. Und die Verschlüsselung von Backups sorgt dafür, dass sie nicht gelesen werden können, falls sie in falsche Hände geraten.

Für Sie ändert sich also wenig: SQL Injection ist und bleibt die größte Gefahr für SQL-Datenbanken, insbesondere wenn sie von Webanwendungen genutzt werden. Aber SQL Injection kann auch sehr effektiv verhindert werden. Wenn Sie durchgehend Stored Procedures und/oder Prepared Statements verwenden und diese parametrisiert aufrufen, sind Sie auf der sicheren Seite.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -