Tag: "dbcc"
Maximum number of rows per data page and minimal record size (SQL Server storage internals)
Oct 29th
Maximale Anzahl Zeilen je Datenseite und minimale Datensatzgröße
(DE) Wie viele Zeilen passen eigentlich maximal auf eine Datenseite? – wenn die Datensätze/Records so klein wie möglich sind. Zunächst, Part 1, was ist der kleinstmögliche Datensatz in SQL Server? Um das zu beurteilen, ist es gut, die Datentypen sowie die Struktur eines Datensatzes genau zu kennen. Der nächste Kandidat sind Datentypen, die laut der Liste in Books Online nur 1 byte Speicherplatz benötigen. Das wären folgende: |
(EN) How many rows maximally do actually fit on a data page? – if the data sets/records are as small as possible. First of all, part 1, what is the smallest possible data set in SQL Server? In order to assess this, it is commendable to know exactly the data types as well as the structure of a data set. One could be tempted to assume that a column of the data type bit is the smallest possible data set. The experienced SQL people will know that a bit alone always also requires at least 1 byte in one record – only with several columns of this data type, the place-saving aspect comes into play (bit (Transact-SQL)). The next candidate are data types which according to the list in Books Online use only 1 byte of storage. These would be the following: |
- bit
- char(1)
- binary(1)
- tinyint
Tatsächlich benötigen in diesem besonderen Fall, einer einzigen Spalte je Datensatz, auch Records mit Nettowert von 2 Bytes, 9 Bytes auf der Datenseite: |
As a matter of fact, in this particular case of a single column per record, also records with a net value of 2 bytes use 9 bytes on the data page: |
- char(2)
- nchar(1)
- binary(2)
- smallint
Wie kommt das? Theoretisch würden für einen Datensatz 8 Bytes ausreichen: 4 Bytes: Datensatzkopf (Typ + Zeiger auf Null-Bitmap) Dazu kommt noch der 2 Bytes große Zeiger im Page-Offset, was dann 10 Bytes ergeben würde. Woran das liegt, sehen wir uns an. |
How come? Theoretically, 8 bytes would be sufficient for a data set: 4 bytes: data set head (type + pointer to NULL-bitmap) Add to that the 2-bytes-pointer in the page offset, which would then result in 10 bytes. We will now look at the reason for this. |
Im Folgenden definiere ich 2 Tabellen mit jeweils einer bzw. 2 Spalten von Datentyp tinyint, der ja bekanntlich einen Byte benötigt: |
In the following, I am defining 2 tables with one and 2 columns each of the data type tinyint, which is known to use 1 byte: |
CREATE TABLE T1col
(col1 tinyint null)
GO
CREATE TABLE T2col
(col1 tinyint null, col2 tinyint null)
Danach füge ich zuerst Datenätze mit dem Wert „255“ bzw „255, 255“ ein, und danach einige mit Wert „NULL“. Mit DBCC Page kann man sich den Header der Datenseiten beider Tabellen ansehen, und findet eine kleine Überraschung (Ergebnis reduziert): |
Next, I am inserting, first, data set of the value “255” or “255, 255” and then a few of the value “NULL.” With the DBCC page, one can look at the header of the data pages of both tables, and one will find a small surprise (reduced result). |
Obwohl die Größe der Daten fixer Länge (pminlen) mit 5 bzw. 6 unterschiedlich angegeben wird, ist der freie Speicherplatz auf der Seite (m_freeCnt) identisch! (rot) Der Datensatz belegt in beiden Fällen jedoch 9 Bytes im Page-body (blau). So sieht die Tabelle, bestehend aus einer Spalte, mit einigen Datensätzen gefüllt, auf der Festplatte aus: |
Even though the size of the fixed-length data (pminlen) is specified differently, with 5 and 6 respectively, the free storage on the page (m_freeCnt) is identical! (red) The record, however, uses in both cases 9 bytes in the page body. (blue) This is what the table, consisting of one column, filled up with a few records, looks like on the hard drive: |
Man sieht, dass 9 Bytes belegt sind, jedoch nur, da nach der NULL Bitmap noch ein Byte jedem Datensatz anhängig ist (gelb markiert). Hier die Tabelle mit 2 Spalten: |
One can see that 9 bytes are used, but only because after the NULL bitmap, one extra byte is attached to each data set (marked in yellow). Below, see the table with 2 columns: |
Auch hier also 9 Bytes, mit dem Unterschied, wie das letzte Byte verwendet wird. Noch etwas deutlicher wird es im Binärformat. Das ist die Tabelle mit 2 Spalten – auch diese benötigt 9 Bytes, und man sieht unten, wie die NULL Bitmap zu ihrem Namen kommt: |
Here, too, it is 9 bytes, with the difference being how the last byte is used. It becomes a bit clearer in the binary format. |
Dieser eine Byte, der für mich keinen klaren Nutzen hat, führt also zu dem Ergebnis, das beide Tabellen letztlich 9 Bytes je Record auf der Festplatte benötigen. |
This one byte, which to me does not have any clear purpose, is what leads to the result that both tables ultimately use 9 bytes per record on the hard drive. |
Die minimale Datensatzgröße ist daher in der Tat 9 Bytes. Der zweitgrößte Datensatz ist jedoch auch 9 Bytes. :-D Dabei darf beliebig gewählt werden zwischen 2 Spalten à 1 Byte oder 1 Spalte à 2 Bytes :-) |
The minimal record size is thus in fact 9 bytes. The second biggest record, however, is also 9 bytes. :-D Here, one may freely choose between 2 columns à 1 byte or 1 column à 2 bytes :-). |
- bit
- char(1)
- binary(1)
- tinyint
- char(2)
- nchar(1)
- binary(2)
- smallint
Kommen wir nun zu Part 2: Wie viele Datensätze passen maximal auf eine Datenseite, wenn wir jetzt wissen, dass jeder Datensatz mindestens 9 Bytes + Offset benötigt? |
Let us now look at Part 2: How many records fit maximally on a data page if we now know that every data set requires a minimum of 9 bytes + offset? |
Wir testen mit einer Tabelle, bestehend aus einer Spalte mit Datentyp tinyint – wohlwissend, dass es dasselbe Ergebnis bringen würde, wenn wir smallint oder etwas anderes aus der Liste oben nehmen würden. |
We are testing with one table of one column with datatype tinyint – knowing full well that it would have the same outcome if we were to take smallint or something else from the list above. |
CREATE TABLE T3_Tinyint
(col tinyint NOT NULL)
GO
…Insert 700 Rows…
Sehen wir uns an, wie voll die Datenseite geworden ist, und welche Page_ID diese hat: |
Then, we will check again how full the data page has become, and which Page_ID it has: |
Ausgehend davon, dass eine Datenseite 8192 Bytes groß ist und abzüglich Header 8096 Bytes für Daten zur Verfügung stehen, bedeutet ein Füllgrad von ~95,107%, das noch gut ~396 Bytes zur Verfügung stehen. Durch 11 ergibt das 36. - 36 Datensätze haben also noch Platz! Was sagt DBCC Page dazu? |
Based on the fact that a data page is 8192 bytes in size and that, less the header, 8096 bytes are available for data, a fill degree of ~95,107% means that some ~396 are still available. Divided by 11 this equals 36 – there is still room for 36 records! What does DBCC Page have to say to this? |
Auch hier: 396 Bytes frei – na dann war unsere Überschlagsrechnung gar nicht so schlecht. :-) Das heißt rein rechnerisch müssten weitere 36 Datensätze auf die Seite passen. |
396 bytes free – well, then, our back-of-the-envelope calculation wasn’t so bad at all. :-) That is to say that in purely arithmetical terms, a further 36 records should fit on the page. |
…Insert 36 Rows…
2 Seiten, direkt ab dem 701. Datensatz. |
2 pages, directly from the 701st data set. |
Freier Platz! – Fragmentierung, „Igitt“ ;-) |
Free space! – Fragmentation, „yuck“ ;-) |
Und schon ist die Seite zu glatten 100% gefüllt: 736 Datensätze. |
And just like that, the page is filled with a sleek 100%: 736 records. |
Ja, die Page_ID ist eine andere – aber nicht, weil ich gemogelt hätte, sondern weil die Storage Engine für den Rebuild neuen Platz reserviert, und den alten nach getaner Arbeit freigegeben hat. Und auch auf Platte sieht es jetzt so aus, wie es sein sollte – bis zum Ende aufgefüllt: |
Yes, the Page_ID is a different one – but not because I might have cheated, but because the storage engine allocated new space for the rebuild, and released the old one after the done work. On the drive, too, it now looks exactly how it’s supposed to – filled up to the end: |
Übrigens: Wenn ich anstelle der Heap-Speicherung der Tabelle einen Clustered Index als Struktur gewählt hätte, wären die Daten in den allermeisten Fällen sofort auf der einen Seite zu 100% abgelegt worden Ansonsten gilt auch hier die Regel: „Niemals auf etwas verlassen, was man nicht selbst getestet hat“ ;-) |
By the way, had I chosen a Clustered Index as structure instead of the Heap-storage, in most cases, the data would have been stored immediately to a 100% on the one page Otherwise, the rule also applies here: “Never rely on something you haven’t tested yourself” ;-) |
Ergebnis: - und dabei macht es noch nicht einmal einen Unterschied, ob man eine Spalte mit 1 oder 2 Bytes Größe, oder gar 2 Spalten mit je einem Byte Größe verwendet. Da jeder Datensatz 9 Bytes zzgl. 2 Bytes Record Offset benötigt, haben wir damit die maximale Kapazität einer SQL Server Datenseite mit 8096 Bytes exakt ausgereizt! :-) - Nicht zu verwechseln mit der maximalen Zeilenlänge von 8060 Bytes. |
Result: - and it does not even make a difference, if one uses one column with 1 or 2 bytes in size, or even 2 columns with one byte in size. Since every data record uses 9 bytes plus 2 bytes record offset we will have exactly exhausted the maximum capacity of an SQL Server data page with 8096 bytes! :-) - Not to be confused with the maximal row length of 8060 bytes. |
my nine bytes
Andreas
CONTROL SERVER vs. sysadmin/sa: permissions, system procedures, DBCC, automatic schema creation and privilege escalation - caveats
Aug 1st
CONTROL SERVER gegen Sysadmin/sa: Berechtigungen, Systemprozeduren, DBCC, automatische Schema-Erstellung und Privilegienausweitung - Fallstricke
(DE) Seit SQL Server 2005 gibt es die Serverweite Berechtigung CONTROL SERVER. Prinzipiell eine Alternative zur sysadmin-Mitgliedschaft, blieb sie dennoch nicht viel mehr als ein Ladenhüter – kaum bekannt und noch weniger verwendet. Einer der Hauptgründe dafür war die fehlende Möglichkeit, dieses Recht einer Gruppe von Prinzipalen/Logins auf Server-Ebene zu geben. Seit SQL Server 2012 ist es ja nun möglich, eigene Server-Rollen zu definieren, so dass diese Berechtigung nun immer mehr Verwendung findet. Dennoch ist sie kein vollständiger Ersatz für sysadmin. Warum das so ist wo die Unterschiede liegen und wo es sogar ein Risiko sein kann, möchte ich im Folgenden für meine Leser festhalten und demonstrieren. |
(EN) Since SQL Server 2005, the server wide permission CONTROL SERVER has been existing. In principle being an alternative to sysadmin-membership, it did not turn out to be much more than a shelf warmer. - Little known and even less used. One of the main reasons for this was the absence of an option to grant this permission to a group of principals/Logins on server-level. Since SQL Server 2012, it has been possible to define custom server-roles, so now this permission is used more and more. However, it is no complete replacement for sysadmin. In the following, I want to demonstrate to my readers exactly why this is, where the differences are and where it can even be a risk. |
Grundlegende Kenntnisse über das Verhalten von sysadmin und dbo setze ich als hinreichend bekannt voraus, und konzentriere mich auf das, was bei der Verwendung von CONTROL SERVER anders ist. |
I will take as a given basic knowledge about the behavior of sysadmin and dbo and therefore focus on the things that are different when using CONTROL SERVER. |
Zunächst wird ein neuer Login, „DBA_TheDude“, angelegt – das Passwort natürlich streng meinen Richtlinien (für Blogs) entsprechend. ;-) Es folgen eine neue Serverrolle, „Role_DBA“, in die der Login als Mitglied aufgenommen wird. Und da diese Rolle für Administratoren gedacht ist, erhält sie die weitest-möglichen Rechte: CONTROL SERVER. |
First of all, a new Login, „DBA_TheDude“, is created – the password of course adhering strongly to my rules (for blogs). ;-) This is followed by a new server role “Role_DBA” to which the Login will be added as a member. And since this role is meant for administrators, it receives the most extensive permission: CONTROL SERVER. |
USE [master]
GO
CREATE LOGIN DBA_TheDude WITH PASSWORD=N'www', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
CREATE SERVER ROLE [Role_DBA]
ALTER SERVER ROLE [Role_DBA]
ADD MEMBER DBA_TheDude
GRANT CONTROL SERVER TO [Role_DBA]
GO
CREATE DATABASE ControlServer_Schema_Demo
GO
Als nächstes melden wir uns als DBA am System an und lassen uns „die (effektiven) Rechte verlesen“ :-) |
Next we log onto the system as DBA and have "our (effective) rights" displayed. :-) |
EXECUTE AS LOGIN = 'DBA_TheDude'
SELECT entity_name, permission_name
FROM sys.fn_my_permissions(NULL, NULL)entity_name
entity_name |
permission_name |
server |
CONNECT SQL |
server |
SHUTDOWN |
server |
CREATE ENDPOINT |
server |
CREATE ANY DATABASE |
server |
CREATE AVAILABILITY GROUP |
server |
ALTER ANY LOGIN |
server |
ALTER ANY CREDENTIAL |
server |
ALTER ANY ENDPOINT |
server |
ALTER ANY LINKED SERVER |
server |
ALTER ANY CONNECTION |
server |
ALTER ANY DATABASE |
server |
ALTER RESOURCES |
server |
ALTER SETTINGS |
server |
ALTER TRACE |
server |
ALTER ANY AVAILABILITY GROUP |
server |
ADMINISTER BULK OPERATIONS |
server |
AUTHENTICATE SERVER |
server |
EXTERNAL ACCESS ASSEMBLY |
server |
VIEW ANY DATABASE |
server |
VIEW ANY DEFINITION |
server |
VIEW SERVER STATE |
server |
CREATE DDL EVENT NOTIFICATION |
server |
CREATE TRACE EVENT NOTIFICATION |
server |
ALTER ANY EVENT NOTIFICATION |
server |
ALTER SERVER STATE |
server |
UNSAFE ASSEMBLY |
server |
ALTER ANY SERVER AUDIT |
server |
CREATE SERVER ROLE |
server |
ALTER ANY SERVER ROLE |
server |
ALTER ANY EVENT SESSION |
server |
CONTROL SERVER |
Klingt gut. Scheint Nichts zu fehlen. Vergleichen wir es mal mit der Liste aller Rechte, die es auf Server-Ebene überhaupt gibt: |
Sounds good. Nothing seems to be missing at first glance. Let’s compare it to the list of all permissions which exist on server-scope: |
SELECT class_desc COLLATE Latin1_General_CI_AI, permission_name COLLATE Latin1_General_CI_AI
FROM sys.fn_builtin_permissions('SERVER')
EXCEPT
SELECT entity_name, permission_name
FROM sys.fn_my_permissions(NULL, NULL)
-->
(0 row(s) affected)
Ok, also wir haben wirklich alle Rechte, die man (auf Serverebene) vergeben kann. Diese Berechtigungen und Kommandos funktionieren auch wie dokumentiert. Wie sieht es aber mit gespeicherten Systemprozeduren aus? |
Ok, we really do have all permissions which can be granted (at server scope). Those permissions and commands really do work as documented. But how about system stored procedures? |
exec sp_readerrorlog
-->
Msg 15003, Level 16, State 1, Procedure sp_readerrorlog, Line 11
Only members of the securityadmin role can execute this stored procedure.
Warum das jetzt? Ganz einfach. Sicher ist es den meisten noch gut aus SQL Server 2000-Zeiten bekannt, dass sicherheitskritische Systemprozeduren einen Check auf Mitgliedschaft in bestimmten Rollen enthalten. Und das tun die meisten derer auch heute noch. Ein Artefakt der damaligen unflexiblen Rechte-Verwaltung, kann man sagen. Wenn man in den Code der sp_readerrorlog schaut, wird es dort genau so gemacht: |
Why ist that now? Quite simple. Most do probably remember from SQL 2000 times, that security wise critical system procedures include a check for membership in certain roles. And most of them still do the same today. One can call it an artefact of the then inflexible permission-management. When we look into the code of sp_readerrorlog, this is what is being done there as well: |
if (not is_srvrolemember(N'securityadmin') = 1)
begin
raiserror(15003,-1,-1, N'securityadmin'
return (1)
end
...
exec sys.xp_readerrorlog...
Okay. In der GUI-Anzeige im Management Studio hilft uns das nicht, wir können es als manuellen Workaround verwenden. Weitere Systemprozeduren, die über SSMS aufgerufen werden und die Ausführung verweigern sind unter anderem: sp_addlinkedserver lässt sich dafür ohne Probleme ausführen – nur eben nicht über die GUI wegen eben genannter Prozedur. Die Einrichtung von Database-Mail über die GUI bleibt ebenfalls den sysadmins vorbehalten. Via Script sollte es jedoch funktionieren (ungetestet). Auch die Konfiguration des Distributors für die Replikation mittels sp_adddistributor ist ohne den sysadmin-Bit nicht möglich. Unter SQL Server 2005 – 2008R2 gibt es für das Hinzufügen/Entfernen von (Server-)Rollenmitgliedern nur die Prozedur sp_addsrvrolemember / sp_dropsrvrolemember. Diese führt ebenfalls eine Serverrollen-Mitgliedschafts-Prüfung durch. - Unter SQL Server 2012 ist diese Prozedur aus Rückwärtskompatibilitätsgründen noch vorhanden, jedoch wurde dieser Check aus dem Code entfernt. Damit verhält sie sich in dieser Hinsicht somit wie der direkte Aufruf von ALTER SERVER ROLE {RoleName} ADD MEMBER {Loginname}. Das gleiche gilt für sp_addrolemember / sp_droprolemember für Datenbankrollen. |
Okay. This does not help in the GUI-presentation in Management Studio but we can use it as a manual workaround. Among other system procedures that are called via SSMS and refuse execution are: sp_addlinkedserver on the other hand can be executed without problems – just not via the GUI because of the just mentioned procedure. Setting up Database-Mail via the GUI is also reserved for sysadmins. It should work via script though (untested). Also the configuration of the distributor for replication by means of sp_adddistributor is not possible without the sysadmin bit. Under SQL Server 2005 – 2008R2, to add/remove (server) role members, solely the system procedure sp_addsrvrolemember / sp_dropsrvrolemember exists. This one also conducts a server-role-membership-check. - Under SQL Server 2012 this procedure still exists for backwards-compatibility reasons, but this specific check has been removed from the code. Thereby it now behaves like the direct call of ALTER SERVER ROLE {RoleName} ADD MEMBER {Loginname}. Same applies to sp_addrolemember / sp_droprolemember for database roles. |
Soweit zu Systemprozeduren. Eigentlich ziemlich simpel, wenn man die Hintergründe kennt. Daher, wo möglich: DDL-Kommandos verwenden, wie seit SQL Server 2005 nahegelegt wird. Randnotiz: Es gibt mindestens 7 Variationen der Prüfung auf sysadmin-Mitgliedschaft. :-) :-( Eine Kostprobe: |
So much for system stored procedures. Quite simple after all, if you know the background. Therefore, wherever possible: use DDL-commands, as it is advised since SQL Server 2005. Side-note: there are at least 7 variations for the check of sysadmin-membership. :-) :-( A taster: |
IF IS_SRVROLEMEMBER('sysadmin') != 1
IF is_srvrolemember('securityadmin') = 0
IF (is_srvrolemember('sysadmin') <> 1)
IF (is_srvrolemember(N'sysadmin') <> 1 )
IF (not is_srvrolemember(N'sysadmin') = 1)
IF (ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) = 0)
IF isnull(is_srvrolemember('sysadmin'),0) = 1
Die häufigsten Fehlermeldungen, die dann ggfls. ausgelöst werden, sind: |
The most frequent error messages which are triggered if applicable are: |
EN:
message_id |
text |
14126 |
You do not have the required permissions to complete the operation. |
14260 |
You do not have sufficient permission to run this command. Contact your system administrator. |
15003 |
Only members of the %s role can execute this stored procedure. |
15247 |
User does not have permission to perform this action. |
21089 |
Only members of the sysadmin fixed server role can perform this operation. |
22904 |
Caller is not authorized to initiate the requested action. DBO privileges are required. |
DE:
message_id |
text |
14126 |
Sie haben nicht die erforderlichen Berechtigungen, um den Vorgang abzuschließen. |
14260 |
Sie haben nicht die erforderliche Berechtigung, um diesen Befehl auszuführen. Wenden Sie sich an den Systemadministrator. |
15003 |
Nur Mitglieder der %1!-Rolle können diese gespeicherte Prozedur ausführen. |
15247 |
Der Benutzer besitzt nicht die Berechtigung zum Ausführen dieser Aktion. |
21089 |
Nur Mitglieder der festen Serverrolle 'sysadmin' können diesen Vorgang ausführen. |
22904 |
Der Aufrufer ist nicht zum Initiieren der angeforderten Aktion berechtigt. Es sind DBO-Privilegien erforderlich. |
Eine vollständige Liste aller Prozeduren, die auf sysadmin-Rollenmitgliedschaft prüfen, folgt am Ende. |
A complete list of procedures that check for sysadmin-membership is included at the end of this post. |
Kommen wir zu einem anderen wichtigen Bereich: DBCC-Kommandos |
Let’s come to another important area: DBCC commands |
DBCC CHECKDB
-->
Msg 7983, Level 14, State 36, Line 1
User 'public' does not have permission to run DBCC checkdb for database 'master'.
Das geht ja gut los. Die „Regel“ ist bei DBCC noch einfacher: Von DBCC CHECKDB über DBCC LOGINFO bis zu DBCC TRACEON. Also auch durchaus wertvolle Kommandos auch für den externen Support. Die einzigen Ausnahmen, die mir bekannt sind:
DBCC DETACHDB
DBCC FREEPROCCACHE und DBCC SQLPERF
DBCC DROPCLEANBUFFERS wiederum setzt die sysadmin-Rollenmitgliedschaft voraus. Diese Berechtigungen sind übrigens recht gut dokumentiert. |
That’s a good start, isn’t it.. The „rule“ for DBCC is even simpler: From DBCC CHECKDB via DBCC LOGINFO to DBCC TRACEON. Thus some quite valuable commands also for external support. The only exceptions known to me are:
DBCC DETACHDB
DBCC FREEPROCCACHE and DBCC SQLPERF
DBCC DROPCLEANBUFFERS on the other hand requires sysadmin-role membership. Those permissions are documented quite well by the way. |
Datenbankberechtigungen Mit CONTROL SERVER hat ein Prinzipal auch vollen Zugriff auf alle Datenbanken. Logins mit lediglich CONTROL SERVER-Berechtigung werden nicht(!) zu dbo gemapped. Welche Auswirkungen das hat, sieht man im Folgenden: |
Database permissions With CONTROL SERVER, a principal has full access to all databases. Logins with merely CONTROL SERVER permission are not(!) mapped to dbo. The consequences of this can be seen in the following: |
USE ControlServer_Schema_Demo
GO
SELECT USER_NAME()
-->
DBA_TheDude
CREATE TABLE dbo.DemoTable1_dbo(id int)
GO
CREATE TABLE DemoTable2_NoSchemaSpecified(id int)
GO
SELECT name, schema_id FROM sys.tables
SELECT * FROM sys.schemas
-->
-->
SSMS:
Was ist passiert? Erinnern Sie sich noch an das pre-SQL 2012 Problem, das man Windows-Gruppen kein Default Schema zuweisen konnte? Und was passierte, wenn ein Entwickler über solch eine Gruppe vergaß, beim Anlegen eines Objektes das Schema anzugeben? Richtig. Das ist genau das gleiche: es wurde ein Schema mit dem Login-Namen des Entwicklern angelegt. Eine reine Freude für spätere Aufräumarbeiten, die „richtigen falschen“ Schemas zu identifizieren und zu löschen. Leider wurde dabei verpasst, auch die Behandlung von CONTROL SERVER Berechtigten anzupassen. Dazu hatte ich ebenfalls ein Connect-Item erstellt.: „Login with CONTROL SERVER Permission Creating an Object without specifying Schema leads to creation of new Schema with Login-Name”. - Leider zu spät. :-/ Vermutlich wird dazu ein identischer Code-Block verwendet, denn genau wie in dem Windows-Gruppen-Szenario ist dieses implizite Anlegen des Schemas nicht abfangbar. Fazit: Immer ein Schema angeben, wenn man als CONTROL Server Berechtigter Objekte anlegt – am besten einfach wirklich immer ein Schema angeben (es gibt noch andere Gründe dafür). |
What happened? Do you remember the pre-SQL 2012 problem, that windows groups could not be assigned a default schema? And what happened if a developer forgot to specify a schema when creating an object? Right. This is exactly the same: a schema with the Login-Name of the developer has been created. A true joy for later clean-up, to identify and drop the „right wrong” schemas. Unfortunately the handling of the CONTROL SERVER permission has been overlooked to be fixed. I did create a connect item: „Login with CONTROL SERVER Permission Creating an Object without specifying Schema leads to creation of new Schema with Login-Name”. – Unfortunately too late. :-/ Probably an identical code-block is being used for that, since just like in the Windows-Group scenario, this implicit creation of the schema is uncatchable. Conclusion: Always specify a schema when creating an object as a CONTROL SERVER granted principal – best: always specify a schema. (there are other reasons for that, too) |
Privilege-Escalation-Risiko: Abschließen möchte ich mit einem sicherheitstechnisch extrem wichtigen Hinweis: Logins mit CONTROL SERVER Berechtigung können standardmäßig JEDEN Login impersonifizieren. JEDEN. Also auch alle Sysadmins und sa selber! – Und auch wenn SQL Authentifizierung nicht aktiv ist! Und so lässt sich das wunderbar ausnutzen: |
Privilege-Escalation-risc: I want to wind up with a security-wise extremely important hint: Logins with CONTROL SERVER permission can impersonate EVERY Login. ALL OF THEM. Thus also all sysadmins and sa itself! – And even if SQL authentication is not active! And this is how this can be exploited marvellously: |
ALTER SERVER ROLE sysadmin
ADD MEMBER DBA_TheDude
--> No
EXEC sp_addsrvrolemember'DBA_TheDude', 'sysadmin';
--> No
EXECUTE AS LOGIN = 'sa';
ALTER SERVER ROLE sysadmin
ADD MEMBER DBA_TheDude
--> “Thank you”
REVERT;
-->
Wer sich also gegenüber dieser Form von Privilege-Escalation schützen möchte, muss das impersonate (mithilfe von DENY) verbieten. Sysadmin-Mitglieder können nicht mit DENY eingeschränkt werden. Prinzipale mit bloßer CONTROL SERVER Berechtigung jedoch schon. So funktionieren alle reinen DDL/DML Kommando-Berechtigungen eben. Also, um es noch einmal klar zu sagen, da es so entscheidend ist: Dummerweise geht das nur über "DENY IMPERSONATE ON LOGIN::[Loginname]". Denn IMPERSONATE ist hierarchisch direkt CONTROL SERVER untergeordnet! |
Who wants to protect himself from this form of privilege escalation must DENY impersonating. Sysadmin-members cannot be restricted anything via DENY. Principals with mere CONTROL SERVER permissions yet can. This is how all plain DDL/DML commands work. So, to say it clearly again, because this is vital: Unfortunately this only works via "DENY IMPERSONATE ON LOGIN::[Loginname]". This is because IMPERSONATE is hierarchically a direct subordinate of CONTROL SERVER! |
USE master
DENY IMPERSONATE ON LOGIN::sa TO [DBA_TheDude]
Bei einer sich ändernden Liste an Systemadministratoren hat man ein Problem, wenn man nicht an dieses Deny denkt! (Nächstes Risiko!) - Spätestens an dieser Stelle sollte man sich mit dem Überwachungs-Feature von SQL Server befassen. |
For a changing list of system administrators one has a real problem, in case of not remembering this crucial Deny! (Next risk!) - At the latest at this point one should address the Auditing feature of SQL Server. |
Mein Fazit und Lessons-learned daher: CONTROL-SERVER selber ist eine konsequente Fortführung der Bemühungen des Sicherheits-Teams, SQL Server sicherheitstechnisch noch einfacher und damit robuster zu machen. Jedoch ist aus dem Grund der Impersonate-Rechte eine Privilege Escalation zu sysadmin sehr einfach. Um diese zu verhindern, sollte man gut mit der Berechtigungs-Hierarchie von SQL Server vertraut sein. – Wie immer: „Wissen schafft Sicherheit.“ Was wirklich schmerzt, ist, das eines der besten Anwendungsszenarien: Rechte für Support-Personal, intern oder Extern. Im Endeffekt ist eine strikte „Separation of Duties“, die Funktionstrennung auf verschiedene „Rollen“ das eherne Ziel. Hier kann man dazu Nachlesen – auch das „SQL Server Separation of Duties Framework“ von codeplex, mit vielen guten Ideen kann ich empfehlen einmal anzusehen: |
My conclusion and lessions-learned therefore are: CONTROL SERVER itself is a consistent continuation of the efforts of the security team to make SQL Server even simpler and thereby more robust. Because of the impersonate-permissions, a privilege escalation is really simple. In order to prevent this, one should be familiar with the permission hierarchy of SQL Server. – As always: “Knowledge leads to security.” What really does hurt, is one of the best application cases: Permissions for support-personnel, internal or external. In the end, a strict “separation of duties” is the iron goal. Here you can read more about it – you should also check out the “SQL Server Separation of Duties Framework” on codeplex with many good ideas: |
http://blogs.technet.com/b/fort_sql/archive/2011/09/12/separation-of-duties-for-dba-s.aspx
Update 04-2014: Mit SQL Server 2014 wird CONTROL Server etwas sicherer. Hier kann man mehr zu den neuen Möglichkeiten lesen: |
Update 04-2014: With SQL Server 2014 CONTROL Server becomes a bit safer. You can read more on the new possibilities here: |
Eine Ablösung des „sa" ist übrigens nicht geplant, jedoch sinkt mit jedem Release die Anzahl der Szenarien, wo sa/sysadmin zwingend benötigt wird. |
A displacement of „sa“ is not planned by the way, but with every release the scenarios where sa/sysadmin is imperatively needed become less. |
Happy securing,
Andreas
Anbei die komplette Liste aller Systemprozeduren unter SQL Server 2012, die auf sysadmin-Rollenmitgliedschaft prüfen (171 – gegenüber 173 unter SQL Server 2008R2. SQL Server 2014 CTP1 ist noch das Gleiche): (Hinweis: Insgesamt 197 Systemprozeduren prüfen auf Serverrollenmitgliedschaft, wie diskadmin oder serveradmin usw.) |
Enclosed the list of all system procedures under SQL Server 2012 that check for sysadmin-role membership (171 – vs. 171 under SQL server 2008. SQL Server 2014 CTP 1 still the same): (Hint: Altogether 197 system procedures check for server role membership like diskadmin or serveradmin and so on) |
Module_Name |
fn_yukonsecuritymodelrequired |
sp_add_agent_parameter |
sp_add_agent_profile |
sp_adddatatype |
sp_adddistributiondb |
sp_adddistributor |
sp_addqreader_agent |
sp_addsubscriber |
sp_addsubscriber_schedule |
sp_addtabletocontents |
sp_attachsubscription |
sp_cdc_cleanup_change_table |
sp_cdc_disable_db |
sp_cdc_disable_table |
sp_cdc_drop_job |
sp_cdc_enable_db |
sp_cdc_enable_table |
sp_cdc_restoredb |
sp_cdc_vupgrade |
sp_certify_removable |
sp_change_agent_parameter |
sp_change_agent_profile |
sp_change_subscription_properties |
sp_change_users_login |
sp_changedistpublisher |
sp_changedistributiondb |
sp_changedistributor_password |
sp_changedistributor_property |
sp_changemergesubscription |
sp_changeqreader_agent |
sp_changereplicationserverpasswords |
sp_changesubscriptiondtsinfo |
sp_checkinvalidivarticle |
sp_copysubscription |
sp_create_removable |
sp_cycle_errorlog |
sp_dbcmptlevel |
sp_dbmmonitoraddmonitoring |
sp_dbmmonitorchangealert |
sp_dbmmonitordropalert |
sp_dbmmonitordropmonitoring |
sp_dbmmonitorhelpalert |
sp_dbmmonitorhelpmonitoring |
sp_dbmmonitorresults |
sp_dbmmonitorupdate |
sp_dbremove |
sp_drop_agent_parameter |
sp_drop_agent_profile |
sp_dropdatatypemapping |
sp_dropdistpublisher |
sp_dropdistributiondb |
sp_dropdistributor |
sp_dropmergepullsubscription |
sp_droppullsubscription |
sp_dropsubscriber |
sp_dsninfo |
sp_enumdsn |
sp_flush_commit_table_on_demand |
sp_generate_agent_parameter |
sp_get_distributor |
sp_get_Oracle_publisher_metadata |
sp_getagentparameterlist |
sp_getdefaultdatatypemapping |
sp_grant_publication_access |
sp_help_agent_default |
sp_help_agent_parameter |
sp_help_agent_profile |
sp_helpdistpublisher |
sp_helpdistributor |
sp_helpmergesubscription |
sp_helpqreader_agent |
sp_helpreplicationdboption |
sp_identitycolumnforreplication |
sp_IHValidateRowFilter |
sp_IHXactSetJob |
sp_link_publication |
sp_monitor |
sp_MSadd_distribution_agent |
sp_MSadd_logreader_agent |
sp_MSadd_merge_agent |
sp_MSadd_snapshot_agent |
sp_MSadd_subscriber_schedule |
sp_MSadd_tracer_history |
sp_MSadd_tracer_token |
sp_MScdc_cleanup_job |
sp_MScdc_db_ddl_event |
sp_MScdc_ddl_event |
sp_MSchange_distribution_agent_properties |
sp_MSchange_logreader_agent_properties |
sp_MSchange_merge_agent_properties |
sp_MSchange_snapshot_agent_properties |
sp_MSchangedynamicsnapshotjobatdistributor |
sp_MSchangedynsnaplocationatdistributor |
sp_MScheck_pull_access |
sp_MScleanupmergepublisher_internal |
sp_MSclear_dynamic_snapshot_location |
sp_MScreate_dist_tables |
sp_MSdbuserpriv |
sp_MSdeletefoldercontents |
sp_MSdrop_6x_replication_agent |
sp_MSdrop_merge_agent |
sp_MSdrop_snapshot_dirs |
sp_MSdropmergedynamicsnapshotjob |
sp_MSdynamicsnapshotjobexistsatdistributor |
sp_MSenumallpublications |
sp_MSfetchAdjustidentityrange |
sp_MSfix_6x_tasks |
sp_MSforce_drop_distribution_jobs |
sp_MSget_agent_names |
sp_MSget_jobstate |
sp_MSget_oledbinfo |
sp_MSget_publication_from_taskname |
sp_MSgetdbversion |
sp_MSgetmaxsnapshottimestamp |
sp_MShelp_repl_agent |
sp_MShelp_replication_status |
sp_MShelp_snapshot_agent |
sp_MShelpconflictpublications |
sp_MShelpdynamicsnapshotjobatdistributor |
sp_MShelplogreader_agent |
sp_MShelpsnapshot_agent |
sp_MShelptranconflictcounts |
sp_MSinit_publication_access |
sp_MSreinit_failed_subscriptions |
sp_MSremoveoffloadparameter |
sp_MSrepl_backup_complete |
sp_MSrepl_backup_start |
sp_MSrepl_createdatatypemappings |
sp_MSrepl_dropdatatypemappings |
sp_MSrepl_enumarticlecolumninfo |
sp_MSrepl_enumpublications |
sp_MSrepl_enumpublishertables |
sp_MSrepl_enumsubscriptions |
sp_MSrepl_enumtablecolumninfo |
sp_MSrepl_getdistributorinfo |
sp_MSrepl_startup_internal |
sp_MSreplagentjobexists |
sp_MSreplcheck_permission |
sp_MSreplcheck_pull |
sp_MSreplcheck_subscribe |
sp_MSreplcheck_subscribe_withddladmin |
sp_MSreplcopyscriptfile |
sp_MSreplremoveuncdir |
sp_MSsetalertinfo |
sp_MSSetServerProperties |
sp_MSsetupnosyncsubwithlsnatdist |
sp_MSsetupnosyncsubwithlsnatdist_cleanup |
sp_MSsetupnosyncsubwithlsnatdist_helper |
sp_MSstartdistribution_agent |
sp_MSstartmerge_agent |
sp_MSstartsnapshot_agent |
sp_MSstopdistribution_agent |
sp_MSstopmerge_agent |
sp_MSstopsnapshot_agent |
sp_MSupdate_agenttype_default |
sp_oledbinfo |
sp_procoption |
sp_removedbreplication |
sp_removesrvreplication |
sp_replication_agent_checkup |
sp_replicationdboption |
sp_resetstatus |
sp_restoredbreplication |
sp_SetAutoSAPasswordAndDisable |
sp_setdefaultdatatypemapping |
sp_updatestats |
sp_validatelogins |
sp_vupgrade_mergeobjects |
sp_vupgrade_replication |
sp_vupgrade_replsecurity_metadata |
xp_repl_convert_encrypt_sysadmin_wrapper |