How SQL Server maintenance becomes an attack path – and what to do about it

Comments 0

Share to social media

This article takes one practical thread from Fabiano Amorim’s broader SQL Server security research: how apparently normal permissions and routine maintenance can become an attack path. The goal is not to walk through a full proof of concept, but to help DBAs answer a more useful production question: how do I know whether my SQL Server instances are exposed to this kind of risk? 

The article covers four connected areas: over-privileged database users, msdb and SQL Server Agent exposure, privileged maintenance jobs, and trigger-based permission hijacking.

For each area, we will look at why the risk exists, what signs a DBA should look for, what specifically to monitor, and how to mitigate the exposure.

This article is part of Fabiano Amorim’s complete guide to SQL Server security. However, the article also takes learnings from Fabiano’s other series, in which he uncovers SQL Server vulnerabilities you may not be aware of.

SQL Server – secure by default?

SQL Server is often marketed or perceived as secure by default, but real-world testing shows that default installations and default permissions still expose several attack surfaces. With this in mind, “secure by default” does not mean “safe without review.” 

Many risks come from legacy behavior, backward compatibility, broad permissions granted to the public role, system procedures, implicit trust boundaries, and administrative convenience features. 

The risk appears when they are combined: a database user with more permissions than necessary, a maintenance job running as a highly privileged owner, an unexpected trigger, or an impersonated module that changes how permissions are evaluated. 

The attack path explained

Real SQL Server security incidents rarely require one catastrophic bug. They often come from chaining smaller weaknesses that were each accepted as normal operational practice.

A simplified chain looks like this: 

  • An application login, support login, or database user is granted db_ownerdb_ddladmindb_securityadmin, or broad ALTER permissions in a production database. 
  • That principal can create or modify objects such as stored procedures, DDL (data definition language) triggers, DML (data manipulation language) triggers, or modules that execute as dbo or owner
  • A maintenance job later touches the database while running as sa, a sysadmin login, or a highly privileged SQL Agent proxy. 
  • The trigger or module runs during that trusted operation and inherits the permissions of the caller that fired it. 
  • The result is privilege escalation, audit evasion, unwanted configuration changes, or unauthorized data access. 

The important point is that none of these items may look suspicious when reviewed in isolation. DBAs create maintenance jobs. Applications sometimes need deployments. SQL Agent often runs administrative workflows. Triggers are a normal SQL Server feature. The security problem is the trust boundary between them. 

Over-privileged database users in SQL Server: why (and how) normal permissions can become dangerous

The db_owner fixed database role is frequently treated as a convenient application role. It is not. A db_owner member controls the database security boundary.

The user can create and alter objects, define triggers, change permissions, create modules with elevated execution context, and often influence operations performed later by privileged users or automated jobs. 

The same concern applies to db_ddladmindb_securityadmindb_accessadmin, CONTROL DATABASE, ALTER ANY USER, ALTER ANY ROLE, ALTER ANY SCHEMA, and broad ALTER permissions on important schemas.

These rights may be necessary for controlled deployment accounts, but they are usually too powerful for runtime application accounts and permanent support accounts. 

What are the warning signs a DBA should look for? 

  • Application service accounts, reporting users, vendor users, or generic support logins in db_owner
  • Permanent membership in db_ddladmindb_securityadmin, or db_accessadmin
  • Users granted CONTROL DATABASE, ALTER ANY SCHEMA, ALTER ANY USER, ALTER ANY ROLE, or IMPERSONATE
  • Unexpected created or modified procedures, triggers, views, synonyms, or assemblies in production. 

Protect your data. Demonstrate compliance.

With Redgate, stay ahead of threats with real-time monitoring and alerts, protect sensitive data with automated discovery & masking, and demonstrate compliance with traceability across every environment.
Learn more

What to monitor, and how

Area What to check How to monitor 
Role membership Members of: 
db_owner
db_ddladmin
db_securityadmin
db_accessadmin
SQLAgentUserRole
SQLAgentReaderRole
SQLAgentOperatorRole
Daily catalog checks and SQL Audit DATABASE_ROLE_MEMBER_CHANGE_GROUP. 

