Schutz vor Berechtigungsmissbrauch in SQL Server

cover image of blog article 'Schutz vor Berechtigungsmissbrauch in SQL Server'

Einführung

Dieser Artikel richtet sich an all jene Datenbankadministratoren, die ihre SQL Server Datenbanken vor Missbrauch und Datenraub durch potentielle Angreifer schützen wollen. Wir erklären Ihnen verschiedene Maßnahmen, die Sie ergreifen können um Ihre User und Administratoren vor solchen Szenarien zu bewahren und gegen einen Hijacking Angriff zu verteidigen. Je mehr Berechtigung eine einzelne Person hat, desto interessanter wird sie für potenzielle Angreifer. Die meisten solcher Angriffe erfolgen über bereits autorisierte Nutzer mit entsprechenden Berechtigungen und Zugriffen auf das System. Allerdings können auch Entwickler ohne ebendiese Berechtigungen Sicherheitslücken ausnutzen und sich Zugriff zu Ihrer Datenbank verschaffen. Wir wollen Ihnen mit diesem Artikel helfen, dass diese Angriffe nicht erfolgreich durchgeführt werden und Sie sich umfassend schützen.

Explizit untersuchen wir folgende Angriff-Szenarios:

  1. Angriff auf Benutzer mit sysadmin Berechtigungen
    Sie sind der leitende DBA oder ein sysadmin auf Serverebene und verfügen über die CONTROL SERVER Berechtigung. Der Angreifer ist ein User mit der Berechtigung, gespeicherte Prozeduren und Trigger direkt oder indirekt zu erstellen, indem sein Code auf die Zielumgebung deployt wird. Sein Ziel ist es sysadmin zu werden oder Ihre Berechtigungen auszunutzen, um sensible Daten zu stehlen oder zu manipulieren.

  2. Cross DB Angriff
    Sie sind User in der Datenbank 1. Der Angreifer, der erhöhte Berechtigungen in der Datenbank 2 besitzt, nicht aber in Datenbank 1, möchte Ihre Rolle übernehmen und kritische Daten stehlen.

  3. Angriff auf Benutzer mit db_owner Berechtigungen
    Sie sind DBA auf Datenbankebene und haben die CONTROL Berechtigung in dieser Datenbank. Der Angreifer besitzt direkte oder indirekte Berechtigungen zur Erstellung von gespeicherten Prozeduren oder anderen ausführbaren Modulen. Er strebt die vollständige Kontrolle über die Datenbank an.

Der Ausgangspunkt für unsere Untersuchung ist die Frage, wie Jobs zur Index- und Statistikpflege durch DDL-Trigger missbraucht werden können. Anschließend werfen wir einen Blick darauf, wie Sie dazu verleitet werden können, DML-Trigger oder gespeicherte Prozeduren auszuführen, die bösartigen Code beinhalten. Wir zeigen kurz, wie Sie gespeicherte Prozeduren schreiben können, die privilegierte Aktionen auf eine Art ausführen, so dass auch nicht privilegierte User diese ausführen können. Im Anschluss schauen wir uns Agent Jobs an, die eine enorme Angriffsfläche für das Hijacking von Berechtigungen darstellen - es sei denn, Sie befolgen unsere Ratschläge. Als letztes gehen wir darauf an, wie ein Entwickler Code in ein Deployment-Skript einschleusen könnte, das Sie ausführen, oder sich zunutze machen könnte, dass Sie der Benutzer einer Anwendung sind, über die er oder sie Kontrolle hat.

Seien Sie sich jederzeit bewusst, je mehr Kontrolle und Berechtigungen Sie haben, desto mehr Angriffsfläche bieten Sie kriminellen Personen.

Angriffe über DDL Trigger

Szenario 1 - Angriff auf sysadmin

Das Szenario 1 beschreibt die Situation, dass Sie die Rolle des DBA auf Serverebene inne haben. Für viele oder sogar alle Datenbanken auf dem Server existieren User mit db_owner Rechten für die lokale Datenbank, jedoch ohne Berechtigungen auf Server-Ebene. Als Größenumfang nehmen wir 50 - 100 kleine bis mittelgroße Datenbanken an.
Sie wollen Wartungsaufträge erstellen die gängige Wartungsarbeiten erledigen: Datenbanken sichern, Datenbankintegrität prüfen, Statistiken aktualisieren und Indizes reorganisieren bzw. neu aufbauen. Solche Wartungsaufträge würden normalerweise unter einem Konto mit sysadmin Berechtigungen laufen. Aber sollten Sie das wirklich tun?

Gefahrensituation: Ein lokaler Power-User hätte die Möglichkeiten mit folgendem Skript einen DDL-Trigger zu einer bestehenden Datenbank hinzuzufügen und sich dadurch selbst zum sysadmin zu machen:

CREATE OR ALTER TRIGGER EvilDdlTri ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS AS 
   IF is_srvrolemember('sysadmin') = 1 
       EXEC('USE master ALTER SERVER ROLE sysadmin ADD MEMBER [DOMAIN\EvilUser]')

Durch Backups oder Integritätsprüfungen wird der DDL Trigger nicht ausgelöst. Jedoch können UPDATE STATISTICS und ALTER INDEX REBUILD / REORGANIZE zur Auslösung des Triggers führen. Er prüft hierbei, ob der ausführende User sysadmin Berechtigungen besitzt. Trifft dies zu, führt der Trigger Anweisungen aus, die im Berechtigungsrahmen des sysadmin liegen. In unserem Beispiel weist sich der User selbst sysadmin Rechte zu und hat somit auf alle Daten uneingeschränkten Zugriff.
Wir empfehlen daher - gerade bei kritischen Daten - die Mitglieder der Administratoren-Gruppe regelmäßig auf Unstimmigkeiten zu überprüfen. Jeder Name, der normalerweise nicht in dieser Gruppe auftauchen sollte, könnte ein Indiz für einen möglichen Angreifer sein und sollte mit besonderer Aufmerksamkeit behandelt werden. Ist das Ziel es Angreifers jedoch “nur” an sensible Daten zu gelangen, kann dies auch direkt durch die Ausführung des Triggers geschehen. Es besteht keine Notwendigkeit für den Angreifer, sich selbst höhere Berechtigungen zuzuweisen, was es umso schwerer macht, einen solchen Fall aufzudecken.

Leider gibt es kaum Möglichkeiten, einen DDL-Trigger zu stoppen, sobald er auf Serverebene in Aktion war. Auch diesen aufzuspüren ist schier unmöglich, da die Updates schließlich durch die Berechtigungsübergabe von einem sa oder Dienstkonto von SQL Server ausgeführt wurden.

Unser Ziel ist es also, möglichst einen Weg zu finden, der einen Angriff durch den DDL Trigger gar nicht erst zulässt.

→ Sorgen Sie dafür, dass Sie niemals Jobs mit mehr Berechtigungen durchführen, als für die dessen Ausführung nötig sind. Diese Strategie wird auch PoLP - Principle of Least Priviledge genannt.

Um das PoLP Prinzip einzuhalten, gibt es eine recht simple Maßnahme die Sie dafür ergreifen können. Eine Wartungsprozedur zur Pflege von Indizes enthält in der Regel dynamischen SQL Code, der über Datenbanken und Tabellen auf dem Server iteriert.

SELECT @sql = 'ALTER INDEX ' + quotename(@ixname) + ' ON ' + 
              quotename(@schema) + '.' + quotename(@table) + ' REORGANIZE'
SELECT @sp_executesql = @db + '.sys.sp_executesql'
EXEC @sp_executesql @sql

SELECT @sql = 'USE ' + quotename(@db) + '
              ALTER INDEX ' + quotename(@ixname) + ' ON ' + 
              quotename(@schema) + '.' + quotename(@table) + ' REORGANIZE'
EXEC (@sql)

Ergänzen Sie den @sql-Batch um folgenden Befehl, um das Hijacking von Berechtigungen zu vermeiden.

EXECUTE AS USER = 'dbo'

Dieser Befehl imitiert den Datenbank Owner. Das heißt, Sie geben sich als Eigentümer der Datenbank aus. Immer dann, wenn Sie sich als ein Datenbankbenutzer ausgeben, wird Ihrem Sicherheitskontext auf Serverebene nicht vertraut - selbst wenn Sie in Wirklichkeit Systemadministrator sind. Um dies zu veranschaulichen, suchen Sie sich eine Datenbank zum Testen (tempdb reicht aus) und erstellen diesen DDL-Trigger:

CREATE OR ALTER TRIGGER ddltri ON database for DDL_DATABASE_LEVEL_EVENTS AS 
   SELECT is_srvrolemember('sysadmin'),* FROM sys.login_token

Wählen Sie nun eine Tabelle aus und führen folgenden Befehl aus:

ALTER INDEX ALL ON sometable REBUILD

Sie erhalten im Nachgang folgende Ausgabe:

