PRACTITIONER GUIDE | DATABASE SECURITY
Practitioner GuideUpdated 13 min read

SQL Server Database Security Hardening: The 15-Point Audit Checklist for Instances Exposed to Application Workloads

xp_cmdshell
allows SQL Server to execute operating system commands directly from T-SQL -- disable unless a specific application requires it, then re-evaluate whether that application can be re-architected
sa
built-in SQL Server System Administrator account is a common brute-force target -- rename and disable if Windows Authentication is used for all connections
sysadmin
fixed server role grants unrestricted SQL Server access including xp_cmdshell execution regardless of whether xp_cmdshell is disabled -- audit sysadmin membership quarterly
TDE
Transparent Data Encryption protects data at rest in the database files and backups -- required for compliance frameworks including PCI DSS and HIPAA for sensitive data

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

SQL Server security hardening follows a predictable pattern: start with the feature surface area (disable everything not in use), move to account and privilege hardening (least privilege, no shared application accounts), configure auditing (failed logins, DDL changes, sensitive data access), and finish with encryption. The CIS SQL Server Benchmarks provide a detailed scoring framework, but this 15-point checklist covers the controls that appear most frequently in penetration test findings and breach investigations.

Surface Area Configuration: Disable Dangerous Features

Check 1: xp_cmdshell disabled

-- Check current state
SELECT name, value_in_use FROM sys.configurations WHERE name = 'xp_cmdshell';
-- Compliant if value_in_use = 0

-- Disable
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 0; RECONFIGURE;
EXEC sp_configure 'show advanced options', 0; RECONFIGURE;

Check 2: CLR integration disabled (if not required)

-- Check current state
SELECT name, value_in_use FROM sys.configurations WHERE name = 'clr enabled';

-- Disable
EXEC sp_configure 'clr enabled', 0; RECONFIGURE;

CLR integration allows custom .NET assemblies to run inside SQL Server. Attackers use CLR assemblies to execute arbitrary code. Disable unless specific application functionality requires it.

Check 3: Ad hoc distributed queries disabled

SELECT name, value_in_use FROM sys.configurations WHERE name = 'Ad Hoc Distributed Queries';
-- Should be 0

Check 4: OLE Automation disabled

SELECT name, value_in_use FROM sys.configurations WHERE name = 'Ole Automation Procedures';
-- Should be 0 -- allows COM object creation from T-SQL

Check 5: Database Mail XPs disabled (if not using Database Mail)

SELECT name, value_in_use FROM sys.configurations WHERE name = 'Database Mail XPs';
-- Should be 0 unless SQL Agent alert emails are required

Run all feature checks at once:

SELECT name, value_in_use AS [Current Value],
    CASE
        WHEN name IN ('xp_cmdshell','clr enabled','Ad Hoc Distributed Queries',
                      'Ole Automation Procedures','Database Mail XPs')
             AND value_in_use = 1 THEN 'FAIL - DISABLE'
        ELSE 'PASS'
    END AS [Security Status]
FROM sys.configurations
WHERE name IN ('xp_cmdshell','clr enabled','Ad Hoc Distributed Queries',
               'Ole Automation Procedures','Database Mail XPs')
ORDER BY name;

Account and Authentication Hardening

Check 6: sa account disabled or renamed

-- Check sa account status
SELECT name, is_disabled FROM sys.server_principals WHERE name = 'sa';
-- is_disabled should be 1

-- Disable sa account
ALTER LOGIN [sa] DISABLE;