Alert on changes outside approved deployment windows. 
Database permissions CONTROL DATABASE
ALTER
ALTER ANY SCHEMA
ALTER ANY USER

ALTER ANY ROLE
IMPERSONATE
CREATE ASSEMBLY
CREATE PROCEDURE
CREATE TRIGGER
Query sys.database_permissions in every user database. Baseline expected permissions and diff against that baseline. 
Risky database settings TRUSTWORTHY
DB_CHAINING
owner_sid
containment

Service Broker activation procedures CLR settings
Query sys.databases and database scoped metadata. Treat deviations as configuration drift. 
Object change activity CREATE, ALTER, and DROP activity for modules, triggers, schemas, users, roles, and assemblies. SQL Audit DATABASE_OBJECT_CHANGE_GROUP
SCHEMA_OBJECT_CHANGE_GROUP
DATABASE_PRINCIPAL_CHANGE_GROUP
DATABASE_PERMISSION_CHANGE_GROUP
Use DDL triggers only for logging, not as the only control. 
  • Separate deployment permissions from runtime permissions. The account that deploys schema changes should not be the same account used by the application during normal operations. 
  • Replace db_owner with custom roles that grant only the required SELECT, INSERT, UPDATE, DELETE, and EXECUTE permissions. 
  • Make elevated access temporary, approved, and auditable. Remove it after the deployment or support task is complete. 
  • Keep TRUSTWORTHY OFF unless there is a documented, reviewed, and tested requirement. Do not use TRUSTWORTHY as a shortcut for cross-database access. 
  • Use dedicated low-use database owner logins. Avoid making user databases owned by sa, domain administrators, or application runtime accounts. 
  • Build a permission baseline and alert on drift, rather than relying on occasional manual reviews. 

Impersonation and module execution context: why EXECUTE AS can defeat your assumptions 

Impersonation is a legitimate SQL Server feature. It allows a module to run under another database or server principal so users can perform a controlled task without receiving broad direct permissions.

The danger appears when impersonation is used as a blanket elevation mechanism, especially through EXECUTE AS OWNER or EXECUTE AS dbo, or when dynamic SQL is executed inside an impersonated module. 

DBAs sometimes assume an explicit DENY is a reliable backstop. That assumption can fail if the principal can later run the operation under a different execution context.

For example, a user who can become dbo, directly or indirectly, can perform actions as dbo even when those actions would be denied to the original user. This is one reason db_owner membership is so sensitive: it changes what the user can become, not only what the user can do directly. 

What are the warning signs a DBA should look for?

  • Modules created with EXECUTE AS OWNER, EXECUTE AS dbo, or an unusually privileged user. 
  • Explicit IMPERSONATE permissions granted to application, vendor, support, or developer accounts. 
  • Databases with TRUSTWORTHY ON, and modules that execute as owner or dbo. 
  • Users manually running EXECUTE AS USER or EXECUTE AS LOGIN outside of approved administrative workflows. 

What to monitor, and how 

Area What to check How to monitor 
Module context Stored procedures, triggers, functions, and views with execute_as_principal_id set. Query sys.sql_modules regularly in each database.

Review every module using EXECUTE AS that is not part of an approved pattern. 
Impersonation grants Database and server IMPERSONATE permissions, especially on dbo, sa, privileged logins, or service accounts. Query sys.database_permissionsand sys.server_permissions.

Alert when IMPERSONATE is granted or denied outside approved changes. 
Runtime use EXECUTE AS USER
EXECUTE AS LOGIN
REVERT
Other modules running under unexpected principals. 
Extended Events:
audit_database_principal_impersonation_event  and audit_server_principal_impersonation_event.