principal_id sid        name               type           usage
---- ------------ -------    ------------------ -------------- ---------------
1    259          0x01050... DOMAIN\user        WINDOWS LOGIN  GRANT OR DENY
1    2            0x02       public             SERVER ROLE    GRANT OR DENY
1    3            0x03       sysadmin           SERVER ROLE    GRANT OR DENY
...

Anhand der ersten Spalte können Sie erkennen, dass Ihre Abfrage unter sysadmin Berechtigungen ausgeführt wird. In der Spalte “usage” rechts wird GRANT OR DENY angezeigt, was im Umkehrschluss bedeutet, dass sowohl die Vergabe als auch die Rücknahme von Berechtigungen möglich sind.

Als nächstes integrieren wir den obigen Befehl in das EXECUTE AS USER Skript:

EXECUTE AS USER = 'dbo'
ALTER INDEX ALL ON sometable REBUILD
REVERT

Möglicherweise erhalten Sie folgende Fehlermeldung:

“Msg 15517, Level 16, State 1, Line 152"

Cannot execute as the database principal because the principal "dbo" does not exist, this type of principal cannot be impersonated, or you do not have permission.”

Das könnte mitunter daran liegen, dass die SID für dbo innerhalb der Datenbank (sys.database_principals) nicht mit der Spalte sys.databases.owner_sid übereinstimmt. Ein solches Szenario passiert immer dann, wenn Sie eine Datenbank-Sicherung auf einem anderen Server wiederherstellen. Der Befehl RESTORE ist dafür verantwortlich, den Eigentümer in sys.databases zu behalten oder alternativ den ausführenden Benutzer als Besitzer zu berechtigen. RESTORE wird jedoch niemals die SID für dbo in der Datenbank lesen oder aktualisieren. Der Datenbank Owner sollte besser durch den ALTER AUTHORIZATION Befehl in den vorgesehenen Eigentümer geändert werden.

Bei erfolgreicher Ausführung der Abfrage erhalten wir folgende Ausgabe:

principal_id sid         name               type           usage
----- ------------ ----------- ------------------ -------------- ----------
0     259          0x010500... DOMAIN\user        WINDOWS LOGIN  DENY ONLY
0     2            0x02        public             SERVER ROLE    DENY ONLY
0     3            0x03        sysadmin           SERVER ROLE    DENY ONLY
...

Die 0 in der linken Spalte heißt, dass Sie nicht als sysadmin handeln. In der Spalte “usage” ganz rechts steht durchgängig DENY ONLY. Das heißt, alle diese User können nur Rechte entziehen, aber keine Berechtigungen vergeben. Mit diesem Vorgehen haben Sie sich also im aktuellen Kontext selbst um die sysadmin Rechte beraubt. Gleiches gilt für den Zugriff auf andere Datenbanken, die allerdings in der obigen Tabelle nicht aufgelistet werden. Sobald Sie EXECUTE AS USER verwenden, agieren Sie sich ausschließlich in der aktuellen Datenbank. Würde nun ein DDL Trigger versuchen, Ihre Berechtigungen auf Serverebene zu missbrauchen oder Daten aus anderen Datenbanken abzuziehen, wird er diese nicht finden können. Geben Sie sich als Datenbank Owner aus, haben Sie weiterhin alle Rechte und Zugriffe auf der Datenbank, um Wartungsarbeiten durchführen zu können. Natürlich besteht aber weiterhin die Möglichkeit, dass Benutzer mit niedrigeren Berechtigungen über den DDL-Trigger Code im Rahmen eines db_owner ausführen können.

Mit dem Befehl REVERT können Sie den Identitätswechsel dann wieder rückgängig machen und zu den Ursprungsberechtigungen zurückkommen. Und dabei brauchen Sie keine Bedenken haben, dass der REVERT von einem DDL Trigger ausgenutzt werden könnte. Dieser wirkt sich nur auf ein EXECUTE AS aus.

Nach der Anwendung von EXECUTE AS USER sehen die Snippets folgendermaßen aus:

SELECT @sql = 'EXECUTE AS USER = ''dbo''
               ALTER INDEX ' + quotename(@ixname) + ' ON ' + 
               quotename(@schema) + '.' + quotename(@table) + ' REORGANIZE
               REVERT'
SELECT @sp_executesql = @db + '.sys.sp_executesql'
EXEC @sp_executesql @sql

SELECT @sql = 'USE ' + quotename(@db) + '
              EXECUTE AS USER = ''dbo''
              ALTER INDEX ' + quotename(@ixname) + ' ON ' + 
              quotename(@schema) + '.' + quotename(@table) + ' REORGANIZE
              REVERT'
EXEC (@sql)

→ Achten Sie darauf, dass Sie zum Wechseln der Zieldatenbank im zweiten Schritt den USE Befehl vor EXECUTE AS - stellen.

Ist die Datenbank als vertrauenswürdig (TRUSTWORTHY) eingestuft worden und einem sa zugeordnet, erzielt der EXECUTE AS USER Befehl jedoch nicht den gewünschten Effekt und der DDL Trigger hat Zugriff auf die sysadmin Berechtigung. Die Kombination von TRUSTWORTHY und der Ernennung eines sa als Owner der Datenbank stellen an sich schon ein großes Sicherheitsrisiko dar, was einem Angreifer mittels DDL Trigger den Aufstieg zu einem Systemadministrator leicht machen würde. Wir empfehlen daher jeder Datenbank einen eigenen SQL Server Login als Besitzer zuzuordnen und ihm darüber hinaus keine Berechtigungen zu erteilen. Etablieren Sie das als best practice in Ihrer Umgebung.

In diesem Abschnitt haben wir uns auf Wartungsjobs mit ihren Tücken und Fallen konzentriert. Sie sind letztlich das offensichtlichste Ziel von Angreifern. Die Regelmäßigkeit der Durchführung bietet eine gute Basis, sich mittels DDL Triggern sysadmin Rechte zu erschleichen, da Angreifer somit nicht lange warten müssen.

Szenario 2 - Angriff auf db_owner

Nicht nur sysadmins sind von Angriffen durch DDL Trigger betroffen. Als db_owner kann es Sie genauso leicht treffen. Die Gefahr besteht vor allem, wenn ein Mitglied mit der Rolle db_ddladmin existiert. Bei manchen Usern empfiehlt es sich, diese als db_ddladmin zu berechtigen, damit diese gespeicherte Prozeduren, Tabellen und Trigger erstellen und bearbeiten können. Die User sollten aber nach Möglichkeit nicht dazu berechtigt werden, DDL Trigger ändern zu dürfen.

Wir empfehlen Ihnen daher, sofern Sie einen db_ddladmin in Ihrer Umgebung haben, folgenden Befehl auszuführen:

DENY ALTER ANY DATABASE DDL TRIGGER TO [Domain\User]

Die noch bessere Alternative wäre, den User nicht direkt als db_ddladmin zu berechtigen. Fügen Sie stattdessen eine Datenbankrolle oder AD Gruppe zu db_ddladmin hinzu und lassen darüber die ausgewählten Benutzer entsprechende Berechtigungen erhalten. Verweigern Sie wie oben gezeigt dieser Rolle oder AD Gruppe die Berechtigung ALTER ANY DATABASE DDL TRIGGER. Damit müssen Sie nicht jedes Mal daran denken, die Berechtigung auf Userebene zu entfernen, sobald ein neuer Entwickler diese Rolle bekommen soll.

Dies ist umso wichtiger, wenn Sie DBA-Aufgaben sowohl auf Server- als auch auf Datenbankebene übernehmen. Vergeben Sie diese Berechtigungen also nur an vertrauenswürdige und verlässliche Kolleg:innen. Je umfangreicher die vergebenen Berechtigungen sind, desto attraktiver werden sie für Angreifer.

Das Szenario Cross-db-Angriffe behandeln wir zu einem späteren Zeitpunkt im Verbund mit dem Thema “Angriff über DML Trigger”.

Instandhaltungsaufträge

Wartungs-Jobs dienen mittlerweile als beliebtes Ziel für Angreifer. Der Hintergrund liegt an der regelmäßigen, wenn nicht sogar täglichen Durchführung der Jobs. Im vorherigen Kapitel sind wir auf die selbstständige Implementierung von Wartungs-Jobs und die Verwendung von EXECUTE AS USER = 'dbo' eingegangen.

Letztlich müssen wir das Rad bei Wartungs-Jobs nicht neu erfinden und so greifen viele auf die folgenden beiden Standard-Lösungen zurück

  1. Wartungspläne in SQL Server Management Studio
  2. Wartungslösung von Ola Hallengren

Wir starten mit der Wartungslösung von Ola Hallengren. Sie verfügt über eine Prozedur IndexOptimize die den Parameter @ExecuteAsUser akzeptiert. Darüber können Sie anfordern, dass die generierten Kommandos von einem angegebenen Benutzer ausgeführt werden sollen. Hierfür können neben dbo auch andere User verwendet werden. Die Idee dahinter ist, dass Sie einen User in jeder Datenbank haben, der zur db_ddladmin Gruppe hinzugefügt wurde. Damit können sie sich auch vor dem Hijacking der db_owner Rolle schützen. Damit ein bestimmter User in jeder neuen Datenbank angelegt wird, können sie diesen zur model Datenbank hinzufügen.