-- Or rename (additional obfuscation if SA can't be disabled)
ALTER LOGIN [sa] WITH NAME = [sql_admin_disabled];

Check 7: Authentication mode -- Windows Authentication only (if no SQL auth required)

-- Check authentication mode (1 = Windows only, 2 = Mixed mode)
SELECT SERVERPROPERTY('IsIntegratedSecurityOnly') AS WindowsAuthOnly;
-- Should be 1 for Windows-only environments

Change via: SQL Server Management Studio > Server Properties > Security > Server authentication > Windows Authentication mode. Requires restart.

Check 8: Sysadmin membership audit

-- List all sysadmin members (should be minimal)
SELECT sp.name AS [Principal], sp.type_desc AS [Type], sp.is_disabled
FROM sys.server_role_members srm
JOIN sys.server_principals sp ON srm.member_principal_id = sp.principal_id
JOIN sys.server_principals r ON srm.role_principal_id = r.principal_id
WHERE r.name = 'sysadmin'
ORDER BY sp.type_desc, sp.name;
-- Expected: BUILTIN\Administrators, NT SERVICE\SQLSERVERAGENT, NT SERVICE\MSSQLSERVER
-- Unexpected: application service accounts, normal user accounts

Check 9: Password policy and lockout enabled for all SQL logins

-- SQL logins with password policy disabled
SELECT name, is_policy_checked, is_expiration_checked
FROM sys.sql_logins
WHERE is_policy_checked = 0 OR is_expiration_checked = 0;
-- All rows are findings -- set CHECK_POLICY=ON for each listed login

-- Fix example
ALTER LOGIN [app_user] WITH PASSWORD = N'[use current password]',
    CHECK_POLICY = ON, CHECK_EXPIRATION = ON;
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Linked Servers and External Connections

Check 10: Linked servers audit

-- List all configured linked servers
SELECT srv.name AS [Linked Server],
       srv.product AS [Product],
       srv.provider AS [Provider],
       ll.uses_self_credential,
       sp.name AS [Mapped Login],
       ll.remote_name AS [Remote Login]
FROM sys.servers srv
LEFT JOIN sys.linked_logins ll ON srv.server_id = ll.server_id
LEFT JOIN sys.server_principals sp ON ll.local_principal_id = sp.principal_id
WHERE srv.is_linked = 1
ORDER BY srv.name;

For each linked server found:

  1. Verify it is still operationally required
  2. Check what credential is used for the remote connection (column uses_self_credential = the current login's credential is forwarded, which is a lateral movement risk)
  3. Verify the remote login has the minimum required permissions on the remote server
  4. Remove unused linked servers: EXEC sp_dropserver 'LinkedServerName', 'droplogins';

Linked servers are a common lateral movement path: if an attacker compromises a SQL login with sysadmin on Server A that has a linked server to Server B running as sa, they have effectively compromised Server B as well.

Check 11: Endpoints and protocols -- disable unused ones

-- Check active SQL Server endpoints
SELECT name, endpoint_id, protocol_desc, state_desc, type_desc
FROM sys.endpoints
WHERE type_desc != 'TSQL';  -- Skip the default TSQL endpoint
-- Disable any HTTP, SERVICE_BROKER, or MIRRORING endpoints not in use

Database Permissions and Auditing

Check 12: Excessive database permissions -- PUBLIC role and db_owner audit

-- Find objects with permissions granted to PUBLIC (available to all users)
SELECT OBJECT_SCHEMA_NAME(major_id) AS [Schema],
       OBJECT_NAME(major_id) AS [Object],
       permission_name AS [Permission],
       state_desc
FROM sys.database_permissions
WHERE grantee_principal_id = DATABASE_PRINCIPAL_ID('public')
  AND class_desc = 'OBJECT_OR_COLUMN'
ORDER BY [Schema], [Object];

-- List all db_owner members (should only be DBA accounts)
SELECT dp.name AS [Principal], dp.type_desc
FROM sys.database_role_members drm
JOIN sys.database_principals dp ON drm.member_principal_id = dp.principal_id
JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id
WHERE r.name = 'db_owner'
ORDER BY dp.name;

Check 13: SQL Server Audit for failed logins and DDL changes

-- Check if SQL Server Audit is configured
SELECT name, status_desc, audit_file_path
FROM sys.server_audits;
-- Finding: no active audit objects means no centralized audit trail

-- Create a minimal server audit (writes to Application Event Log for SIEM forwarding)
CREATE SERVER AUDIT [SecurityAudit]
TO APPLICATION_LOG
WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);

CREATE SERVER AUDIT SPECIFICATION [LoginAuditSpec]
FOR SERVER AUDIT [SecurityAudit]
ADD (FAILED_LOGIN_GROUP),
ADD (SUCCESSFUL_LOGIN_GROUP),
ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP),
ADD (DATABASE_PERMISSION_CHANGE_GROUP);

ALTER SERVER AUDIT [SecurityAudit] WITH (STATE = ON);
ALTER SERVER AUDIT SPECIFICATION [LoginAuditSpec] WITH (STATE = ON);

Encryption and Data Protection

Check 14: Transparent Data Encryption (TDE) enabled on databases with sensitive data

-- Check encryption state of all user databases
SELECT db.name, dek.encryption_state, dek.encryption_state_desc,
       dek.key_algorithm, dek.key_length
FROM sys.databases db
LEFT JOIN sys.dm_database_encryption_keys dek ON db.database_id = dek.database_id
WHERE db.name NOT IN ('master','model','msdb','tempdb')
ORDER BY db.name;
-- encryption_state 3 = encrypted, 0 or NULL = not encrypted

-- Enable TDE (requires master key and certificate first)
USE master;
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '[use vault-managed password]';
CREATE CERTIFICATE TDECert WITH SUBJECT = 'TDE Certificate';
-- Back up the certificate IMMEDIATELY after creation
BACKUP CERTIFICATE TDECert TO FILE = N'C:\Secure\TDECert.cer'
    WITH PRIVATE KEY (
        FILE = N'C:\Secure\TDECertKey.pvk',
        ENCRYPTION BY PASSWORD = '[use vault-managed password]'
    );

USE YourDatabase;
CREATE DATABASE ENCRYPTION KEY WITH ALGORITHM = AES_256
    ENCRYPTION BY SERVER CERTIFICATE TDECert;
ALTER DATABASE YourDatabase SET ENCRYPTION ON;

Check 15: Force encrypted connections (TLS in transit)

-- Check current encryption setting (via registry, not T-SQL)
-- SQL Server Configuration Manager > SQL Server Network Configuration >
-- [Instance] Properties > Flags > Force Encryption = Yes