SQL Audit for permission and principal changes. 
Dynamic SQL under elevation EXEC
sp_executesql
OPENQUERY
linked server execution
xp_cmdshell
OLE Automation
external scripts
Static code review plus XE sql_statement_completed and module_end for high-risk modules. 
  • Avoid EXECUTE AS dbo and EXECUTE AS OWNER as a default pattern. Use them only when the trust boundary is fully understood. 
  • Prefer certificate-signed modules for controlled privilege elevation. Module signing can grant a specific permission to a specific module without broadly changing the caller’s execution context. 
  • Remove unnecessary IMPERSONATE grants. Treat IMPERSONATE on privileged principals as equivalent to a high-privilege administrative grant. 
  • Do not allow unparameterized dynamic SQL inside impersonated modules. Parameterize, validate identifiers with QUOTENAME where appropriate, and avoid concatenating user-controlled input. 
  • Review modules again after restores, vendor deployments, and application upgrades. These are common moments for elevated execution context to appear unexpectedly. 

msdb and SQL Server Agent exposure: why msdb is a server-level security boundary

msdb is not just a place where job history is stored. It contains SQL Server Agent jobs, job steps, schedules, operators, alerts, credentials, proxies, backup and restore history, Database Mail metadata, maintenance plans, and SSIS-related metadata. A user with excessive permissions in msdb may be able to influence how privileged automation runs on the instance. 

SQL Server Agent is especially important because a job step may run as the SQL Agent service account, as the job owner, or through a proxy credential depending on the subsystem and configuration.

A job that looks like routine maintenance can become a privilege boundary if a lower-privileged user can modify the job, modify objects that the job touches, or control parameters used by the job. 

What are the warning signs a DBA should look for?

  • Non-DBA users in SQLAgentUserRoleSQLAgentReaderRole, or SQLAgentOperatorRole
  • Jobs owned by sa or sysadmin logins where the job touches databases controlled by non-sysadmin db_owner users. 
  • Job steps that execute dynamic T-SQL, PowerShell, CmdExec, SSIS packages, replication agents, or external scripts. 
  • Proxies and credentials that are unused, shared across unrelated jobs, or more privileged than needed. 
  • Jobs created or modified by unexpected users, especially outside deployment windows. 
  • Maintenance jobs that run against all databases without accounting for untrusted database owners or triggers. 

What to monitor and how 

Area What to check How to monitor 
Agent roles Membership in:
msdb SQLAgentUserRole
SQLAgentReaderRole
SQLAgentOperatorRole 
Query msdb.sys.database_role_members.

Alert when non-DBA accounts are added. 
Job ownership Jobs owned by sa, sysadmin logins, disabled logins, vendor accounts, or generic accounts. Query msdb.dbo.sysjobs and sys.server_principals.

Baseline expected owners. 
Job changes Job creation, deletion, step changes, schedule changes, proxy changes. SQL Audit object/permission changes in msdb.

Extended Events 
rpc_completed and
sql_batch_completed 
filtered on:
sp_add_job
sp_update_job
sp_add_jobstep
sp_update_jobstep
sp_delete_job
sp_start_job 
Job execution Unexpected manual starts, failures, reruns, long-running steps, commands changed before execution. msdb.dbo.sysjobhistory, sysjobactivity, SQL Agent logs, and Windows event logs. Alert on failure patterns and changes near execution time. 
Credentials/proxies Proxy mappings to subsystems, credential identities, jobs using high-privilege proxies. Query msdb proxy and credential metadata. Review with Windows/local admin team for actual OS rights. 
  • Restrict SQL Agent role membership. Do not grant SQLAgentOperatorRole or SQLAgentReaderRole broadly just because a user needs to see job status. 
  • Use dedicated job owner accounts. Avoid using personal DBA accounts as job owners. Avoid sa as the default owner unless there is a clear reason and compensating monitoring. 
  • Separate job administration from database ownership. A user who controls a database should not automatically be able to influence a sysadmin-owned job that touches that database. 
  • Review job steps for dynamic SQL and external execution. CmdExec, PowerShell, SSIS, and external scripts deserve special attention because they can cross the database/host boundary. 
  • Remove unused proxies and credentials. For required proxies, use least-privilege Windows accounts and limit each one to only the necessary subsystems. 
  • Baseline job definitions and alert when a job command, owner, proxy, subsystem, or schedule changes. 

Privileged maintenance jobs and trigger hijacking: why maintenance can become an escalation path