Die Standardeinstellung für @ExecuteAsUser ist NULL. Dies bedeutet im Umkehrschluss, dass hier kein Identitätswechsel vorliegt und Sie somit nicht gegen Permission Hijacking geschützt sind, bis sie diesen Default-Wert überschreiben. Beachten Sie, dass der Job (wie oben bereits thematisiert) bei @ExecuteAsUser='dbo' aufgrund einer Nichtübereinstimmung zwischen dbo und dem Eintrag in sys.databases fehlschlagen kann.

Mit Wartungsplänen in SSMS gehen Sie in der Regel ein “größeres” Risiko ein, da Sie keine Möglichkeit haben, gegen den Missbrauch von Berechtigungen vorzugehen. Behalten Sie jedoch im Hinterkopf, dass das Problem hauptsächlich bei Index- & Statistikwartungen auftritt, da diese DDL Trigger auslösen. BACKUP und DBCC CHECKDB lösen keine DDL Trigger aus. Somit ist ein entsprechender Schutz mit EXECUTE AS USER nicht notwendig. (Hinweis: Es gibt nach wie vor Szenarien, die noch nicht dahingehend untersucht wurden, ob sie DDL Trigger anstoßen)

Außerdem muss es erst User in der Datenbank geben, die berechtigt wurden, einen DDL Trigger zu erstellen, ohne gleichzeitig sysadmin zu sein. In den meisten Teams übernehmen die gleichen Personen die DBA-Tätigkeiten auf Server- als auch auf Datenbankebene. So lange sie also keine weiteren Personen der db_owner oder db_ddladmins hinzufügen, haben Sie diesbezüglich nichts zu befürchten.

Gibt es jedoch User, die die Rolle db_owner inne haben, ohne Mitglied von sysadmin zu sein und die genannten Möglichkeiten mit SSMS Wartungsplänen oder dem @ExecuteAsUser Parameter von Ola Hallengren kommen für Sie nicht in Frage, sollten Sie folgende Optionen berücksichtigen:

  1. Schreiben Sie Ihren eigenen Wartungsplan, der EXECUTE AS USER dort verwendet, wo es notwendig ist
  2. Nutzen Sie die Wartungslösung von Ola Hallengren zumindest hinsichtlich Index- & Statistikpflege, indem Sie @ExecuteAsUSer für IndexOptimize verwenden.
  3. Planen Sie separate Index- & Wartungsjobs für jede Datenbank, in der es db_owner gibt, die nicht gleichzeitig sysadmin sind. Dieser Job muss einem SQL Server Login zugeordnet sein, der in der Datenbank die db_owner Berechtigung hat, jedoch keine weiteren datenbankübergreifenden Berechtigungen hat.
  4. Teilen Sie Ihren lokalen Usern in den betroffenen Datenbanken mit, dass die Index- & Statistikwartungen eigenständig geplant werden müssen.
  5. Sperren Sie die DDL Trigger auf Datenbankebene mit einem DDL Trigger auf Serverebene. Damit ermöglichen Sie sich, die Wartungspläne in SSMS zu verwenden, ohne entsprechende Jobs planen zu müssen.
  6. Wenn Sie als DBA einen Entwickler oder Consultant als db_owner zum Troubleshooting oder Performance Tuning hinzugefügt haben, können Sie diese Person vielleicht nachträglich zu db_ddladmin herabstufen und anschließend das Recht ALTER ANY DATABASE DDL TRIGGER entziehen.
  7. Vertrauen Sie darauf, dass die fragwürdigen Personen ihre Berechtigungen nicht missbrauchen.

Letztlich müssen Sie entscheiden welche Variante am Besten und sinnvollsten für Ihre Umgebung ist, da beispielsweise Nummer 7 nicht auf hochsensiblen Systemen praktiziert werden sollte, auf Entwicklungs- oder Testservern aber eher kein Problem darstellt. Datenbank DDL Trigger mittels DDL Triggern auf Serverebene zu verbieten macht eigentlich nur Sinn, wenn Sie die volle Kontrolle darüber haben, von wo Datenbankbackups wiederhergestellt werden können. Haben Entwickler selbst die Möglichkeit Datenbanken wiederherzustellen, da sie der Besitzer sind oder der Server-Rolle dbcreator angehören, können auch darüber Datenbank DDL Trigger mit schadhaftem Code auf Ihren Server gelangen. Außerdem können DDL Trigger auch sehr hilfreich sein, wie beispielsweise bei Audits.

Sollten Sie den DDL Trigger auf Serverebene umsetzen wollen, können Sie diesen mit folgendem Befehl erstellen:

CREATE OR ALTER TRIGGER server_ddltri ON ALL SERVER 
                FOR CREATE_TRIGGER, ALTER_TRIGGER AS
   DECLARE @objecttype varchar(20),
           @eventdata xml = eventdata()
   SELECT @objecttype = E.e.value('(./text())[1]', 'nvarchar(MAX)')
   FROM   @eventdata.nodes('/EVENT_INSTANCE/TargetObjectType') AS E(e)
   IF upper(@objecttype) = 'DATABASE'
   BEGIN
      ROLLBACK TRANSACTION
      ; THROW 50000, 'Database level DDL triggers not permitted on this server', 1
   END

Die Bereitstellung des DDL Triggers auf Serverebene allein reicht nicht aus. Sie müssen alle vorhandenen Datenbanken auf die DDL Trigger prüfen, da diese durch den neuen Trigger nicht gelöscht bzw. an der Ausführung gehindert werden. Verwenden Sie dafür folgenden Befehl:

CREATE TABLE #ddltridbs (db sysname NOT NULL PRIMARY KEY)
DECLARE @sql nvarchar(MAX)
SELECT @sql = 
   (SELECT 'IF EXISTS (SELECT *
                       FROM   ' + quotename(name) + '.sys.triggers
                       WHERE parent_class_desc = ''DATABASE'')
              INSERT #ddltridbs (db) VALUES(' + quotename(name, '''') + ')' +
              char(13) + char(10)
    FROM   sys.databases
    WHERE  state_desc = 'ONLINE'
      AND  database_id > 4
    FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)')
PRINT @sql
EXEC (@sql)
SELECT * FROM #ddltridbs

Diesen Befehl sollten Sie ebenfalls verwenden, wenn Sie ein älteres Backup oder eines von außerhalb wiederherstellen möchten.

Möchten Sie diesen Weg einschlagen und dennoch das Hinzufügen von nützlichen DDL Triggern erlauben, können Sie den nachfolgenden Befehl zu Ihrem Trigger auf Serverebene hinzufügen:

IF is_srvrolemember('sysadmin') = 1 RETURN

Somit sind Administratoren nach wie vor in der Lage, die Trigger zu erstellen. Weiterhin sollten Sie den DDL Trigger immer EXECUTE AS SELF hinzufügen:

CREATE OR ALTER TRIGGER ddltri ON DATABASE WITH EXECUTE AS SELF
        FOR DDL_DATABASE_LEVEL_EVENTS AS

SELF wird hierbei verwendet, da OWNER für DDL Trigger nicht erlaubt sind. Dadurch wird sichergestellt, dass der Trigger in einem nicht vertrauenswürdigen Sicherheitskontext ausgeführt wird und keine Aktionen außerhalb der Datenbank ausgeführt werden können. Dazu gehört beispielsweise Code, der vom DDL Trigger ausgeführt wird. Sie sollten den Datenbank Usern mitteilen, dass sie zukünftig original_login() verwenden müssen, um die Umgebung ordnungsgemäß zu überwachen, da SYSTEM_USER nur den imitierten Kontext zurück liefert.

DML Trigger##

Wenn ein DDL Trigger für den Missbrauch von Berechtigungen verwendet werden kann, kann dies auch ein regulärer Trigger für INSERT, UPDATE oder DELETE.

Szenario 1 - Angriff auf sysadmin
Als erfahrener DBA wurden sie vermutlich schon das ein oder andere Mal gefragt, eine Datenbereinigung durchzuführen. Nicht etwa weil es in Ihrem Aufgabenbereich liegen würde, sondern weil dazu auch die ein oder andere kompliziertere SQL Anweisung zur Aktualisierung von Tabellen innerhalb der Datenbank nötig ist und Sie das letztendlich besser können als der Entwickler. Oder zumindest behauptet dieser es. Denn möglicherweise hat diese Person auch einen DML Trigger zu einer Tabelle hinzugefügt der prüft, ob ein User zu der sysadmin Gruppe gehört, nur um dann schadhaften Code auszuführen.

Zum Schutz vor DDL Triggern haben wir in den vorherigen Abschnitten bereits erklärt, wie der Befehl

EXECUTE AS USER = 'dbo'

verwendet werden soll. Hier geht es jedoch vordergründig um den Schutz der sysadmin Berechtigungen, die db_owner Rolle werden wir nun im weiteren Verlauf des Artikels beleuchten.

