Types of MSSQL Users
| Column name | Data type | Description |
|---|---|---|
| name | sysname | Name of principal, unique within the database. |
| principal_id | int | ID of principal, unique within the database. |
| type | char(1) | Principal type: |
| type_desc | nvarchar(60) | Description of principal type. |
| default_schema_name | sysname | Name to be used when SQL name does not specify a schema. Null for principals not of type S, U, or A. |
| create_date | datetime | Time at which the principal was created. |
| modify_date | datetime | Time at which the principal was last modified. |
| owning_principal_id | int | ID of the principal that owns this principal. All fixed Database Roles are owned by dbo by default. |
| sid | varbinary(85) | SID (Security Identifier) of the principal. NULL for SYS and INFORMATION SCHEMAS. |
| is_fixed_role | bit | If 1, this row represents an entry for one of the fixed database roles: db_owner, db_accessadmin, db_datareader, db_datawriter, db_ddladmin, db_securityadmin, db_backupoperator, db_denydatareader, db_denydatawriter. |
| authentication_type | int | Applies to: SQL Server 2012 (11.x) and later. |
| authentication_type_desc | nvarchar(60) | Applies to: SQL Server 2012 (11.x) and later. |
| default_language_name | sysname | Applies to: SQL Server 2012 (11.x) and later. |
| default_language_lcid | int | Applies to: SQL Server 2012 (11.x) and later. |
| allow_encrypted_value_modifications | bit | Applies to: SQL Server 2016 (13.x) and later, SQL Database. |
Offensive reading of sys.database_principals
From an attacker perspective, this view is more useful than just a user list:[1][3]
type/type_descquickly tells you if you are dealing with a regular SQL user (S), Windows-backed principal (U/G), a role (R), or Microsoft Entra-backed principals (E/X).authentication_type_desc='DATABASE'is a strong hint of a contained user. These accounts live inside the database, are easy to miss if you only enumerate server logins, and often survive restores/migrations.authentication_type_desc='NONE'usually means a principal that cannot authenticate directly (for example someWITHOUT LOGIN, certificate-mapped, asymmetric-key, or role-style principals), but it can still matter for ownership chains andEXECUTE AS USER.owning_principal_idmatters when reviewing custom roles, application roles, or ownership chains.is_fixed_role=1only tells you that the principal is a built-in role. It does not show all effective permissions inherited through that role, so always enumerate role membership separately.
[!TIP] If your output looks incomplete, remember that metadata visibility is restricted. A low-privileged user can always see their own principal, system users, and fixed database roles, but not necessarily every other user or role member.
Quick enumeration
SELECT dp.name,
dp.type,
dp.type_desc,
dp.authentication_type_desc,
SUSER_SNAME(dp.sid) AS mapped_login,
USER_NAME(dp.owning_principal_id) AS owner_name,
dp.default_schema_name,
dp.create_date
FROM sys.database_principals AS dp
WHERE dp.name NOT IN ('sys', 'INFORMATION_SCHEMA')
ORDER BY dp.type_desc, dp.name;
SELECT r.name AS role_name,
m.name AS member_name,
m.type_desc,
m.authentication_type_desc
FROM sys.database_role_members AS drm
JOIN sys.database_principals AS r ON r.principal_id = drm.role_principal_id
JOIN sys.database_principals AS m ON m.principal_id = drm.member_principal_id
ORDER BY r.name, m.name;
SELECT grantee.name AS grantee,
target.name AS target_user,
perm.state_desc
FROM sys.database_permissions AS perm
JOIN sys.database_principals AS grantee ON grantee.principal_id = perm.grantee_principal_id
JOIN sys.database_principals AS target ON target.principal_id = perm.major_id
WHERE perm.class_desc = 'DATABASE_PRINCIPAL'
AND perm.permission_name = 'IMPERSONATE';
Useful quick triage for suspicious/high-value principals:
SELECT name, type_desc, authentication_type_desc
FROM sys.database_principals
WHERE name IN ('dbo', 'guest')
OR is_fixed_role = 1
OR type IN ('E', 'X')
OR authentication_type_desc IN ('DATABASE', 'EXTERNAL')
ORDER BY name;
Principals and roles worth special attention
dbo: this is not the same as thedb_ownerrole and not the same as the login recorded as database owner. If you can reachEXECUTE AS USER = 'dbo'orEXECUTE AS OWNER, explicitDENYon the original principal is not a reliable barrier anymore.guest: every database has it. IfCONNECTis available, users without a mapped database principal can inherit any permission granted toguest. Enumerate bothguestandpublicgrants when access seems broader than expected.- Contained users and
WITHOUT LOGINusers: contained users are good pivot and persistence indicators because they do not depend on a server login.WITHOUT LOGINusers cannot authenticate directly, but Microsoft explicitly notes that they can connect to other databases asguest, so they are especially interesting when you already control execution context changes. db_securityadmin: this role can manage permissions and custom role membership, so it can often be turned into a stronger position even when it is not instantsysadmin. Fixed-role membership changes still requiredb_owner.EXTERNAL_USER/EXTERNAL_GROUPS: in Azure SQL / SQL Managed Instance these are usually Microsoft Entra-backed identities. Do not ignore them during enumeration because they can own schemas, be role members, and participate in impersonation paths. Database-principal impersonation is supported even where server-level Entra impersonation is limited.- Special roles in
msdb: enumerate them separately. Microsoft documents thatdb_ssisadminanddc_admincan become privilege-escalation material because Integration Services packages may end up executing through SQL Server Agent in a high-privilege context.[1]
If you find interesting IMPERSONATE, db_owner, or linked-server paths, continue from the main MSSQL page:
Tooling
MSSQLPwner can enumerate linked-server and impersonation chains automatically:[2]
mssqlpwner corp.com/user:pass@10.10.10.10 enumerate
mssqlpwner corp.com/user:pass@10.10.10.10 get-chain-list
mssqlpwner corp.com/user:pass@10.10.10.10 interactive