-- Verify client connections are encrypted
SELECT session_id, encrypt_option, auth_scheme, net_transport
FROM sys.dm_exec_connections
WHERE encrypt_option = 'FALSE';
-- Any results indicate unencrypted connections -- trace to client application and force encryption

The bottom line

SQL Server hardening follows the principle of reducing surface area: disable xp_cmdshell, CLR, linked servers, and OLE Automation unless each is specifically required and documented. Disable or rename the sa account, audit sysadmin membership quarterly, and enable SQL Server Audit for failed logins and permission changes. Enable TDE for databases with regulated data and force encrypted client connections via TLS. Run these 15 checks on each new SQL Server instance before it goes into production and re-run after each major configuration change.

Frequently asked questions

Does disabling xp_cmdshell actually prevent OS command execution by sysadmin users?

No. Sysadmin users can re-enable xp_cmdshell themselves: `EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;`. Disabling xp_cmdshell only prevents execution by users below sysadmin level. The real control is limiting who has sysadmin access. For defense in depth, disable xp_cmdshell (raises the bar for non-sysadmin attackers) and audit sysadmin membership closely. Additionally, run the SQL Server service under a low-privilege service account so that even if xp_cmdshell is used, the resulting OS commands run as a limited user.

Is TDE encryption sufficient for PCI DSS cardholder data environment compliance?

TDE satisfies the PCI DSS encryption at rest requirement (Requirement 3.5) for protecting stored cardholder data. However, TDE only encrypts data files and backups -- it does not encrypt data in transit between the application and SQL Server, data in SQL Server memory, or data visible in clear text to sysadmin logins. PCI DSS also requires encryption of cardholder data in transit (Requirement 4.2), which requires TLS between the application server and SQL Server. Always Encrypted (a SQL Server feature that encrypts column-level data at the client before it reaches SQL Server) provides stronger protection for the most sensitive columns.

What is the risk of leaving the BUILTIN\Administrators group in the sysadmin role?

The BUILTIN\Administrators group grants any local administrator on the SQL Server host automatic sysadmin access to SQL Server. This means any account that can escalate to local admin on the SQL Server OS (through a local privilege escalation vulnerability or a misconfigured service) automatically has sysadmin in SQL Server. Best practice is to remove BUILTIN\Administrators from the sysadmin role and use explicit, named Windows accounts or groups for SQL Server administration. This is a CIS SQL Server Benchmark Level 1 finding.

How do I audit which accounts have permission to read sensitive database columns?

Run: `SELECT dp.name AS [Principal], dp.type_desc, perm.permission_name, OBJECT_SCHEMA_NAME(perm.major_id) AS [Schema], OBJECT_NAME(perm.major_id) AS [Table], COL_NAME(perm.major_id, perm.minor_id) AS [Column] FROM sys.database_permissions perm JOIN sys.database_principals dp ON perm.grantee_principal_id = dp.principal_id WHERE perm.class = 1 AND perm.minor_id != 0 ORDER BY [Table], [Column], dp.name`. Column-level permissions in SQL Server are rarely used intentionally, so any results warrant review. For sensitive data discovery in columns, consider using SQL Server Data Discovery and Classification to tag columns containing PII, PCI, or HIPAA data, then audit access to those tagged columns.

What SQL Server audit events should be forwarded to a SIEM for security monitoring?

Configure SQL Server Audit with the following Server Audit Specification events: FAILED_LOGIN_GROUP (failed authentication attempts), LOGIN_CHANGE_PASSWORD_GROUP (password changes), SERVER_ROLE_MEMBER_CHANGE_GROUP (sysadmin membership changes), DATABASE_ROLE_MEMBER_CHANGE_GROUP (db_owner membership changes), and SCHEMA_OBJECT_PERMISSION_CHANGE_GROUP (permission grants and revocations). At the Database Audit Specification level: DATABASE_PERMISSION_CHANGE_GROUP and SELECT/INSERT/UPDATE/DELETE on high-sensitivity tables if table-level auditing is required for compliance. Forward SQL Server audit logs to your SIEM via the Windows Application event log (if using Windows log destination) or via a log forwarding agent reading from the audit file share.

How do I prevent SQL Server sysadmin privilege escalation via linked server abuse?

Linked servers in SQL Server can be configured with a security context that grants the connecting user elevated privileges on the remote server. Audit all linked servers with: SELECT name, product, provider, data_source, is_remote_login_enabled FROM sys.servers WHERE is_linked=1. For each linked server, check the login mapping: EXEC sp_helplinkedsrvlogin. Linked servers that use a fixed sysadmin login on the remote server effectively grant any user who can execute queries on the local server sysadmin access on the remote. Remove unnecessary linked servers. For required linked servers, use per-login mapping rather than a global self-mapping, and restrict which users have EXECUTE access to linked server queries via DENY EXECUTE on the linked server procedures. Monitor for EXECUTE AT LINKED SERVER patterns in extended events or SQL audit.

Sources & references

  1. CIS Microsoft SQL Server Benchmarks
  2. Microsoft: SQL Server security best practices
  3. Microsoft: Surface area configuration for SQL Server

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.