The key to locking down resources in any kind of
system—database or otherwise—is quite simple in essence: any given user
should have access to only the bare minimum set of resources required,
and for only as much time as access to those resources is needed.
Unfortunately, in practice this is more of an ideal goal than an actual
prescription for data security; many systems do not allow privilege to
be easily escalated dynamically, and Windows-based solutions have not
historically been engineered to use escalation of privilege as a means
by which to gain additional access at run time.
In many non-Windows operating systems that were originally designed for multiuser access, it has long been possible to impersonate
other users when access to a resource owned by that user is required.
It is important to note that impersonation is slightly different than
reauthentication; instead of logging out and resending credentials,
thereby stopping whatever is running, impersonation allows a process to
temporarily escalate
its privileges, taking on the rights held by the impersonated
principal. The most common example of this at an operating system level
is UNIX's su command, which allows
a user to temporarily take on the identity of another user, easily
reverting back when done. Windows systems can handle some degree of
impersonation as well, but Microsoft has only recently provided
APIs—such as the .NET WindowsIdentity class—which make doing so as convenient as in UNIX systems.
Permissions in Windows systems are typically provided using Access Control Lists
(ACLs). Granting permission to a resource means adding a user to the
list, after which the user can access the resource again and again, even
after logging in and out of the system. This kind of access control
provides no additional security if, for instance, an attacker takes over
an account in the system. By taking control of an account, the attacker
automatically has full access to every resource that the account has
permission to access.
By controlling access with impersonation, the user is required to effectively request access to the resource dynamically, each time access is required. In addition, rights to the resource will only be maintained during the course of impersonation. Once the user reverts
(i.e., turns off impersonation), the additional access rights are no
longer granted. In effect, this means that if an account is compromised,
the attacker will have to also compromise the impersonation context in
order to gain access to more secure resources.
The idea of security through least privilege
involves creating users with few or no permissions, and allowing them
to briefly escalate their privileges when greater access is required.
This is generally implemented using proxies,
users (or other security principals) that have access to a resource but
cannot be authenticated externally. Use of low-privileged external
users complemented by higher-privileged proxy users provides a buffer
against attack due to the fact that the only accounts that an attacker
can directly compromise from the outside have no permissions. Accessing
more valuable resources requires additional work on the part of the
attacker, giving you that much more of a chance to detect problems
before they occur.
Creating Proxies in SQL Server
SQL Server 2005 allows creation of both server-level principals (logins) that cannot log in, and database-level principals (users)
that are not associated with a login. It is only possible to switch
into the execution context of one of these users or logins via
impersonation, making them ideal for privilege escalation scenarios.
In order to create a proxy login (which can be used to delegate server-level permissions such as BULK INSERT or ALTER DATABASE), you must first create a certificate in the master database. A certificate as a trusted way to verify the
identity of a principal without a password. The following syntax can be
used to create a certificate in master.
USE master
GO
CREATE CERTIFICATE Dinesh_Certificate
ENCRYPTION BY PASSWORD = 'stR0n_G paSSWoRdS, pLE@sE!'
WITH SUBJECT = 'Certificate for Dinesh'
GO
Once the certificate has been created, a proxy login can be created using SQL Server 2005's CREATE LOGIN FROM CERTIFICATE syntax:
CREATE LOGIN Dinesh
FROM CERTIFICATE Dinesh_Certificate
This login can be
granted permissions, just like any other login. However, to use the
permissions, it must be mapped to a database user. This is done by
creating a user using the same certificate that was used to create the
login, using the CREATE USER FOR CERTIFICATE syntax.
Another type of proxy principal that can be created is a database user not associated with a server login. This is done using CREATE USER WITHOUT LOGIN:
CREATE USER Bob
WITHOUT LOGIN
This user, like any
database user, can be assigned ownership and other permissions. However,
it is impossible to log in to the server and authenticate as Bob.
Instead, you must log in using a valid server-level login and
authenticate to the database with whatever database user is associated
with your login. Only then can you impersonate Bob, taking on whatever
permissions the user is assigned.
Data Security in Layers: The Onion Model
Generally speaking,
the more levels an attacker must penetrate in order to access a valuable
resource, the better the chance is that an attack will not be
successful. Developers should strive to carefully construct multiple
layers of protection for any sensitive data, in order to ensure that if
one security measure is breached, other obstacles will keep an attacker
at bay.
The first layer of
defense is everything outside of the database server, all of which falls
into the realm of authentication. Once a user is authenticated, SQL
Server's declarative permissions system kicks in, and a login is
authorized to access one or more databases, based on user mappings.
From there, each user
is authorized to access specific resources in the database. Another
layer that can be added for additional security here is use of stored
procedures. By assigning permissions only via stored procedures, it is
possible to maintain greater control over when and why escalation should
take place.
Of course, the stored
procedure itself must have access to whatever tables and columns are
required, and these resources can be further locked down if necessary,
using encryption or row-level security schemes.
Figure 1
shows some of the layers that should be considered when defining a SQL
Server security scheme, in order to secure the sensitive data as well as
possible.
A stored procedure
layer provides an ideal layer of indirection between the data and the
data access, allowing for additional security to be programmed in via
parameters or other inline logic. For instance, it is trivial to log
every access to sensitive data via a stored procedure, by including
logging code in the procedure. Likewise, a stored procedure might be
used to force users to access data on a granular basis, by requiring
parameters that are used as predicates to filter data. These security
checks are difficult or impossible to force on callers without using
stored procedures to encapsulate the data access logic.