This is the most subtle part of the chain. DML (data manipulation language) and DDL (data definition or declaration language) triggers don’t execute under the permissions of the trigger creator. Instead, they execute when another statement fires them, and the effective context is tied to the caller and the execution path that fired the trigger. If a highly privileged maintenance job performs an operation that fires a trigger in a database controlled by a lower-privileged db_owner, the trigger may be able to hijack the permissions of that privileged operation. 

This matters for consolidated SQL Server instances where different teams or applications own different databases, but central DBA jobs run maintenance across all databases. The local db_owner may not be sysadmin. The maintenance job may be sysadmin. The trigger is the bridge between them. 

The risk is not limited to intentionally malicious users. It also affects untrusted restores, vendor databases, old applications, databases migrated from other servers, and environments where developers or third parties have broad database rights. 

Important caveat: BACKUP DATABASE and DBCC CHECKDB don’t fire the same DDL triggers as operations such as ALTER INDEX or UPDATE STATISTICS. The exposure depends on what the maintenance job actually does. Inventory the job steps before deciding which databases are safe to touch with privileged context. 

What are the warning signs a DBA should look for?

  • DDL triggers at database scope, especially triggers listening to broad event groups such as DDL_DATABASE_LEVEL_EVENTS
  • Triggers modified shortly before a privileged job runs. 
  • Triggers containing ALTER SERVER ROLE, CREATE LOGIN, GRANT, IMPERSONATExp_cmdshell, OLE Automation, linked server calls, external script execution, or dynamic SQL. 
  • Maintenance jobs running as sa or sysadmin across databases owned by application teams, vendors, or restored from external sources. 
  • Databases with TRUSTWORTHY ON and sysadmin ownership. This can defeat the protective sandbox that EXECUTE AS USER would otherwise provide. 

What to monitor and how 

Area What to check How to monitor 
Database triggers DML and DDL triggers, disabled status, create/modify dates, parent object, event group coverage. In each database, query the following:
 sys.triggers
sys.trigger_events
sys.sql_modules
Also, alert on
CREATE/ALTER/DROP TRIGGER
Server triggers Server-level DDL triggers and logon triggers. Query the following:
sys.server_triggers
sys.server_trigger_events
sys.server_sql_modules
Also, audit SERVER_OBJECT_CHANGE_GROUP
Suspicious trigger content Dynamic SQL, server-role changes, login creation, GRANT/ALTER AUTHORIZATIONxp_cmdshell, sp_OA*, external scripts, linked-server execution. Static code scanning of trigger definitions. XE/SQL Audit for module execution and privileged statements. 
Maintenance execution context Which login owns the job, whether the step runs as a proxy, whether the job uses EXECUTE AS, and which databases are touched. Review msdb job metadata and job history. Capture job start/stop and command text changes. Baseline all maintenance jobs. 
Privileged operations fired inside user DBs ALTER INDEX, UPDATE STATISTICS, schema changes, data cleanup, ETL changes, replication/CDC maintenance. XE sql_statement_completed
object_altered
object_created
object_deleted
SQL Audit DATABASE_OBJECT_CHANGE_GROUP and SCHEMA_OBJECT_CHANGE_GROUP

A safer maintenance pattern 

When a central maintenance job must run DDL or DML inside databases that may be controlled by non-sysadmin owners, avoid letting the job’s server-level privileges be hijacked by code inside those databases. One defensive pattern is to execute the database-level maintenance under a database user context, commonly dbo, so the operation remains inside the database boundary instead of carrying the sysadmin token into trigger execution.

This pattern is useful because a database-level EXECUTE AS context does not automatically carry server-level privileges. However, it’s not magic. If the database is TRUSTWORTHY ON and owned by a sysadmin, or if unsafe cross-database ownership chains exist, the protection may be weakened or bypassed. Always review database owner, TRUSTWORTHY, DB_CHAINING, and module execution context before relying on this pattern. 