Sollte eine Aktualisierung von Datenbanken auf Ihrer To-Do-Liste stehen und entsprechende Daten von verknüpften Servern müssen abgerufen werden, empfehlen wir Ihnen diese Daten in temporäre Tabellen zu speichern, bevor Sie Ihre Berechtigungen herabstufen und die tatsächliche Aktualisierung mit EXECUTE AS USER + REVERT durchführen. Solange Sie nur Daten selektieren, gibt es keine Angriffsfläche.

Für das genannte Szenario sollten Sie in der Tabelle sys.triggers nachsehen, welche Trigger in den Tabellen vorhanden sind, die Sie aktualisieren möchten. Nicht nur um bösartigen Code zu identifizieren, sondern generell um zu erfahren, ob Trigger vorhanden sind und falls ja, welchen Zweck sie erfüllen. Trotzdem ersetzt diese Vorgehensweise nicht die Verwendung von EXECUTE AS USER.

Szenario 2 - Cross-DB Angriffe

In diesem Szenario sind Sie der Admin der Datenbank 1, die eine Vielzahl an sensiblen Daten enthält. Der Administrator der Datenbank 2 möchte nun einige Daten Ihrer Datenbank in seine Datenbank übernehmen und teilt Ihnen mit, dass er dafür eine eigene Tabelle erstellt hat, in die die Daten eingespeist werden können. Dafür wurde temporär ein User mit INSERT Berechtigungen für diese Tabelle erstellt. Die Gefahr besteht, dass es möglicherweise einen Trigger gibt, der dem Administrator der Datenbank 2 weitaus mehr Berechtigungen zuteilt, als vorgesehen sind. Er könnte als Nutzer zu Ihrer Datenbank hinzugefügt werden oder mehr Daten kopieren, als die Sie ihm eigentlich geben wollen.

Wenn Ihnen die Recherche in sys.triggers auch keine Hilfe ist, könnte es möglicherweise daran liegen, dass der Admin der Datenbank 2 Ihre VIEW DEFINITION auf verschiedene Ebenen eingeschränkt hat und Ihnen daher keine Trigger aufgelistet werden. Weiterhin kann es sein, dass Sie eventuell den schädlichen Trigger entdecken, jedoch seinen Code nicht einsehen können/dürfen.

Eine andere Möglichkeit besteht darin, dass Ihnen der Datenbank 2 Admin für den Datentransfer die Kontrolle über ein Schema überträgt. In diesem dürfen Sie selbst die Tabellen zur Ablage der Daten erstellen. Was Sie mit Ihren Berechtigungen jedoch nicht herausfinden können ist, dass DDL Trigger existieren, die schädlichen Code ausführen.

Wie können Sie in diesem Fall also vorgehen? Auch hier lautet die Antwort wieder: EXECUTE AS USER. Sie haben jedoch keine Berechtigungen zur Impersonierung von dbo auf der Datenbank 2, also welchen User können Sie stattdessen verwenden?

Lösung: Sie geben sich als sich selbst aus. Angenommen ihr Login ist DOMAIN\AdminForDB_1. Extrahieren Sie alle benötigten Daten zunächst in temporäre Tabellen und nutzen anschließend folgenden Befehl zum Datentransfer in die Datenbank B:

USE B 
go 
EXECUTE AS USER = 'DOMAIN\AdminForDB_1'

Damit setzen Sie den Sicherheitskontext außer Kraft und kein Trigger kann Ihre Berechtigungen missbrauchen. Überprüfen Sie jedoch im Vorhinein in sys.databases, ob die Datenbank 2 als TRUSTWORTHY gekennzeichnet ist. Notwendig dafür sind Ihnen vorhandene Serverberechtigungen wie z.B. VIEW ANY DATABASE. Diese Berechtigung wird standardmäßig an alle User vergeben.

Szenario 3 - Angriff auf db_owner

Angriffe auf db_owner können über DML Trigger von Mitgliedern mit der Rolle db_ddladmin oder von Usern mit der ALTER Berechtigung auf dem dbo Schema oder anderen wichtigen Tabellen/Schemata durchgeführt werden. In den meisten Fällen erstellen sie einen Trigger für eine Tabelle und verleiten die Opfer dazu, verschiedene Operationen auf dieser Tabelle auszuführen.

Mit folgendem DDL Trigger können Sie sich vor Angriffen über DML Trigger schützen:

CREATE TRIGGER no_triggers_please ON DATABASE FOR 
     CREATE_TRIGGER, ALTER_TRIGGER, DROP_TRIGGER AS
   IF is_rolemember('db_owner') = 0
   BEGIN
      ROLLBACK TRANSACTION
      ; THROW 50000, 'Only db_owner can work with triggers in this database', 11
   END

Dieser Trigger ist vor allem hilfreich, wenn in der verwendeten Datenbank wenig bis gar keine Trigger im Regelbetrieb verwendet werden, oder sie Entwicklern das Arbeiten mit gespeicherten Prozeduren, nicht aber mit Triggern erlauben wollen. Sie sollten jedoch darauf achten, dass Mitglieder mit der Rolle db_ddladmin über keine ALTER ANY DATABASE DDL TRIGGER Rechte verfügen. Ansonsten könnten sie den DDL Trigger kurzzeitig deaktivieren, um anschließend einen DML Trigger mit bösartigem Code zu erstellen.

Grundsätzlich wollen wir von der Verwendung von Triggern nicht gänzlich abraten. Sie sind gerade hinsichtlich der Datenbankintegrität ein hilfreiches Tool für Entwickler. Sie könnten durchaus Entwickler haben, denen Sie dedizierte Rechte für das Entwickeln mit DDL (also z.B. Prozeduren, jedoch keine DDL Trigger!) und DML Triggern gestatten, aber Sie wollen nicht, dass diese Personen auch Benutzer oder Zertifikate erstellen. In diesem Fall ist db_ddladmin eine gute Wahl.

Angriff durch gespeicherte Prozeduren

In Anlehnung an das Szenario im vorherigen Kapitel stellt Ihnen die Person, die Sie um Hilfe bittet, eine gespeicherte Prozedur zur Verfügung, die Sie ausführen sollen. Eine Prozedur, die möglicherweise mit einem kleinen Zusatz versehen wurde, um Ihre Berechtigungen für Aktionen zu nutzen, die Sie nicht beabsichtigt haben.

Insgesamt hat dieser Angriff viel mit dem Angriff über DML-Trigger gemeinsam. Man könnte allerdings sagen, dass der Angriff über DML-Trigger hinterlistiger ist, weil man vielleicht gar keinen Trigger erwartet. Andererseits kann man, wenn man eine gespeicherte Prozedur zur Ausführung erhält, klar erkennen, dass man sich auf unbekanntes Terrain begibt, und es bedarf nur eines geringen Maßes an Paranoia, um auf die Idee zu kommen, die Prozedur zumindest zu überfliegen, um zu sehen, was sie vorhat. Sie werden jedoch feststellen, dass die Prozedur wiederum andere Prozeduren aufruft und kompliziertes dynamisches SQL aufbaut, bei dem einige Daten aus verschiedenen Tabellen entnommen werden. Letztendlich erlaubt es die Zeit vielleicht nicht, den gesamten Code zu analysieren.

Wie wir bereits mehrere Male angemerkt haben, sollten Sie auch in diesem Fall einen Identitätswechsel vornehmen. In Datenbank-übergreifenden Szenarien geben Sie sich als sich selbst aus. Als Sysadmin verwenden Sie dbo. Abhängig von der Situation in der Datenbank können Sie jedoch immer noch Opfer eines Angriffs auf db_owner werden.

Bei einem Angriff auf db_owner, könnte es sich um einen Angreifer mit CREATE PROCEDURE Rechten in der Datenbank oder mit ALTER Berechtigungen in einem zentralen Schema wie dbo handeln. Meist sind db_ddladmin User mit solchen Rechten ausgestattet. Überlegen Sie daher immer genau, ob es explizit Nutzer mit dieser Rolle geben muss bzw. sollte. Eruieren Sie, ob es Entwickler in Ihrem Haus gibt, die zwingend mit gespeicherten Prozeduren arbeiten müssen, aber keine Berechtigungen zum Bearbeiten und Schreiben von Tabellen, Triggern oder Views haben. So empfiehlt es sich ggf. ein separates Schema zu erstellen und dem Entwickler ALTER Berechtigung zu erteilen, so dass er dort arbeiten kann. Sie kontrollieren im Anschluss den entwickelten Code auf Unstimmigkeiten und übertragen ihn, sofern er Ihr Code Review übersteht, in das Schema, in welchem er sich schlussendlich befinden soll.

