Quick Scan Report – Database Ownership

What this check looks for

Two ownership conditions, reported together:

  • The owner does not resolve to a login. SUSER_SNAME(owner_sid) returns null, usually because the person who created the database has left and their account was deleted.
  • The owner is a member of sysadmin. Which is very common, and is the one with the security consequence.

This page also covers issue 71, SSISDB not owned by sa, which is the same question applied to the Integration Services catalog.

The check is skipped on Amazon RDS. Note that it deliberately limits how often it calls SUSER_SNAME and IS_SRVROLEMEMBER, because on an instance with slow Active Directory those calls are slow.

Why it matters

The two conditions are different problems and the second is the serious one.

An unresolved owner breaks things quietly: cross database ownership chaining, TRUSTWORTHY behaviour, change data capture, Service Broker, and dbo mapping inside the database. None of it produces an error mentioning ownership. That case is covered in its own right by the database with no owner check.

An owner who is a sysadmin is a privilege escalation path, and specifically so when the database is also TRUSTWORTHY. The combination means:

  1. The database is marked TRUSTWORTHY, so code inside it is trusted outside it.
  2. The database owner is a sysadmin.
  3. Therefore code running as dbo in that database can be made to act with sysadmin rights on the whole instance.

Anyone who can create a stored procedure in that database, or anyone who compromises an application account with db_owner, can escalate to full control of the server. It is one of the best known escalation routes in SQL Server, it requires no exploit, and it is entirely a configuration mistake.

TRUSTWORTHY on its own is not the problem, and a sysadmin owner on its own is not the problem. Together they are. Which is why this check reports the ownership half: it is the half you can fix without breaking the feature that needed TRUSTWORTHY.

Restored databases are a common source. Restoring a database carries its owner SID with it, so a database restored from another instance often ends up owned by whoever restored it, who is usually a sysadmin.

How to confirm it yourself

SELECT d.[name],
       SUSER_SNAME(d.[owner_sid])                              AS [owner],
       d.[is_trustworthy_on],
       d.[is_db_chaining_on],
       CASE WHEN SUSER_SNAME(d.[owner_sid]) IS NULL THEN 'owner does not resolve'
            WHEN IS_SRVROLEMEMBER('sysadmin', SUSER_SNAME(d.[owner_sid])) = 1 THEN 'owner is sysadmin'
            ELSE 'ok' END                                      AS [finding]
  FROM sys.databases AS d WITH (NOLOCK)
 ORDER BY [finding], d.[name];

The dangerous combination, on its own:

SELECT [name], SUSER_SNAME([owner_sid]) AS [owner], [is_trustworthy_on]
  FROM sys.databases WITH (NOLOCK)
 WHERE [is_trustworthy_on] = 1
   AND [database_id] > 4
   AND IS_SRVROLEMEMBER('sysadmin', SUSER_SNAME([owner_sid])) = 1;

Any row from that query is worth acting on today.

How to fix it

Deal with TRUSTWORTHY first where both are true, because it is usually the one that should not be there at all:

ALTER DATABASE [YourDatabase] SET TRUSTWORTHY OFF;

Most databases with TRUSTWORTHY on do not need it. It gets set to make an unsigned CLR assembly work, or to let a procedure reach outside the database, and both have better answers: signing the assembly with a certificate, or using EXECUTE AS with a specific certificate based login. If turning it off breaks something, that tells you which feature depends on it and the fix is to sign the module properly rather than to trust the whole database.

Then set a sensible owner:

USE [YourDatabase];
GO
ALTER AUTHORIZATION ON DATABASE::[YourDatabase] TO [sa];

sa is the conventional answer: it always exists, it is not deleted when somebody leaves, and it grants the database nothing it did not already have. It works even when sa is disabled or renamed, which is a good practice in its own right.

Where TRUSTWORTHY genuinely has to stay on, do not use sa or any other sysadmin. Create a low privileged login purely to own that database:

CREATE LOGIN [DbOwner_YourDatabase] WITH PASSWORD = 'a strong password', CHECK_POLICY = ON;
ALTER LOGIN [DbOwner_YourDatabase] DISABLE;   -- it never needs to log in
ALTER AUTHORIZATION ON DATABASE::[YourDatabase] TO [DbOwner_YourDatabase];

A disabled login can own a database perfectly well, and it closes the escalation path while leaving the feature working.

Then stop it recurring. Set ownership as part of your restore and deployment runbooks, so a database restored by a sysadmin does not silently end up owned by one.

How long it takes

About two hours across an instance, most of it establishing whether any TRUSTWORTHY database genuinely needs it.


Report Why you would go there
Security Posture Ownership alongside the rest of the security surface.
Database Overview Owner and options for every database.
Logins Which logins exist and which are sysadmin.
master Server Permissions Server level grants that compound this.
Restore History Whether a restore is how the ownership got this way.
Orphan Users The database user side of the same disappearances.
Check
Database with no owner The unresolved owner case on its own terms.
SSISDB not owned by sa The same question for the SSIS catalog.
Orphan database users Users left behind by the same deleted logins.
Database Connections As SA Applications connecting with far too much privilege.
SQL logins with blank passwords or policy turned off The accounts an attacker would use to reach this.

Frequently asked questions

Everything works. Why change it? The unresolved case is a latent fault that surfaces at migration. The sysadmin plus TRUSTWORTHY case is a live security hole rather than a latent one.

Is sa really the right owner? For most databases, yes: it always exists and it survives people leaving. The exception is a TRUSTWORTHY database, where any sysadmin owner is the problem.

Can I change the owner while users are connected? Yes. ALTER AUTHORIZATION is a metadata change and does not need exclusive access.

Why does the check mention Active Directory being slow? Because SUSER_SNAME and IS_SRVROLEMEMBER resolve Windows principals, and on an instance with a slow or unreachable domain controller those calls are slow. The check limits how many it makes for that reason.