If you use Ola Hallengren’s Maintenance Solution, review the @ExecuteAsUser option for maintenance commands that run inside user databases. This can help reduce the risk of server-level permission hijacking during index and statistics maintenance, provided the database trust settings are also safe. 

  • Don’t run all maintenance across all databases as an unrestricted sysadmin context without considering database trust boundaries. 
  • Inventory triggers before onboarding a database into central maintenance, especially after restore, migration, vendor deployment, or application upgrade. 
  • Wrap database-local DDL/DML maintenance in a database-level EXECUTE AS context where appropriate and tested. 
  • Use dedicated maintenance accounts and job owners. Avoid personal accounts. Avoid using sa as a convenience default for every job. 
  • Disable or remove unexpected triggers. Require change approval for any trigger that can fire during privileged maintenance. 
  • Keep TRUSTWORTHY OFF and avoid sysadmin-owned user databases unless explicitly required and documented. 
  • Consider running maintenance for untrusted or vendor databases in separate jobs with additional logging and a more restrictive execution context. 

A practical monitoring implementation for SQL Server – what you should do

A good SQL Server monitoring design combines configuration checks, runtime telemetry, and change auditing. None of these controls is enough on its own, though. You’ll also benefit from catalog queries, which tell you what exists now, and SQL Audit, covering who changed permissions and objects.

There’s also Extended Events, which helps capture runtime behavior, and SQL Agent history explains when automation actually ran. 

You may also be interested in…

The complete guide to auditing SQL Server

The exact audit configuration depends on edition, compliance requirements, storage location, and performance tolerance, but the following action groups are a practical starting point for this topic:

  • DATABASE_ROLE_MEMBER_CHANGE_GROUP – changes to database role membership, including db_owner and SQL Agent fixed database roles in msdb. 
  • SERVER_ROLE_MEMBER_CHANGE_GROUP – changes to sysadmin and other fixed server roles. 
  • DATABASE_PERMISSION_CHANGE_GROUP and SERVER_PERMISSION_CHANGE_GROUP – permission grants, denies, and revokes. 
  • DATABASE_PRINCIPAL_CHANGE_GROUP and SERVER_PRINCIPAL_CHANGE_GROUP – user and login creation, alteration, and deletion. 
  • DATABASE_OBJECT_CHANGE_GROUP and SCHEMA_OBJECT_CHANGE_GROUP – creation, alteration, or deletion of procedures, triggers, schemas, and other database objects. 
  • SERVER_OBJECT_CHANGE_GROUP – changes to server-level objects, including server-level triggers. 
  • SCHEMA_OBJECT_ACCESS_GROUP – optional, higher-volume auditing for execution/access to specific high-risk procedures or schemas. 

Use Extended Events for targeted telemetry. Avoid capturing every statement on every production server without filters. Start with a small, high-signal session and expand only where needed. 

Operational checklist for monitoring SQL Server

  • Daily: review changes to sysadmin membership, db_owner membership, SQL Agent roles, job owners, job steps, and trigger definitions. 
  • Weekly: compare permissions, database settings, module execution contexts, and SQL Agent proxies against the approved baseline. 
  • Before maintenance onboarding: inspect the target database for triggers, TRUSTWORTHY, DB_CHAINING, owner, CLR assemblies, Service Broker activation, and EXECUTE AS modules. 
  • After restore or vendor deployment: treat the database as untrusted until programmable objects, owners, permissions, triggers, and jobs are reviewed. 
  • After incident or suspicious change: preserve SQL Audit files, XE files, SQL Agent logs, default trace data if still available, Windows event logs, and msdb job history before cleanup. 

Future-proof database monitoring with Redgate Monitor

Multi-platform database observability for your entire estate. Optimize performance, ensure security, and mitigate potential risks with fast deep-dive analysis, intelligent alerting, and AI-powered insights.
Learn more & try for free

A prioritized SQL Server hardening plan 