Oftmals haben Sie jedoch keine andere Wahl, als Entwicklern Berechtigungen im dbo Schema einzuräumen. Sonst würden Sie sowohl die Arbeit des Entwicklers, als auch Ihre eigene deutlich verkomplizieren. Seien Sie sich bewusst, dass in diesem Fall jede Prozedur in Ihrer Datenbank eine Bedrohung darstellen könnte. Auch die, die sie in der Vergangenheit selbst erstellt haben, zwischenzeitlich jedoch von einem Angreifer mit bösartigem Code modifiziert wurden. In diesem Fall können Sie keine Anwendungsprozeduren als “Sie selbst” ausführen. Stattdessen empfiehlt es sich einen User zu erstellen, der mit minimalen Rechten ausgestattet wird. Ein sogenannter sandboxuser.

CREATE USER sandboxuser WITHOUT LOGIN

Sie gewähren dem sandboxuser genau die Rechte, die ein typischer User bekommen würde. Das heißt, Sie fügen diesen User zu genau den Berechtigungsgruppen hinzu, denen andere Anwender (keine Entwickler!) ebenfalls angehören. Möchten Sie nun eine gespeicherte Prozedur ausführen, führen Sie mit EXECUTE AS USER einen Identitätswechsel zu diesem sandboxuser durch. Neben dem Schutz vor Hijacking bietet diese Vorgehensweise noch einen weiteren Vorteil: War sich der Entwickler nicht sicher, welche Berechtigungen normale Endanwender haben, können Sie hierüber feststellen, ob die Entwicklungsbestandteile über diese Berechtigungen hinausgehen und zusammen mit dem Entwickler erörtern, wie das Problem zu lösen ist. Beachten Sie jedoch, dass ein User ohne Login (sandboxuser) keine Berechtigungen außerhalb der Datenbank hat und der Zugriff auf verbundene Server oder andere Datenbanken fehlschlagen wird.

Als sysadmin können Sie dieses Problem umgehen, indem Sie einen dedizierten sandbox Login erstellen, der allein für den Identitätswechsel mit EXECUTE AS LOGIN verwendet wird. Dieser hätte dann datenbankübergreifende Rechte. Aber hier ist auch Vorsicht geboten. Befinden Sie sich in einer Umgebung mit vielen unabhängigen Datenbanken, könnten Sie leicht in einen Cross-DB Angriff geraten und sollten stattdessen für jede Datenbank einen eigenen sandbox Login erstellen. Wie bereits mehrere Male erwähnt, ist es wichtig, sysadmin- & dbo Rechte zu schützen.

Kehren wir nun zu der Situation zurück, in der Sie in der Sysadmin-Rolle sind und in einer Datenbank arbeiten sollen, in der Sie normalerweise nicht die DBA-Aufgaben auf Datenbankebene übernehmen, sondern diese einem lokalen Power-User obliegen. Darüber hinaus gibt es Benutzer, die Prozeduren und möglicherweise auch DML-Trigger erstellen können, ohne db_owner zu sein. Sie müssen Ihre Sysadmin-Berechtigungen vor Angriffen des Datenbank-DBAs schützen, und Sie müssen die db_owner-Berechtigungen vor Angriffen von Power-Usern mit niedrigeren Rechten in der Datenbank schützen. Wenn der Datenbank-DBA Ihnen einen Sandbox-Benutzer zur Verfügung stellt, für den Sie sich ausgeben können, würden Sie diesen natürlich verwenden. Und wenn der db_owner Ihnen mitteilt, dass es keine Benutzer mit erhöhten Rechten gibt, können Sie sich einfach als dbo ausgeben. Für die Bedrohungen gegen db_owner ist schließlich der DBA auf Datenbankebene verantwortlich. Andererseits haben Sie vielleicht ein mulmiges Gefühl, wer die Schuld bekommt, wenn etwas Schlimmes passiert, also gehen Sie lieber auf Nummer sicher. Hier sind die Schritte, die Sie in einem solchen Fall unternehmen würden:

  • Als erstes impersonieren sie dbo
  • Als dbo erstellen Sie einen sandboxuser ohne Login
  • Erteilen Sie dem User erforderliche Berechtigungen für Tabellen und Prozeduren
  • Impersonieren Sie den sandboxuser
  • Führen Sie die notwendigen Skripte aus
  • Kehren Sie zu dbo zurück
  • Löschen Sie den sandboxuser
  • Machen Sie den Identitätswechsel rückgängig

Schreiben Sie eigene gespeicherte Prozeduren

Bisher haben wir uns mit Szenarien beschäftigt, wie wir einen Identitätswechsel durchführen und uns damit selbst Berechtigungen entziehen, damit diese nicht missbraucht werden können. Doch was passiert, wenn mitten in der Aktion Ihre eigenen weitreichenderen Rechte wie z.B. BULK INSERT benötigt werden?

Eine Option ist es, nicht User mit entsprechenden Berechtigungen auszustatten, sondern direkt die Prozedur mit Rechten zu beladen. Der Vorteil hierbei ist, dass die Prozedur über die volle Kontrolle über vorhandene Berechtigungen verfügt. Um die Prozedur zu “berechtigen” müssen wir sie mittels Zertifikaten signieren. Als nächstes erstellen Sie einen Principal aus diesem Zertifikat und erteilen ihm die Berechtigungen, die Sie in der Prozedur einbetten möchten.
Handelt es sich bei der Berechtigung um eine Datenbankberechtigung, erstellen sie einen Datenbank-Principal, also einen User. Handelt es sich um eine Serverberechtigung, erstellen sie einen Server-Principal, d.h. einen Login. Diese Principals sind keine normalen Logins oder User, sondern ein spezieller Typ, der sich nicht wirklich anmelden kann. Sie existieren nur, um Berechtigungen und Zertifikate miteinander zu verbinden.
Bei Berechtigungen innerhalb einer Datenbank, muss das Zertifikat nur in dieser Datenbank vorhanden sein. Für Berechtigungen auf Serverebene wird das Zertifikat in der master Datenbank (damit Sie eine Anmeldung erstellen können, um die Berechtigungen zu erteilen) als auch in der Benutzerdatenbank (damit die Prozedur signiert werden kann) benötigt.
Wenn Sie eine Prozedur ausführen, die mit einem Zertifikat signiert wurde und die Signatur gültig ist, wird das Token für den Auftraggeber, das aus dem Zertifikat erstellt wurde, zu den Sicherheits-Tokensätzen für den aktuellen Prozess hinzugefügt. (Sie können diese Datensätze in sys.login_token und sys.user_token einsehen.) Ihre Berechtigungen setzen sich aus aller diesen Token gewährten Berechtigungen zusammen, so dass die dem Zertifikat gewährten Berechtigungen innerhalb der Prozedur in Kraft treten. Sobald die Prozedur beendet wird, wird das Token für den Zertifikatsinhaber entfernt.

Sehr interessant ist auch die Frage, unter welchen Bedingungen das Token für den Zertifikatsinhaber unter den Sicherheitstokens verbleibt:

Wenn Sie dynamisches SQL aufrufen, ist der Zertifikatsinhaber und die gewährten Berechtigungen noch vorhanden. Das Gleiche gilt, wenn Sie eine Systemprozedur aufrufen. Wenn Sie jedoch ein vom Benutzer geschriebenes Modul aufrufen - einen Trigger, eine gespeicherte Prozedur oder eine Funktion - wird das Token entfernt, es sei denn, dieses Modul ist ebenfalls mit dem Zertifikat signiert.
Der letzte Punkt ist im Zusammenhang mit diesem Artikel äußerst wichtig. Das bedeutet, dass Sie jede beliebige Berechtigung in Ihre gespeicherte Prozedur packen können, ohne sich Sorgen machen zu müssen, dass diese Berechtigung von einem Trigger beim Einfügen oder Aktualisieren von Daten gekapert wird. Das Gleiche gilt, wenn Ihre Prozedur eine andere gespeicherte Prozedur aufruft, die von jemand anderem geändert werden könnte. Diese Prozedur hat keinen Zugriff auf die Berechtigungen, die in Ihrer Prozedur enthalten sind.

Der db_owner Fall

Nun schauen wir uns die Situationen, die wir bereits beschrieben haben, einmal genauer an und starten mit der Rolle db_owner. Angenommen, die Anwendung muss berechtigt sein, einen Datenbankbenutzer mit ALTER ANY USER zu erstellen und diesen Benutzer zur Standardrolle für Anwendungsbenutzer hinzuzufügen. Platzieren Sie den Code zur Erstellung mit dynamischem SQL in einer gespeicherten Prozedur add_new_user. Die Anwendung prüft, ob der aufrufende User entsprechende Rechte besitzt, um Nutzer erstellen zu dürfen.

Erland Sommarskog hat bereits eine Lösung bereitgestellt, die den Prozess zur Signatur von Prozeduren weitestgehend automatisiert. Angelehnt an seine gespeicherte Prozedur GrantPermsToSP würde der Code mit dem Befehl add_new_user folgendermaßen aussehen:

