Secure Microsoft SQL Server with cnspec
Scan Microsoft SQL Server instances and databases against security and compliance best practices with cnspec.
Scan your Microsoft SQL Server instances to find security risks before they become incidents. cnspec evaluates server logins and roles, permissions, surface-area configuration, database ownership and trustworthiness, encryption, credentials, linked servers, SQL Agent proxies, and audit configuration.
cnspec connects over TDS and inventories the instance through read-only catalog queries against sys.* and msdb.*. It never touches your data.
If you're new to cnspec, start with the Quickstart. For an overview of every database cnspec can scan, see the database scanning overview.
Prerequisites
To scan SQL Server with cnspec, you must have:
- cnspec installed on your workstation
- Network access to the instance on its TDS port (1433 by default)
- A login that can read the instance catalog
Authenticate
cnspec authenticates with a SQL login, Windows (NTLM) integrated authentication, or a Microsoft Entra ID access token.
cnspec shell mssql sql.contoso.com --user sa --ask-passWindows or Entra authentication, and named instances:
cnspec shell mssql sql.contoso.com --instance SQL2022 --auth windows --user 'CONTOSO\auditor' --ask-pass
cnspec shell mssql sql.contoso.com --auth azure --user auditor@contoso.com --token ACCESS_TOKENPrefer a least-privileged login for auditing. VIEW ANY DEFINITION, or the ##MS_DefinitionReader## role on SQL Server 2022 and later, lets cnspec see all server principals and permissions. Without it some principals are simply invisible rather than causing a failure, so grant it before you trust a passing result.
Connection options
| Option | Description |
|---|---|
--host | Instance hostname or IP address (also accepted as the positional argument) |
--port | Instance port (default 1433) |
--instance | Named instance, resolved through SQL Browser when the port is unknown |
--user, -u | Login name (sa, DOMAIN\user, or a user principal name) |
--password, -p | Password, or --ask-pass to be prompted |
--auth | sql (default), windows, or azure |
--token | Microsoft Entra ID access token, for --auth azure |
--database | Scope the connection to a single database |
--encrypt | strict, mandatory (default), optional, or disable |
--trust-server-certificate | Skip TLS certificate validation |
--discover | auto (default), all, databases, instance, or none |
Verify with a quick SQL Server check
cnspec shell mssql sql.contoso.com --user sa --ask-passcnspec> mssql.server { version edition isMixedModeAuthEnabled }
mssql.server: {
version: "Microsoft SQL Server 2022 (RTM-CU26) ... Developer Edition (64-bit) on Linux ..."
edition: "Developer Edition (64-bit)"
isMixedModeAuthEnabled: true
}If cnspec connects and shows the version, you're ready to scan.
Scan SQL Server
By default cnspec discovers each online database as its own mssql-database asset alongside the instance asset:
cnspec scan mssql sql.contoso.com --user auditor --ask-passTo scan the instance only:
cnspec scan mssql sql.contoso.com --user auditor --ask-pass --discover noneWhen a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.
Mondoo doesn't yet ship an out-of-the-box SQL Server policy. The resources are shaped around the CIS Microsoft SQL Server Benchmark, so the checks below map closely to its recommendations. Use them as a starting point and create your own policies to meet your requirements.
Explore and test checks interactively
cnspec shell mssql sql.contoso.com --user auditor --ask-passList server logins
cnspec> mssql.server.logins { name type isDisabled }Read a surface-area configuration option
cnspec> mssql.server.configurations.where(name == "clr enabled") { name valueInUse }
mssql.server.configurations.where: [
0: {
name: "clr enabled"
valueInUse: 0
}
]List databases and their encryption state
cnspec> mssql.server.databases { name isTrustworthy isEncrypted ownerName stateDesc }List linked servers
cnspec> mssql.server.linkedServers { name product provider dataSource isDataAccessEnabled }Inspect a database's backup history
cnspec> mssql.server.databases.where(name == "TESTDB").first.backups { type isEncrypted keyAlgorithm }List server audits
cnspec> mssql.server.audits { name isEnabled destination onFailure }Example security checks
Ensure the sa login is disabled
cnspec> mssql.server.logins.where(name == "sa").all(isDisabled == true)
[ok] value: trueEnsure Windows authentication mode is enforced
Mixed mode leaves SQL logins usable, which puts password policy outside of Active Directory:
cnspec> mssql.server.isMixedModeAuthEnabled == false
[ok] value: trueEnsure the CLR is disabled
cnspec> mssql.server.configurations.where(name == "clr enabled").all(valueInUse == 0)
[ok] value: trueEnsure xp_cmdshell is disabled
xp_cmdshell runs operating system commands from inside the database engine:
cnspec> mssql.server.configurations.where(name == "xp_cmdshell").all(valueInUse == 0)
[ok] value: trueEnsure Ad Hoc Distributed Queries are disabled
cnspec> mssql.server.configurations.where(name == "Ad Hoc Distributed Queries").all(valueInUse == 0)
[ok] value: trueEnsure no database is marked Trustworthy
A Trustworthy database lets code inside it reach outside its own boundary, so a compromise of the database escalates to the instance:
cnspec> mssql.server.databases.none(isTrustworthy == true)
[ok] value: trueEnsure user databases are encrypted at rest
Database IDs 1 through 4 are the system databases (master, tempdb, model, msdb), so filtering above 4 leaves the databases you created:
cnspec> mssql.server.databases.where(databaseId > 4).all(isEncrypted == true)
[ok] value: trueEnsure backups are encrypted
cnspec> mssql.server.databases.all(backups.all(isEncrypted == true))
[ok] value: trueEnsure SQL logins enforce Windows password policy
A SQL login created without CHECK_POLICY sits outside password complexity and lockout rules entirely:
cnspec> mssql.server.logins.where(isActiveDirectoryPrincipal == false && isFixedRole == false).all(isPolicyChecked == true)
[ok] value: trueEnsure the instance forces encrypted client connections
cnspec> mssql.server.forceEncryption == true
[ok] value: trueEnsure at least one audit is running
An audit that is defined but not enabled records nothing:
cnspec> mssql.server.audits.any(isEnabled == true)
[ok] value: trueEnsure no CLR assembly is registered as UNSAFE
An UNSAFE_ACCESS assembly runs arbitrary code with no sandbox:
cnspec> mssql.server.databases.all(clrAssemblies.where(isUserDefined).none(permissionSet == "UNSAFE_ACCESS"))
[ok] value: trueEnsure no SQL Agent proxy is available to everyone
A proxy accessible to the msdb public role lets any job author run steps as the proxy's credential:
cnspec> mssql.server.proxyAccounts.none(isAccessibleToPublic == true)
[ok] value: trueReview linked servers and how they authenticate
Each linked server is a trust relationship to another instance. usesSelfCredential means the caller's own identity is forwarded, so the link inherits whatever the caller holds:
cnspec> mssql.server.linkedServers { name dataSource provider linkedLogins { localLogin remoteName usesSelfCredential } }Review who holds CONTROL SERVER
cnspec> mssql.server.permissions.where(permissionName == "CONTROL SERVER") { granteeName state }Learn more
- Microsoft SQL Server Resource Pack Reference: every SQL Server resource and field cnspec can query
- Write Effective MQL: guide to authoring checks and queries