If you need to turn this guidance into an action plan, start with the changes that reduce the largest number of attack paths without requiring application redesign. 

  1. Inventory and remove permanent db_owner from application, support, vendor, and reporting accounts. 
  1. Review msdb role membership and SQL Agent job ownership. Move jobs to dedicated owner accounts and remove unnecessary operators. 
  1. Baseline SQL Agent jobs and alert on job command, owner, proxy, subsystem, and schedule changes. 
  1. Inventory DDL and DML triggers across all user databases and server scope. Investigate recently modified triggers first. 
  1. Review all modules using EXECUTE AS OWNER, EXECUTE AS dbo, or privileged execution context. 
  1. Keep TRUSTWORTHY OFF and avoid sysadmin-owned user databases unless there is a documented exception. 
  1. Implement SQL Audit for role, permission, principal, and object changes. Add targeted XE sessions for impersonation, trigger changes, and SQL Agent procedure calls. 
  1. Change maintenance jobs so database-local operations run in a safer database-level context where appropriate and tested. 
  1. Create a restore intake process for untrusted or vendor databases before they’re introduced to production maintenance jobs. 

Conclusion 

SQL Server security is not only about disabling obvious risky features. Instead, it’s more about understanding where trust crosses boundaries.

db_owner user, for example, is not merely a user who can write data. msdb is not merely a history database. SQL Agent is not merely a scheduler. A trigger is not merely application logic. When these features interact, routine administration can become an attack path. 

DBAs are in the best position to reduce this risk because the exposure is usually visible in the metadata: role membership, job ownership, proxy usage, trigger definitions, module execution context, database ownership, and trust settings. As such, finding the data is easy. Treating the data as a security boundary and continuously monitoring it, however, is more difficult.

Moving from secure by default to just secure means verifying every privilege that can change code, every job that runs with elevated rights, and every database that central automation touches. Once those trust relationships are visible, they can be reduced, monitored, and controlled.

Simple Talk is brought to you by Redgate Software

Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.
Discover how Redgate can help you

FAQs: How to stop SQL Server maintenance becoming an attack path

1. Is SQL Server secure by default?

Not entirely. Default installations and default permissions still leave several attack surfaces open, including broad public role grants, legacy compatibility features, and administrative conveniences. “Secure by default” doesn’t mean a DBA can skip reviewing role membership, permissions, and trust settings.

2. What is trigger hijacking in SQL Server?

Trigger hijacking happens when a DML or DDL trigger created by a lower-privileged user fires during an operation run by a highly privileged process, such as a sysadmin-owned maintenance job. Because the trigger’s effective context is tied to the caller that fired it, the trigger can inherit and misuse that caller’s elevated permissions.

3. Why is db_owner a security risk for application accounts?

A db_owner member can create and alter objects, define triggers, change permissions, and build modules with elevated execution context, effectively controlling the database’s security boundary. This is far more access than most runtime application or support accounts need, and it becomes especially dangerous when combined with privileged maintenance jobs or impersonation.

4. Why does msdb matter for SQL Server security?

msdb stores SQL Server Agent jobs, schedules, credentials, proxies, and job history, and job steps can run under the SQL Agent service account, the job owner, or a proxy. A user with excessive permissions in msdb can potentially influence how privileged automation executes on the instance, making it a server-level security boundary rather than just a metadata store.

5. Can EXECUTE AS OWNER or EXECUTE AS dbo bypass an explicit DENY?

Yes, in some cases. If a principal can run code under a different execution context — such as becoming dbo through an impersonated module — actions that would normally be denied to that user can still execute under the impersonated identity. This is one reason db_owner membership and broad IMPERSONATE grants are treated as high-risk.

6. How can a DBA check whether their SQL Server instance is exposed to this kind of risk?

Start by querying system catalog views such as sys.database_role_members, sys.database_permissions, sys.sql_modules, and sys.triggers to baseline role membership, permissions, execution context, and trigger definitions. Pair this with SQL Audit (role, permission, and object change groups) and targeted Extended Events sessions to catch drift and suspicious activity over time.

This document contains proprietary information and is protected by copyright law.

Copyright © 2026 Red Gate Software Limited. All rights reserved

Article tags

About the author

Fabiano Amorim

See Profile

Fabiano Amorim is a Data Platform MVP since 2011 who loves to conquer complex, challenging problems - especially ones that others aren’t able to solve. He first became interested in technology when his older brother would bring him to his work meetings at the age of 14. With over a decade of experience, Fabiano is well known in the database community for his performance tuning abilities. Elsewhere, he loves to read and spend time with his family.