DECLARE @perms Management.Permission_list
INSERT @perms (perm) VALUES('ALTER ANY USER',
                           ('ALTER ON ROLE::ApplicationUsers')
EXEC Management.GrantPermsToSP 'add_new_user', @perms, @debug = 1

Die Prozedur erstellt nun ein Zertifikat und den Benutzer mit Namen. Wird GrantPermsToSP für die gleiche Prozedur erneut ausgeführt, wird alles was zuvor erstellt wurde gelöscht und anschließend neu erzeugt.

Wenn Sie Ihre Prozedur add_new_user nun testen, sollten Sie sich als Sandboxuser (oder einen anderen User mit Anwender-Berechtigungen) ausgeben, um das gewünschte Verhalten zu überprüfen.

Der Sysadmin_Fall

Angenommen, Sie wurden gebeten, eine Aufgabe zur Datenmanipulation für eine Datenbank D zu schreiben, und die Aufgabe beinhaltet das Laden einer Datendatei mit BULK INSERT, was die Berechtigung ADMINISTER BULK OPERATIONS auf Serverebene erfordert. Diese Berechtigung wollen Sie den Benutzern in D nicht direkt erteilen, da dies ihnen erlauben würde, jede beliebige Datei auf dem Server zu laden, und das ist nicht zulässig. Daher beschließen Sie, eine gespeicherte Prozedur, load_data, zu schreiben und die Zertifikatsignierung zu verwenden, um die Bulk-Load-Berechtigung innerhalb der gespeicherten Prozedur zu verpacken.

Auch hierfür gibt es bereits ein Skript GrantPermsToSP_server von Erland Sommarskog, die diesen Task automatisieren kann. Dieses Skript hat einen Header Teil, der durch folgenden Code für unser Beispiel anzupassen ist. Der restliche Teil des Skripts bleibt unverändert.

USE master
go
-- Set up parameters: the procedure to sign and the database it belongs to.
DECLARE @database nvarchar(260) = 'D',
        @procname nvarchar(520) = 'dbo.load_data'

-- The permissions to grant through the certificate. Leave table empty
-- to only remove current permissions.
DECLARE @perms TABLE (perm nvarchar(400) NOT NULL PRIMARY KEY)
INSERT @perms VALUES
   ('ADMINISTER BULK OPERATIONS')

-- Run with debug or not?
DECLARE @debug bit = 1

Das Skript bildet die Namen des Zertifikats und des Logins aus den Namen der Datenbank und des Verfahrens. Wenn Sie das Skript erneut ausführen, werden alle vorhandenen Objekte, Zertifikate, Logins, Signaturen usw. gelöscht und neu erstellt.

Es gibt noch zwei weitere Dinge zu erwähnen:

  1. Das Skript entfernt den privaten Schlüssel des Zertifikats, um absolut sicher zu gehen, dass der lokale Hauptbenutzer das Zertifikat nicht zum Signieren von Prozeduren verwenden kann. (Der private Schlüssel muss vorhanden sein, wenn Sie signieren, aber nur der öffentliche Schlüssel wird benötigt, um die Signatur zu validieren.)
  2. Wenn die Datenbank Teil einer Verfügbarkeitsgruppe ist, kopiert das Skript das Zertifikat, die Anmeldung und die Berechtigungen vom Master auf alle Knoten in der Verfügbarkeitsgruppe.

Auch wenn die Idee ist, dass die Benutzer in D diese Prozedur selbst ausführen sollen, müssen Sie testen, ob sie funktioniert. Natürlich können Sie load_data nicht als Sie selbst ausführen, und zwar aus zwei Gründen:

  1. Ihre Berechtigungen könnten missbraucht werden.
  2. Sie würden nicht testen, ob Sie die Berechtigung korrekt verpackt haben.

Allerdings können Sie auch nicht Ihren Standardtrick anwenden, indem Sie dbo mit EXECUTE AS USER verkörpern, denn wenn Sie einen Datenbankbenutzer impersonieren, befinden Sie sich in der aktuellen Datenbank in einer Sandbox und es gelten keine Berechtigungen auf Serverebene, nicht einmal die, die durch eine Zertifikatsanmeldung hinzugefügt wurden. Sie müssen einen Sandbox-Login erstellen, den Sie als Benutzer zur Datenbank D hinzufügen (vergessen Sie nicht, vorher EXECUTE AS USER auszuführen!) und diesem Benutzer die für die Ausführung der Prozedur erforderlichen Berechtigungen auf Datenbankebene erteilen. Mit EXCUTE AS LOGIN geben Sie sich als dieser Benutzer aus. Anstatt einen Sandbox-Login zu erstellen, könnten Sie auch einfach einen der Benutzer in D impersonieren, vorzugsweise mit dessen Zustimmung.

Der Cross-DB Fall

Angenommen ein Administrator in Datenbank B muss regelmäßig Daten aus Ihrer Datenbank A abrufen. Sie möchten eine gespeicherte Prozedur schreiben, die der B-Administrator ohne Ihre Anwesenheit ausführen kann - und dennoch die volle Kontrolle darüber hat, welche Daten extrahiert werden. Kann dies mit der Zertifikatsignatur erreicht werden? Auf jeden Fall, aber wenn Sie nicht auch die Rolle des db_owner in B innehaben, ist es etwas schwierig, und in der Praxis müssen Sie mit dem B-Administrator zusammenarbeiten, selbst wenn Sie dieser Person nicht vertrauen.

Um dies zu verstehen, schauen wir uns zunächst an, wie Sie vorgehen würden, wenn Sie zufällig db_owner Rechte in beiden Datenbanken hätten. In diesem Fall unterscheidet sich die Vorgehensweise nicht allzu sehr von der Verpackung von Berechtigungen auf Serverebene. Sie können auch hier mit GrantPermsToSP_server.sql arbeiten, da die Schritte analog sind:

  1. Erstellen Sie ein Zertifikat in der Datenbank B
  2. Signieren Sie die Prozedur in der Datenbank B
  3. Entfernen Sie den private key
  4. Exportieren Sie das Zertifikat in die Datenbank A
  5. Erstellen Sie einen Benutzer in der Datenbank A für das Zertifikat (keinen Login)
  6. Gewähren Sie dem Benutzer die erforderlichen Rechte

Um dies zu testen, machen Sie es wie im vorigen Abschnitt: Sie richten einen Sandbox-Login ein, den Sie als Benutzer in B hinzufügen - aber nicht in Datenbank A! Sie sehen, dass das Zertifikat nicht nur die SELECT-Berechtigungen bietet, die Sie ihm zugestehen, sondern auch CONNECT-Berechtigungen für die Datenbank A, so dass der Benutzer, der die Prozedur ausführt, keinen eigenen Zugriff auf die Datenbank haben muss.

Betrachten wir nun den Fall, dass Sie nur der Administrator von A sind. Dies stellt Sie vor eine Reihe von Herausforderungen:

  1. Sie sind vielleicht nicht dazu berechtigt, gespeicherte Prozeduren in der Datenbank 2 zu erstellen.
  2. Sie haben vermutlich erst recht keine Berechtigungen, Zertifikate zu erstellen und Prozeduren in der Datenbank 2 zu signieren.
  3. Wie sollten sie diese Prozedur testen?
  4. Würde der Administrator in Datenbank B Ihnen vertrauen und Ihre vorbereitete Prozedur ausführen? Schließlich sollte er sich die gleichen Fragen stellen wie Sie auch.

Für einige dieser Probleme gibt es Abhilfemaßnahmen. Für die ersten beiden Probleme besteht eine Lösung darin, dass Sie die Prozedur in einer Datenbank, über die Sie die Kontrolle haben, zusammen mit einem Zertifikat erstellen und die Prozedur signieren. Sie können dann ein Exportskript erstellen, das Sie an den B-Administrator senden. Dieses Skript würde die gespeicherte Prozedur, die Erstellung des öffentlichen Schlüssels des Zertifikats und eine ADD SIGNATURE-Anweisung mit der Signatur aus Ihrer Quelldatenbank enthalten.

Was den letzten Punkt betrifft, so muss der Administrator von Datenbank B Ihre Prozedur überprüfen, um sicherzustellen, dass die einzigen Operationen, die Sie in Ihrer eigenen Datenbank durchführen, einfache SELECT-Anweisungen sind, die keine Berechtigungen aushebeln können. Würde er eine DML-Operation oder einen Aufruf einer gespeicherten Prozedur sehen, wird er sich weigern, Ihre Prozedur auszuführen, da ein Trigger oder eine verschachtelte gespeicherte Prozedur seine Berechtigungen in Datenbank B aushebeln könnte.

Für Schritt 3 haben wir jedoch keine gute Lösung parat. Sie können Ihre Prozedur nicht in Datenbank B testen, indem Sie sie selbst ausführen - Ihre Berechtigungen könnten missbraucht werden. Sie können EXECUTE AS USER nicht verwenden, da Sie damit in den Kontext von Datenbank B gelangen und der Zugriff auf Ihre eigene Datenbank trotz des Zertifikats fehlschlägt. Höchstwahrscheinlich werden Sie auf irgendeine Weise mit dem Administrator von B zusammenarbeiten müssen.

Agent Jobs

Erstellen Sie als sysadmin einen Auftrag im SQL Server Agent, wird der Auftrag standardmäßig im Kontext des Dienstkontos für SQL Server Agent ausgeführt. Nehmen wir mal an, dass Sie einen Job erstellen, der eine gespeicherte Prozedur in einer Datenbank ausführt, in der es Benutzer gibt, die die Berechtigung haben, diese Prozedur zu ändern, aber nicht sysadmin sind. Wie wir mittlerweile wissen, ist dies ein enormes Risiko für einen Datenmissbrauch.
In diesem Kapitel zeigen wir Ihnen, welche Alternativen es gibt. Zunächst möchten wir einige Grundsätze zu Agent-Jobs aufstellen, die gespeicherte Prozeduren ausführen, die Anwendungsaufgaben in einer Benutzerdatenbank durchführen (hier geht es also nicht wirklich um Index- und Statistikpflege):

  1. Sie sollten mit demselben Berechtigungssatz eines typischen Datenbankusers ausgeführt werden.
  2. Führt die Prozedur Aktionen aus, die über die zuvor festgelegten Berechtigungen hinausgehen, sollten diese in eine entsprechende Prozedur mit Zertifikatssignatur inkludiert werden.
  3. Jobs sollten nicht im Besitz von Personen sein, die das Unternehmen verlassen und aus dem Active Directory herausfallen könnten. Der Job Besitzer sollte ein abstrakter User oder eine dedizierte Anwendungs-ID sein.
  4. Vermeiden Sie es, Nicht-sysadmin-Benutzer zu einer der SQLAgent Rollen in msdb hinzuzufügen.
  5. Als Alternative zu Agent Jobs gibt es beispielsweise noch den Windows Taskplaner, der standardmäßig auf dem Anwendungsserver verwendet wird.

Sie haben 3 Möglichkeiten einen Agentenjob sicherheitstechnisch zu steuern.

→ Verwenden Sie einen Parameter, der sich auf der Erweitert Seite des Auftragsschrittdialogs befindet. Klicken Sie auf “Als Benutzer ausführen”. Hier empfiehlt es sich, einen sandboxuser ohne Login zu verwenden. Diese Variante ist allerdings nicht für alle Jobs praktikabel.

→ Wenn Sie den Jobbesitzer in einen Nicht-Sysadmin-Benutzer ändern, gibt der SQL Agent ein EXECUTE AS LOGIN für diesen Login aus. Da es sich hierbei um Impersonation auf Serverebene handelt, bedeutet dies, dass es innerhalb der Instanz keine Einschränkungen gibt, so dass der Job auf andere Datenbanken zugreifen und in Prozeduren verpackte Berechtigungen auf Serverebene verwenden kann. Allerdings wird der Impersonation außerhalb des SQL Servers nicht vertraut, was bedeutet, dass der Auftrag nicht auf verknüpfte Server mit Selbstzuordnung zugreifen kann. Verknüpfte Server mit expliziter Benutzerzuordnung funktionieren jedoch.
Wenn Sie diese Option verwenden, seien Sie sich dessen bewusst, dass die Rolle SQLAgentUserRole dann in die msdb Datenbank hinzugefügt wird.

→ Verwenden Sie einen Proxy und führen Sie einen Jobstep als CmdExec aus. Es ist etwas mehr Arbeit, und es kann sein, dass Sie mehr Leute involvieren müssen. Aber wenn diese Lösung erstmal lauffähig ist, werden Sie sich wahrscheinlich für diesen Standard entscheiden. Folgen Sie hierfür diesen Schritten:

  • Der AD Admin erstellt einen Windows-Login und fügen diesen der AD Gruppe hinzu, die für die Anwendung benötigt wird
  • Erstellen Sie folgenden Login in SQL Server:
CREATE CREDENTIAL MadafaTest WITH IDENTITY = 'DOMAIN\ApplLogin', SECRET='password'
  • “MadafaTest” ist in diesem Fall ein selbst gewählter Name. Verwenden Sie für die Anmeldung einen eigenen
  • Gehen Sie in den Knoten für den SQL Server Agent
  • Navigieren Sie anschließend zu Proxies
  • Wählen Sie das Betriebssystem CmdExec und klicken auf neu/new
  • Geben Sie den Proxy Namen und die Anmeldeinformationen ein
  • Legen Sie einen sa als Job Owner fest
  • Bei der Anlage des Jobsteps wählen Sie als Typ “Operating System (CmdExec)” und vom “Run As” dropdown Menü den zuvor erstellten Proxy aus
  • Der Inhalt des Job Steps würde in etwa so aussehen
SQLCMD -I -b -S $(ESCAPE_DQUOTE(SRVR)) -d dbname -Q"EXEC somesp"

Der SQL Server Agent ruft die Anmeldeinformationen vom SQL Server ab und führt eine Anmeldung bei Windows mit dem Benutzernamen und dem Passwort durch, die mit den Anmeldeinformationen gespeichert wurden. Das bedeutet, dass alles wie eine Anmeldung aus der Anwendung heraus abläuft und daher der Zugriff auf verknüpfte Server ohne Einschränkungen funktioniert.

Angriffe über Bereitstellungsskripte

Bisher hatten wir immer einen Angreifer, der bereits einen Zugriff auf die SQL Server Instanz besitzt. Nun werden wir einen Fall beleuchten, in der der Angreifer keinen Zugriff auf die SQL Server Instanzen besitzt und trotzdem Ihre Berechtigungen missbrauchen kann.

Szenario:

Als Systemadmin erhalten Sie vom Anwendungsteam ein Skript zur Bereitstellung oder Aktualisierung einer Anwendungsdatenbank. Wir gehen davon aus, dass Sie sowohl serverseitige, als auch datenbankseitige Berechtigungen zur Ausführung der Anwendung besitzen. Grundsätzlich besteht die Gefahr, dass ein bösartiger Entwickler einen schadhaften Code hätte einfließen lassen können, der beispielsweise einen Login für eine Sysadmin-Rolle erstellt. Um hier Abhilfe zu schaffen, sollten Sie zunächst definieren, was das Skript bzw. die Anwendung tun soll. Sind es nur Anwendungen innerhalb der Datenbank, oder bewegen wir uns hier auf Serverebene. Hier wird Ihnen das Entwicklungsteam Beihilfe leisten müssen.
Wenn Sie schon das Skript von den Entwicklern erhalten, ist es hilfreich, die dazu erstellte Dokumentation anzufordern, was genau die Transaktionen auf Datenbank- oder auf Serverebene bewirken sollen. Sollten Ihnen die Informationen darüber verwehrt bleiben, empfehlen wir das Bereitstellungsskript innerhalb eines Tests mit folgendem Befehl auszuführen:

EXECUTE AS USER = 'dbo' WITH NO REVERT

Die WITH NO REVERT Klausel macht es schier unmöglich, zur ursprünglichen Identität zurückzukehren. Warum Sie die Klausel benutzen sollten, hat den Hintergrund, dass ein böswilliger Entwickler möglicherweise mit einem REVERT in dem Skript gearbeitet hat. Dies würde dann einen Identitätswechsel begünstigen. Wenn Sie sich auf diese Weise als dbo ausgeben, werden alle Versuche auf Serverebene fehlschlagen und Sie werden in jedem Fall davon in Kenntnis gesetzt. Auffälligkeiten im Skript würden Sie als gewissenhafter DBA mit dem Anwendungsteam besprechen und dessen auf den Grund gehen.

Es kann allerdings auch sein, dass die Anwendung auf Dinge außerhalb der Datenbank zugreifen muss. Dies ist mindestens genauso umstritten wie ein Zugriff auf einen Linked Server. In diesem Fall reicht es nicht aus, sich als dbo auszugeben. Folgende Verteidigungsmaßnahmen sind hier von Vorteil:

  1. Codeüberprüfung
    Die Überprüfung des Codes wird Offensichtliches aufdecken. Beispielsweise ein CREATE LOGIN oder ALTER SERVER ROLE auf sysadmin. Allerdings sind die meisten Angreifer deutlich geschickter und machen keine solch offensichtlichen “Fehler”. Gerade bei vielen Zeilen an Code erfordert die Überprüfung einiges an Zeit.

  2. Automatischer Code-Scan
    Hierfür verwenden Sie ein Skript, dass durch den Code läuft und nach verdächtigen Zeilen oder Vorgängen sucht. Aber auch hier besteht die Gefahr, dass Angreifer deutlich schlauer sind und entsprechend schädlichen Code zu verstecken wissen.

  3. Ausführung durch einen Login mit eingeschränkten Berechtigungen
    Wenn Sie wissen, dass das Bereitstellungsskript lediglich auf einen verknüpften Server zugreifen muss, empfehlen wir wieder den Sandboxuser-Login. Arrangieren Sie den Zugriff des Sandbox-Logins auf dem verknüpften Server. Dasselbe gilt für eine einzelne Aktionen wie das Bulk-Loading von Daten aus einer Datei; Sie würden dem Sandbox-Login ADMINISTER BULK OPERATIONS oder die für die betreffende Aktion erforderliche Berechtigung erteilen. Wenn das Skript jedoch mehrere Aktionen auf Serverebene (oder in msdb) durchführt, kann es einige Zeit dauern, bis Sie die genaue Menge der benötigten Berechtigungen herausgefunden haben.

  4. Auditierung
    Vermutlich die beste Option, vorausgesetzt Sie haben eine gute Überwachung auf Serverebene eingerichtet. Sobald das Bereitstellungsskript ausgeführt wurde untersuchen Sie das Überwachungsprotokoll auf Auffälligkeiten.

Bislang haben wir uns auf Angriffe auf Serverebene konzentriert. Das bedeutet nicht, dass es nicht auch Angriffe auf Datenbankebene geben kann. Der Angreifer kann bereits Zugang zum Server haben, hat aber das Deployment-Skript als beste Option für seine Ambitionen in der Datenbank auserkoren. Da das Deployment-Skript für seine legitimen Aktionen möglicherweise die gleichen Berechtigung benötigt, wie für die ungewollten, ist es schwierig, sich mit einem Sandbox-Benutzer zu schützen. Wenn Sie allerdings der Meinung sind, dass das Skript nur Tabellen und gespeicherte Prozeduren erstellen/ändern, aber keine sicherheitsrelevanten Aktionen durchführen soll, könnten Sie einen Sandbox-Benutzer verwenden, der Mitglied von db_ddladmin, aber nicht von db_owner ist, so dass das Skript nicht plötzlich einen Benutzer zum db_owner befördern kann. Code-Review und Auditing sind möglicherweise Ihre einzigen Optionen. Und Auditing wird auch viel schwieriger sein als auf Serverebene, da das Deployment-Skript so viele Dinge ausführen kann, dass eine bösartige Aktion leicht durchschlüpfen könnte.

In den obigen Ausführungen sind wir davon ausgegangen, dass Sie ein reines SQL-Skript haben. Sie können aber auch ein Deployment-Paket in Form eines DACPAC oder einer MSI-Installation erhalten. Dies kann schwieriger zu handhaben sein. Ein wirklich sicherheitsbewusster DBA könnte versuchen, den SQL-Code auf die eine oder andere Weise zu extrahieren, aber bei weitem nicht alle haben die Zeit oder die Fähigkeiten, dies zu tun. Wenn Sie das Installationspaket mit einer Anmeldung mit eingeschränkten Rechten ausführen können, sollten Sie das auf jeden Fall tun. Ansonsten ist das anschließende Auditing vielleicht die einzige Option.

Ein weiterer Schritt in diese Richtung ist eine Deployment Pipeline, bei der das Einchecken in den Produktionszweig zu einer Installation in der Produktion führt. Wie Sie jetzt vielleicht verstehen, eröffnet dies einem böswilligen Entwickler die Möglichkeit, schlechten Code einzuschleusen. Der Sinn für diese Art von Pipeline für ein Test- oder QA-System ist nachvollziehbar. Bei einem kritischen System sollte jedoch hinterfragt werden, ob dies aus der Sicht der Sicherheit als auch der Stabilität tragbar ist. Wenn Sie sich in dieser Situation befinden, können Sie versuchen, die Pipeline mit eingeschränkten Rechten auszuführen, nicht stärker als db_ddladmin und generell ALTER ANY DATABASE DDL TRIGGER verweigern. Alles, was darüber hinausgeht, sollte einen manuellen Eingriff erfordern.

Angriffe über die Anwendung

Sie sind Systemadministrator, aber Sie sind auch Benutzer einiger Anwendungen. Das Zeiterfassungssystem, ein Bestellsystem, was immer Sie wollen. Sie melden sich bei diesen Anwendungen mit der Windows-Authentifizierung an. Was Sie jedoch nicht wissen, ist, dass ein Entwickler Code eingeschleust hat, um zu prüfen, ob der aktuelle Benutzer sysadmin ist, und in diesem Fall führt die Anwendung bösartigen Code aus. Vielleicht macht der Code den Entwickler zum Systemadministrator. Vielleicht stiehlt oder manipuliert er Daten.

Dabei kann es sich um Code in gespeicherten Prozeduren handeln, aber auch um Code, der direkt vom Client übermittelt wird und den Sie nicht kontrollieren oder überprüfen können. Der Angriff richtet sich auch möglicherweise nicht gegen den Server, auf dem sich das Zeiterfassungssystem befindet. Nein, der Entwickler weiß, dass Sie auch der DBA für das sehr sensible HR-System oder andere kritische Systeme sind und stellt eine Verbindung in diese Richtung her.

Welche Möglichkeiten des Schutzes haben Sie hier? Auf eigene Faust können Sie nicht viel tun. Da Sie über eine separate Anwendung agieren, steht Ihnen EXECUTE AS USER nicht zur Verfügung. Aber mit guten Standards in Ihrer Organisation kann dies vermieden werden. Sie können in einer Umgebung arbeiten, in der sie zwei Windows-Konten haben. Das eine ist ein normales Konto, mit dem Sie sich von Ihrem Laptop aus bei Windows anmelden. Dann benutzten Sie eine Remote Desktop Verbindung, bei der Sie sich mit Ihrem anderen Windows-Konto anmelden, um auf einen Jump-Server zu gelangen, von dem aus Sie auf eine sensible SQL-Server-Instanz zugreifen. Für Ihre täglichen Aufgaben als Angestellter verwenden Sie ein Konto ohne besondere Rechte in Datenbanken und für Ihre qualifizierte Arbeit als DBA verwenden Sie ein anderes Konto, das aber nicht für Mail und andere Aktivitäten geeignet ist. Dieses Konto ist nur für die Verwaltung von SQL Servern gedacht. Anstatt sich über einen Jump-Server zu verbinden, genügt es, SSMS mit dem Befehl RUNAS für das qualifizierte Konto zu starten (sofern die Netzwerktopologie dies zulässt).

Man muss kein Systemadministrator sein, um Opfer eines Angriffs über eine Anwendung zu werden. Wenn es nur darum geht, Daten zu stehlen, kann ein Entwickler des weniger sensiblen Systems B prüfen, ob ein Benutzer auch Zugang zum hochsensiblen System A hat, und sich in diesem Fall unbemerkt bei diesem System anmelden und Daten in eine Datei auf dem Anwendungsserver für B extrahieren (zu dem der Entwickler möglicherweise Zugang hat). Es versteht sich von selbst, dass es keine gute Idee wäre, die Datenbanken für A und B auf derselben SQL Server-Instanz zu haben. Aber wie bereits erwähnt, könnte die Anwendung einfach eine Verbindung zum anderen Server herstellen. Kann dies verhindert werden? Ja, zumindest solange es einen Anwendungsserver für das sensible System gibt und das Netzwerk so segmentiert werden kann, dass nur der Anwendungsserver und die Jumpserver für das DBA-Team eine Verbindung zur Instanz für A herstellen können.

Fazit

In diesem Artikel haben wir uns einige Gefahrensituationen angesehen, denen Sie in SQL Server begegnen können. Genauer gesagt, wie Benutzer in Ihrem Arbeitsumfeld versuchen könnten, Ihre Berechtigungen zu missbrauchen und Sie dazu zu bringen, Code auszuführen, den Sie nicht ausführen sollten.
Hier eine Zusammenfassung der wichtigsten Verteidigungsmaßnahmen, die wir uns angesehen haben:

  • Verwenden Sie EXECUTE AS USER , um alle Ihre Berechtigungen außerhalb der aktuellen Datenbank temporär zu entfernen.
  • Verweigern Sie Mitgliedern von db_ddladmin die Berechtigung ALTER ANY DATABASE DDL TRIGGER .
  • Erstellen Sie einen Sandbox-Benutzer oder einen Sandbox-Login, der über keine Berechtigungen verfügt, die über die für die Aufgabe erforderlichen hinausgehen. Normalerweise sollte dieser Berechtigungssatz mit dem eines normalen Anwendungsbenutzers übereinstimmen.
  • Verwenden Sie die Zertifikatsignierung, wenn eine gespeicherte Prozedur eine Aktion ausführen muss, für die Berechtigungen erforderlich sind, die über das hinausgehen, was einem normalen Anwendungsbenutzer angemessen gewährt werden kann.
  • Agent-Jobs für Anwendungen sollten mit den gleichen Berechtigungen ausgeführt werden wie ein normaler Anwendungsbenutzer.
  • Verwenden Sie unterschiedliche Windows-Logins einerseits für Ihre eigentlichen DBA-Aufgaben und andererseits für E-Mail Clients, Zeiterfassungssysteme usw.
  • Stellen Sie sicher, dass sensible Datenbanken nicht von Anwendungsservern anderer Systeme erreicht werden können.

Sollten Sie weitere Fragen zu diesem Thema haben, stehen wir Ihnen gerne mit Rat und Tat als Experten zur Verfügung. Kontaktieren Sie uns gerne über das Kontaktformular für ein unverbindliches Beratungsgespräch.