A breached host puts the database instance under suspicion too. During a SQL Server compromise, preserve evidence first, then inspect the paths an attacker can use to persist or run code.

Preserve Evidence After a SQL Server Compromise
I record the suspected time range, instance version, host state, and who has access before removing anything. Coordinate with the incident response owner. Preserve SQL Server error logs, Windows event logs, Agent history, audit files, and relevant backups. Take copies with documented timestamps and hashes under the organization's evidence procedure. Turning off a suspicious job before capturing its definition can erase the best clue. Do not run unknown procedures or scripts discovered during the sweep. Review their text as evidence.
Use a restored copy or isolated forensic environment for deeper analysis when possible. The goal is to understand what changed and what executed, not to make the dashboard green by deleting the first unfamiliar object.
Compare Logins and Server Roles
List SQL and Windows principals, creation and modification dates, disabled state, and membership in high-privilege roles. Compare with an approved baseline. A familiar login newly added to sysadmin matters as much as a new login. Inspect explicit permissions as well as role membership. I avoid calling every recently modified principal malicious; legitimate deployments happen. The time window and change records decide what needs investigation.
SELECT sp.name, sp.type_desc, sp.create_date, sp.modify_date,
sp.is_disabled, rolep.name AS server_role
FROM sys.server_principals AS sp
LEFT JOIN sys.server_role_members AS srm
ON srm.member_principal_id = sp.principal_id
LEFT JOIN sys.server_principals AS rolep
ON rolep.principal_id = srm.role_principal_id
WHERE sp.type IN ('S','U','G')
ORDER BY sp.modify_date DESC, sp.name;Inspect Startup and Agent Persistence
Startup stored procedures can execute when SQL Server starts. They live in master, so the query below reads master.sys.procedures from any database. Inspect their definitions without executing them. Agent jobs and job steps deserve the same attention, including disabled jobs that can be enabled later. Compare creation and modification dates to the suspected window and review command text, proxies, schedules, and owners. A harmless-looking job name is not a reliable description of its step. I have seen operational jobs with names left over from earlier tasks, so I compare definitions to a known-good baseline rather than judging names alone.
SELECT name, is_auto_executed, create_date, modify_date
FROM master.sys.procedures
WHERE is_auto_executed = 1;
SELECT j.name, j.enabled, SUSER_SNAME(j.owner_sid) AS job_owner,
j.date_created, j.date_modified, s.step_id,
s.subsystem, s.command
FROM msdb.dbo.sysjobs AS j
JOIN msdb.dbo.sysjobsteps AS s ON s.job_id = j.job_id
ORDER BY j.date_modified DESC, j.name, s.step_id;Check Risky Features After a SQL Server Compromise
Review xp_cmdshell, CLR integration, and other configuration changes against policy. Inspect linked servers, providers, mappings, and remote login privileges. An attacker does not need a new binary if an existing server feature already reaches the operating system or another instance. Record the configured and running values, then correlate with change records. Do not enable a disabled feature simply to test it. Investigation is read-only until response owners choose containment steps.
SELECT name, value, value_in_use
FROM sys.configurations
WHERE name IN (N'xp_cmdshell', N'clr enabled', N'Ole Automation Procedures');
SELECT name, product, provider, data_source
FROM sys.servers WHERE is_linked = 1;
Find Changed Database Objects
Look for procedures, functions, views, triggers, and tables modified since the suspected time. A modify_date is a triage signal, not proof of hostile change. Deployment tools can touch objects legitimately, and some metadata dates have special behavior. Review definitions, permissions, DDL triggers, and unusual EXECUTE AS clauses. Search for external calls, dynamic SQL, and obfuscated strings in a copy of the definitions. Compare with source-controlled or backed-up definitions where available, but do not assume the current deployment folder is trustworthy after a host breach.
I also check database principals and role membership. A server-level login can look normal while a database-level grant gives broad rights. Work through each database that the suspected identity could reach.
Inspect Credential and Execution Paths
Server logins are only one part of persistence. Review SQL Agent proxies and credentials, database-scoped credentials, asymmetric-key or certificate-signed modules, and EXECUTE AS definitions when the suspected account had rights to change them. Compare each with a known-good baseline. I also inspect service accounts and Windows scheduled tasks with the host response team. A database query cannot certify the operating system, and an OS scan cannot certify every database principal.
Look for changed endpoints and unusual external connectivity, including linked servers and external data sources where used. Preserve definitions before removing anything. A new credential can be harmless deployment work, while an old credential whose secret was stolen can be the critical path. Investigation follows evidence, not only creation dates.
Record the SQL Server Compromise Timeline and Its Gaps
Put every collected item on one timeline: suspected breach, login change, job creation, feature toggle, object modification, and observed execution. Note which clocks use UTC and which use local time. I normalize them in the investigation copy and keep original timestamps intact. If Agent history was purged or audit was disabled, record that gap. Do not fill it with a confident story. A gap changes how strongly you can say an object was or was not used.
What is the smallest confirmed unauthorized action? Start there, then follow the identity and permission chain outward. Once containment is approved, rotate exposed credentials, disable malicious persistence, and rebuild from trusted media as the response plan requires. Keep a record of every change made during containment so later evidence is not confused with attacker activity. The final report should make clear which findings were observed directly and which remain hypotheses.
After collection, store a read-only copy of each output with the query text and collection timestamp. A CSV without the query that produced it is difficult to interpret later. If a query itself fails because of missing permission, record that gap instead of assuming the category is clean. Evidence quality matters as much as the number of checks completed.
Correlate With Execution Evidence
Agent history, audit records, Extended Events, and error logs can show whether a suspicious object ran. Query Store can help identify query text during its retention period, but it is not a complete security log. Absence of a record is not proof of absence. Record gaps in collection plainly. What was the first known unauthorized action, and what credentials could have enabled it? Those questions guide the next sweep. Keep the original logs, the query outputs, and the exact collection time together.
I hand the findings to incident response with confidence labels: confirmed change, suspicious difference, and unverified lead. Remediation follows the containment plan, including credential rotation and host rebuild decisions. A database sweep after a SQL Server compromise is part of incident response, not a substitute for it.
Related reading on this blog: List Users with System Admin (sysadmin) Rights: Part 2 and Who Dropped Table? Part 2.

A clean-looking login list is not a clean server, it is one line of an investigation.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.




