Although a large amount of security metadata can be viewed from SQL Server Management Studio, there is some metadata that can only be viewed using T-SQL. Even for metadata that can be viewed through the GUI, such as the roles that a database user belongs to, there are times that it is best to use T-SQL; for example, if you need to script an action that you perform on a regular basis, or if you need to review metadata for many principals.
A complete guide to security metadata within SQL Server is worthy of a book in its own right. Therefore, this chapter explains some of the most useful and interesting metadata objects and provides insights into how you may use them.
Security Principal Metadata
When you are implementing, reviewing, or auditing a security policy on an instance, it is likely that you need to retrieve information about many security principals or securable objects. As an example of this, part of your security policy might state that all databases must be owned by the sa account, and you need to verify that this is the case. You could, of course, enter the context menu of each database, select Properties, and then review the Owner field on the General page of the Database Properties dialog box. If the instance hosts 200 databases, however, then this may be a rather tedious and time-consuming task.
Instead of using the GUI, it makes sense to use SQL Server metadata. The database owner of each database can be returned by using the sp_MShasdbaccess stored procedure or by querying the sys.databases catalog view.
The sp_MShasdbaccess stored procedure does not accept parameters; it returns the name and owner of each database, as well as the status of each database.
Tip
The sp_MShasdbaccess procedure only returns rows for databases that the caller has access to. Providing that the procedure is run by a database administrator, this should not be an issue.
To retrieve the data from sys.databases, you need to return the SID (security identifier) of each database by retrieving the owner_sid column and passing this column to the SUSER_SNAME() system function. This is demonstrated in Listing 6-1.
Listing 6-1. Retrieve Database Owners from sys.databases
SELECT name
,SUSER_SNAME(owner_sid)
FROM sys.databases ;
The SUSER_SNAME() function accepts a SID as a parameter and returns the login name associated with the SID. If no parameter is passed to the function, then it returns the login name of the caller.
Tip
Many people get confused about the difference between the SUSER_SNAME() function and a very similar function called SUSER_NAME(). They both return a login name. The difference is that SUSER_SNAME() accepts a SID as a parameter. SUSER_NAME() accepts a login id (principal id) as a parameter.
Finding a User’s Effective Permissions
When you have a complex hierarchy of server roles and database roles, as well as permissions granted directly to users, it can sometimes be challenging to work out exactly which permissions a user has. A system function that can help with this issue is sys.fn_my_permissions(). This function accepts the parameters described in Table 6-1.
Table 6-1. sys.fn_my_permissions Parameters
Parameter | Description |
---|---|
securable | The name of the securable that you wish to determine a user’s permissions against. |
securable_class | The type of securable that is interrogated; for example, SERVER, DATABASE, or OBJECT. |
The function returns the columns described in Table 6-2.
Table 6-2. sys.fn_my_permissions
Column | Description |
---|---|
entity_name | The name of the securable. |
subentity_name | If the securable has columns, then subentity_name contains the name of the column; otherwise, it is NULL. |
permission_name | The name of the permission assigned to the security principal. |
The function is designed to return information about the caller of the function, but you can change this behavior by using the EXECUTE AS statement. The EXECUTE AS statement is used to specify the name of a login or user whose identity should be used as the execution context of the session.
As an example of how the EXECUTE AS statement works, please review Listing 6-2.
Listing 6-2. EXECUTE AS Example
USE master
GO
CREATE LOGIN DemoLogin WITH PASSWORD=N'Pa$$w0rd', CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
ALTER SERVER ROLE sysadmin ADD MEMBER DemoLogin
USE AdventureWorks2016CTP3
GO
SELECT SUSER_SNAME() ;
EXECUTE AS LOGIN = 'DemoLogin' ;
SELECT SUSER_SNAME() ;
REVERT ;
SELECT SUSER_SNAME() ;
The script returns three results. The first result is your own login name. The second is DemoLogin’s login name. And the third result is your own login again. This is because the REVERT keyword is used to change the session context back to your own security context.
The script in Listing 6-3 demonstrates how the sys.fn_my_permissions function can be used in conjunction with the EXECUTE AS clause to find a user’s effective permissions at the instance, the database, and the object (within the current database) levels in a single query. I first wrote about this technique back in 2011 on the “SQL Server: Down & Dirty” blog ( www.sqlserverdownanddirty.blogspot.com ); since then, the method has been used and replicated by many others.
Caution
If a database uses a different collation to the server, then you may need to use the COLLATE statement within the query to avoid issues with running the script.
Listing 6-3. Find a User’s Effective Permissions
EXECUTE AS LOGIN = 'DemoLogin'
SELECT o.name
, a.entity_name
, a.subentity_name
, a.permission_name
FROM sys.objects o
CROSS APPLY sys.fn_my_permissions(CONCAT(
QUOTENAME(
SCHEMA_NAME(schema_id))
, '.'
, QUOTENAME(o.name))
, 'OBJECT') a
UNION ALL
SELECT d.name
, a.entity_name
, a.subentity_name
, a.permission_name
FROM sys.databases d
CROSS APPLY fn_my_permissions(QUOTENAME(d.name), 'DATABASE') a
UNION ALL
SELECT @@SERVERNAME COLLATE Latin1_General_CI_AS
, a.entity_name
, a.subentity_name
, a.permission_name
FROM fn_my_permissions(NULL, 'SERVER') a
ORDER BY 1
REVERT
The script works by running three separate queries and creating a union of the results. The first query returns each object name from sys.objects and passes this name, along with the schema name, into the sys.fn_my_permissions() function . The second query does the same thing, but instead of interrogating sys.objects, the script interrogates sys.databases to retrieve permissions at the database level. The final query resolves the user’s effective permissions against the instance itself.
Securable Metadata
There are ways in which your security profile may determine that your objects need to be secured. The following sections explore some of these potential requirements and demonstrate how metadata can help you verify or enforce your policy.
Code Signing
Code injection attacks can cause security breaches. You can protect against them by using code signing. For now, however, let’s simply assume that your security policy states that all assemblies and stored procedures must be code signed to help minimize the security footprint.
The script in Listing 6-4 reports which stored procedures in the database have been code signed and if the signature is valid. The script uses two security metadata objects. The first is sys.Certificates. The columns returned by this catalog view are described in Table 6-3.
Table 6-3. sys.Certificates Columns
Column | Description |
---|---|
name | The name of the certificate. |
certificate_id | The id of the certificate. |
principal_id | The id of the database user that owns the certificate. |
pvt_key_encryption_type | The encryption method of the private key. Possible values are as follows: • NA: Indicates that there is no private key associated with the certificate. • MK: Indicates that encryption is by the database master key. • PW: Indicates that encryption is by password. • SK: Indicates that encryption is by the service master key. |
pvt_key_encryption_type_desc | The textual description of the private key encryption type. Possible values are as follows: • NO_PRIVATE_KEY • ENCRYPTED_BY_MASTER_KEY • ENCRYPTED_BY_PASSWORD • ENCRYPTED_BY_SERVICE_MASTER_KEY |
is_active_for_begin_dialog | Specifies if the certificate is allowed to be used to begin an encrypted Service Broker conversation. • 0: Indicates that it is not allowed to start an encrypted Service Broker conversation. • 1: Indicates that it is allowed to start an encrypted Service Broker conversation. |
issuer_name | The name of the authority that issued the certificate. |
cert_serial_number | The serial number of the certificate. |
sid | The login SID of the certificate. |
string_sid | The name of the login SID. |
subject | The subject associated with the certificate. |
expiry_date | The certificate’s expiry date. |
start_date | The certificate’s start date. |
thumbprint | The SHA-1 hash of the certificate. |
attested_by | Internal use. |
pvt_key_last_backup_date | The date and time that the certificate was last backed up. |
The second metadata object used by the script is sys.fn_check_object_signatures(). This system function is used to return information regarding object signatures and their validity, based on the thumbprint of a certificate or asymmetric key. The function accepts the parameters described in Table 6-4.
Table 6-4. sys.fn_check_object_signatures Parameters
Parameter | Description |
---|---|
@Class | The type of thumbprint that the function checks. The acceptable values are • Certificate • Asymmetric key |
@Thumbprint | The thumbprint to be checked. |
The sys.fn_check_object_signatures function returns the columns described in Table 6-5.
Table 6-5. sys.fn_check_object_signatures Columns
Column | Description |
---|---|
type | The type description of the entity. |
entity_id | The object id of the evaluated entity. |
is_signed | Denotes if the object is signed or not. • 0: Indicates that the object is not signed. • 1: Indicates that the object is signed. |
is_signature_valid | Denotes if the object’s signature is valid. If the object is not signed, it returns 0. • 0: Indicates that either the object is not signed or that the signature is not valid. • 1: Indicates that the object’s signature is valid. |
Listing 6-4. Check Objects’ Signatures
DECLARE @thumbprint VARBINARY(20) ;
SET @thumbprint =
(
SELECT thumbprint
FROM sys.certificates
WHERE name LIKE '%SchemaSigningCertificate%'
) ;
SELECT entity_id
, SCHEMA_NAME(o.schema_id) + '.' + OBJECT_NAME(entity_id) AS ProcedureName
, is_signed
, is_signature_valid
FROM sys.fn_check_object_signatures ('certificate', @thumbprint) cos
INNER JOIN sys.objects o
ON cos.entity_id = o.object_id
WHERE cos.type = 'SQL_STORED_PROCEDURE ' ;
GO
The first part of the script retrieves the thumbprint of the database’s code signing certificate from the sys.Certificates catalog view. The second part of the script passes this thumbprint into the sys.fn_check_object_signatures and joins the results to the sys.objects catalog view to retrieve the schema name of the procedure.
Permissions Against a Specific Table
You may have a specific table or set of tables that contains sensitive information, and your security policy may state that you need to regularly audit who has permissions to that table and who assigned those permissions. Using SQL Server metadata, this is a straightforward task.
The sp_table_privileges system stored procedure identifies all permissions that principals have against a specific table, along with who granted those permissions. The procedure accepts the parameters described in Table 6-6.
Table 6-6. sp_table_privileges Parameters
Parameter | Description |
---|---|
@table_name | The name of the table to report on. |
@table_owner | The name of the schema to which the table belongs. |
@table_qualifier | The name of the database that hosts the table. |
@fUsePattern | Specifies if _, %, [, and ] should be treated as wildcard characters. • 0 indicates that they should be treated as literals. • 1 indicates that they should be treated as wildcard characters. |
The columns returned by the sp_table_privileges procedure are described in Table 6-7.
Table 6-7. sp_table_privileges Columns
Column | Description |
---|---|
TABLE_QUALIFIER | The database in which the table resides. |
TABLE_OWNER | The schema in which the table resides. |
TABLE_NAME | The name of the table. |
GRANTOR | The security principal that granted the permission. |
GRANTEE | The security principal assigned the permission. |
PRIVILEGE | The permission that has been assigned. |
IS_GRANTABLE | Specifies if the grantee has the WITH GRANT* assignment. |
The statement in Listing 6-5 returns results for all tables in the current database.
Listing 6-5. sp_table_privileges
EXEC sp_table_privileges @Table_name = '%' ;
Audit Metadata
As discussed in Chapter 3, SQL Server Audit provides a granular and lightweight method of auditing users’ actions within SQL Server. One of the advantages of SQL Server Audit is that you are able to “audit the audit” in an attempt to avoid non-reputability. For example, if an ill-intending DBA turned off the auditing, while they performed a malicious act, the action itself would not be audited, but the fact that the DBA had turned the audit off and then turned it back on again would be audited.
SQL Server exposes many metadata objects that assist a DBA in his work. One of the objects that I find most useful is the sys.fn_get_audit_file() function. The function returns the contents of a SQL Server Audit file. This can be inserted into a table for further analysis. The function accepts three parameters, which are described in Table 6-8.
Table 6-8. sys.fn_get_audit_file() Parameters
Parameter | Description |
---|---|
file_pattern | The name of the audit file that you wish to read. This path can contain the * wildcard to read multiple files. This is useful when you have rollover files. |
initial_file_name | Specifies the path and the name of a specific file in the audit file set where the file read should begin. If not required, pass NULL. |
audit_record_offset | Specifies a known location with the file specified for the initial_file_name parameter and begins the file read at this record. If not required, pass NULL. |
The sys.fn_get_audit_file() function returns the columns described in Table 6-9.
Table 6-9. sys.fn_get_audit_file() Columns
Column | Description |
---|---|
event_time | The date and time at which the audited event occurred. |
sequence_number | A sequence number of records within a single audit entry where the entry was too large to fit inside a buffer and was broken down. |
action_id | The id of the action. |
succeeded | Specifies if the action that caused the audit event to fire was successful. • 0: Indicates that the action failed. • 1: Indicates that the action succeeded. |
permission_bitmask | Where appropriate, specifies the permissions that were assigned or revoked. |
is_column_permission | Specifies if the permission (in the permission_bitmask column) was a column-level permission. • 0: Indicates that it was not a column-level permission. • 1: Indicates that it was a column-level permission. |
session_id | The id of the session in which the event occurred. |
server_principal_id | The principal id of the login that performed the action, which caused the audit event to fire. |
database_principal_id | The principal id of the database user that performed the action, which caused the audit event to fire. |
target_server_principal_id | Where applicable, returns the principal id of the login that was subject to a permission assignment or revocation. |
target_database_principal_id | Where applicable, returns the principal id of the database user that was subject to a permission assignment or revocation. |
object_id | Where applicable, returns the object id of the target object that caused the audit event to fire. |
class_type | The type of auditable entity on which the auditable event occurred. |
session_server_principal_name | The name of the login that the session was executing in. This is blank if no session was established. For example, where a failed login has been audited |
server_principal_name | The name of the login that performed the action, which caused the audit event to fire. |
server_principal_sid | The SID of the login that performed the action, which caused the audit event to fire. |
database_principal_name | The name of the database user that performed the action, which caused the audit event to fire. |
target_server_principal_name | Where applicable, returns the name of the login that was subject to a permission assignment or revocation. |
target_server_principal_sid | Where applicable, returns the SID of the login that was subject to a permission assignment or revocation. |
target_database_principal_name | Where applicable, returns the name of the database user that was subject to a permission assignment or revocation. |
server_instance_name | The serverinstance name of the instance where the audit event occurred |
database_name | The name of the database in which the audit event occurred. |
schema_name | The schema context in which the audit event occurred. |
object_name | The name of the object, which was the subject of the auditable event. |
statement | The T-SQL statement that caused the audit event to fire. |
additional_information | For some events, an XML document is returned, containing additional information. For example, if a failed login is audited, the additional information includes the IP address that the login attempt originated from. |
file_name | The fully qualified name of the audit file. |
audit_file_offset | The buffer offset of the audit record, within the file. |
user_defined_event_id | When an audit event has been written using sp_audit_write, returns the user defined event id. |
user_defined_information | When an audit event has been written using sp_audit_write, returns user defined additional information. |
The query in Listing 6-6 returns all records from all audit files stored within the c:audit folder.
Listing 6-6. Read an Audit File
SELECT * FROM sys.fn_get_audit_file('c:audit*',NULL,NULL) ;
Encryption Metadata
Chapter 5 discusses encryption in SQL Server, and as you can imagine, there are a raft of metadata objects that expose information regarding your encryption configuration. The following sections discuss useful metadata that is exposed around Always Encrypted and TDE.
Always Encrypted Metadata
Because there can be a one-to-many relationship between column master keys and column encryption keys, followed by a one-to-many relationship between column encryption keys and encrypted columns, metadata can be invaluable in keeping track of how your data is encrypted. The query in Listing 6-7 joins sys.tables and sys.columns to the new sys.column_encryption_keys, sys.column_encryption_key_values, and sys.column_master_keys catalog views to provide a complete path through the hierarchy from the column to key store location of the column master key.
The sys.column_encryption_keys view returns the columns described in Table 6-10.
Table 6-10. sys.column_encryption_keys Columns
Column | Description |
---|---|
name | The name of the column encryption key. |
column_encryption_key_id | The id of the column encryption key. |
create_date | The date and time that the key was created. |
modify_date | The date and time that the key was last modified. |
The sys.column_encryption_key_values view returns the columns described in Table 6-11.
Table 6-11. sys.column_encryption_key_values Columns
Column | Description |
---|---|
column_encryption_key_id | The id of the column encryption key. |
column_master_key_id | The id of the column master key that has been used to encrypt the column encryption key. |
encrypted_value | The value of the column encryption key, encrypted using the column master key. |
encryption_algorithm_name | The algorithm used to encrypt the column encryption key. |
The sys.column_master_keys view returns the columns described in Table 6-12.
Table 6-12. sys.column_master_keys
Column | Description |
---|---|
name | The name of the column master key. |
column_master_key_id | The id of the column master key. |
create_date | The date and time that the key was created. |
modify_date | The date and time that the key was last modified. |
key_store_provider_name | The type of key store that the column master key is stored in. |
key_path | The path to the key within the key store. |
Listing 6-7 demonstrates how these metadata objects can be drawn together.
Listing 6-7. Interrogate Always Encrypted Metadata
SELECT
t.name AS TableName
, c.name AS ColumnName
, c.encryption_type_desc
, c.encryption_algorithm_name
, cek.name AS ColumnEncryptionKeyName
, cev.encrypted_value
, cev.encryption_algorithm_name
, cmk.name as ColumnMasterKeyName
, cmk.key_store_provider_name AS column_master_key_store_provider_name
, cmk.key_path
FROM sys.columns c
INNER JOIN sys.column_encryption_keys cek
ON c.column_encryption_key_id = cek.column_encryption_key_id
INNER JOIN sys.tables t
ON c.object_id = t.object_id
JOIN sys.column_encryption_key_values cev
ON cek.column_encryption_key_id = cev.column_encryption_key_id
JOIN sys.column_master_keys cmk
ON cev.column_master_key_id = cmk.column_master_key_id ;
TDE Metadata
Note
For described information regarding TDE, please refer to Chapter 5.
TDE metadata is exposed through the sys.databases, sys.certificates, and sys.database_encryption_keys catalog views. The sys.databases catalog view contains a column called is_encrypted. This column has the data type of BIT and returns 0 if a database is not encrypted with TDE and 1 if it is encrypted. Information on the certificate used to encrypt the database encryption key is exposed through sys.certificates.
The sys.database_encryption_keys catalog view exposes details of the keys used to encrypt the databases. It returns one row for each database that has a database encryption key associated with it. Table 6-13 details the columns returned by this catalog view.
Table 6-13. sys.database_encryption_keys Columns
Column | Description |
---|---|
database_id | The id of the database that is encrypted using the key. |
encryption_state | Specifies the current state of encryption for the database indicated by the database_id column. The following are possible values: • 0: Indicates that no encryption key is present. You will not see this status under normal operations, because if no key exists, the catalog view does not return a row. • 1: Indicates that the database is not encrypted. You see this status when TDE has been encrypted, but the database encryption key has not been dropped. • 2: Indicates that the database is currently being encrypted. You see this status immediately after enabling TDE on a database while the background encryption thread is still running. • 3: Indicates that the database is encrypted. • 4: Indicates that a change to the database encryption key is currently in progress. • 5: Indicates that the database is currently being decrypted. You see this status immediately after turning off TDE for a database, before the background thread completes. • 6- Indicates that a change to the database encryption key or the server certificate used to encrypt the database encryption key is currently in progress. |
create_date | The date and time that the database encryption key was created. |
regenerate_date | The date and time that the database encryption key was regenerated. |
modify_date | The date and time that the database encryption key was last modified. |
set_date | The date and time that the database encryption key was associated with the database. |
opened_date | The date and time that the database encryption key was last opened. |
key_algorithm | The algorithm used to encrypt the database encryption key. |
key_length | The length of the key. |
encryptor_thumbprint | The encrypted value of the certificate used to encrypt the database encryption key. |
encryptor_type | Indicates the type of encryptor that was used to encrypt the database encryption key. Possible values are • ASYMMETRIC KEY • CERTIFICATE |
percent_complete | If the encryption_state column indicates a status of 2 or 5, this column indicates how far through the encryption or decryption process the background thread is. If the encryption_state column indicates a different status, then this column returns 0. |
The metadata exposed for TDE can be useful at various times; for example, to return a list of encrypted database on the instance, use the script in Listing 6-8.
Listing 6-8. Return a List of Encrypted Databases
SELECT name
FROM sys.databases
WHERE is_encrypted = 1 ;
If you need to ensure that all of the server certificates that are used to encrypt database encryption keys have been backed up, you can use the query in Listing 6-9. This query returns a list of certificates used in TDE that have not been backed up.
Listing 6-9. Ensure that Certificates Have Been Backed Up
SELECT
DB_NAME(dek.database_id) AS DatabaseName
,c.name AS CertificateName
FROM AdventureWorks2014.sys.dm_database_encryption_keys dek
INNER JOIN master.sys.certificates c
ON c.thumbprint = dek.encryptor_thumbprint
WHERE c.pvt_key_last_backup_date IS NULL ;
If you had a task of encrypting many databases on an instance, you could even use metadata to create a metadata-driven script that would do the hard work for you. I used this script recently—when a friend of mine mentioned that he had been quoted three months by his DBA team—to encrypt 400+ databases on an instance.
The script in Listing 6-10 firsts creates a server certificate that is used to encrypt the database encryption key for each database. The script then uses the sp_msforeachdb system stored procedure to loop around each database.
Inside the loop, the script first checks to ensure that it is not in the context of a system database, and then checks to ensure that the database has not already been encrypted. This makes the script re-runnable, should you have an issue part way through. After the checks are complete, it creates a database encryption key before enabling TDE.
Listing 6-10. Metadata Driven Encryption Script
USE master
GO
CREATE CERTIFICATE TDECert WITH SUBJECT = 'My DEK Certificate';
GO
EXEC sys.sp_MSforeachdb @command1 = 'USE ?
IF (SELECT DB_ID()) > 4
BEGIN
IF (SELECT is_encrypted FROM sys.databases WHERE database_id = DB_ID()) = 0
BEGIN
CREATE DATABASE ENCRYPTION KEY
WITH ALGORITHM = AES_128
ENCRYPTION BY SERVER CERTIFICATE TDECert
ALTER DATABASE ?
SET ENCRYPTION ON
END
END' ;
Securing Metadata
While metadata can prove incredibly useful—not just from the security perspective, but also in every other area of SQL Server administration, it can also prove to be a security hole in its own right. If metadata were accessible to everybody, then an attacker could use it to gain information, regarding the configuration of your instance.
Therefore, most metadata only becomes visible to a user after they have been granted permissions to use the object in some way. For example, if you grant the user Phil the SELECT permission on dbo.MyTable, Phil automatically sees the row within sys.tables and sys.objects that relates to the dbo.MyTable object.
If a user needs to see metadata about an object that they should not have permissions to use in any other way, then the VIEW DEFINITION permission can be granted upon that object. The VIEW DEFINITION permission can also be granted at the scope of a database or an entire instance. At the instance level, the permission VIEW ANY DEFIITION gives complete access to metadata instance-wide. This can be useful when you are creating metadata-driven automated scripts and you wish to apply the principal of least privilege.
There are some metadata objects in which users cannot be automatically granted VIEW DEFINITION permission when other permissions are assigned to the object. This is because the objects sit outside of the permissions structure. Take partitions, for example. Each table can be split across three partitions: one for in-row data, another for LOB (large object) data, and the third for overflow data. There is no way to assign permissions on partitions, because they are not directly accessible.
In these circumstances, the public role has the ability to view the associated metadata, and the VIEW DEFINITION permission does not apply. The metadata objects that are visible to the public role are as follows:
sys.partition_functions
sys.partition_range_values
sys.partition_schemes
sys.data_spaces
sys.filegroups
sys.destination_data_spaces
sys.database_files
sys.allocation_units
sys.partitions
sys.messages
sys.schemas
sys.configurations
sys.sql_dependencies
sys.type_assembly_usages
sys.parameter_type_usages
sys.column_type_usages
Risks of Metadata Visibility
Even with the security measures that are in place to protect SQL Server, an attacker may still be able to expose some metadata if the overall security design of your application is weak. For example, imagine that you have a web application that handles security in the application tier and then connects to a SQL Server instance by using a single, highly privileged account.
If the web application is vulnerable to SQL injection, then an attacker could force the execution of the query in Listing 6-11.
Listing 6-11. Forced Information Disclosure
SELECT 1 + name FROM sys.tables
When run against the AdventureWorks2016 database, the query in Listing 6-11 returns the error message shown in Figure 6-1.
Figure 6-1. Forced error message
This error message has provided the attacker with the following information:
There is a table in the database s ScrapReason.
The application is leaking metadata.
The application is (probably) running through a highly privileged account.
This information gives an attacker plenty of insight into where to start an attack. For example, if the attacker is correct and the application does run through a single account, then it is likely that there is a user’s table specifying permissions. The attacker could amend his query to filter by tables that contain the wildcard strings %user% or %login%. Once the attacker has this information, he can attack the table specifically and start spoofing user identities!
Tip
The moral of the story is that you should always evaluate the security profile of an application holistically to minimize the risk of attack. Even if your instance is secure, a poorly designed application tier could leave you vulnerable.
Summary
SQL Server exposes a vast amount of metadata. This includes metadata that relates to the security implementation within your instance. This security-related metadata assists a DBA in ensuring that the security policy is met. For example, metadata checks a user’s effective permissions at every level of the hierarchy, checks for modules that have not been code signed, and checks which principals have what permissions to a specific securable.
Useful metadata is also exposed about encryption artifacts. This metadata can be used for a variety of purposes, from auditing the column master key locations of an Always Encrypted implementation to automating a TDE implementation.
Although metadata brings many advantages, it also brings risks. If metadata is exposed, then it launches an attack against SQL Server. This is an even higher risk when an application uses a single, highly privileged user to connect to the database engine.