{"id":111349,"date":"2026-06-19T12:00:00","date_gmt":"2026-06-19T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=111349"},"modified":"2026-06-15T09:39:33","modified_gmt":"2026-06-15T09:39:33","slug":"exposing-a-sql-injection-vulnerability-youve-never-heard-of-what-it-is-how-it-works-and-key-takeaways","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/exposing-a-sql-injection-vulnerability-youve-never-heard-of-what-it-is-how-it-works-and-key-takeaways\/","title":{"rendered":"Exposing a SQL injection vulnerability you&#8217;ve never heard of (what it is, how it works, and key takeaways)"},"content":{"rendered":"\n<p><strong>SQL injection is one of the oldest and most well-understood vulnerability classes in software security. Most developers know the rules: use parameterized queries, avoid string concatenation, sanitize your inputs. And yet, SQL injection vulnerabilities continue to appear even in places where you least expect them.<\/strong> <\/p>\n\n\n\n<p><strong>This article documents a SQL injection vulnerability I discovered in <code>sys.sp_dbmmonitorupdate<\/code>, a Microsoft-signed system stored procedure.<\/strong> <strong>For other SQL Server security vulnerabilities I&#8217;ve discovered, see the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/sql-server-security-vulnerabilities-you-werent-aware-of\/\" target=\"_blank\" rel=\"noreferrer noopener\">full series here<\/a>.<\/strong><\/p>\n\n\n\n<p>What makes this case particularly interesting is not just that the vulnerability exists in a trusted system object, but how it works: the injection bypasses a <code>REPLACE<\/code>-based sanitization attempt through a subtle Unicode character conversion that happens silently during a variable assignment.<\/p>\n\n\n\n<p>The vulnerability was reported to Microsoft and they have since fixed it, but it&#8217;s still worth exposing and explaining given how intricate it is. So, that&#8217;s what I&#8217;ll do in this article.<\/p>\n\n\n\n<p><em>For full details of the fix, check <a href=\"https:\/\/msrc.microsoft.com\/update-guide\/en-US\/vulnerability\/CVE-2025-53727\" target=\"_blank\" rel=\"noreferrer noopener\">CVE-2025-53727<\/a>.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-sys-sp-dbmmonitorupdate\">What is sys.sp_dbmmonitorupdate?<\/h2>\n\n\n\n<p><strong>Before we begin, it&#8217;s worth explaining what <code>sys.sp_dbmmonitorupdate<\/code> actually is. It&#8217;s a system <a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/for-the-love-of-stored-procedures\/\" target=\"_blank\" rel=\"noreferrer noopener\">stored procedure<\/a> used to monitor the state of <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/database-engine\/database-mirroring\/database-mirroring-sql-server?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">Database Mirroring<\/a>, a high-availability feature in <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server<\/a>. The procedure accepts a single parameter &#8211; <code>@database_name<\/code> &#8211; which specifies which mirrored database to update.<\/strong><\/p>\n\n\n\n<p>Since it&#8217;s a <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.object?view=net-10.0\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft system object<\/a>, it&#8217;s treated as inherently trustworthy by both the SQL Server engine and many security tools. This trust is at the heart of <em>why<\/em> this vulnerability matters.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-vulnerability-explained\">The vulnerability explained<\/h2>\n\n\n\n<p>Inside <code>sys.sp_dbmmonitorupdate<\/code>, after inserting a monitoring row, the procedure needs to call <code>sys.sp_dbmmonitorresults<\/code> to retrieve the latest data for alert evaluation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-vulnerable-code\">The vulnerable code<\/h3>\n\n\n\n<p>It does this by building a <a href=\"https:\/\/www.sqlshack.com\/dynamic-sql-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">dynamic SQL<\/a> command string and executing it:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">-- The variable declaration \u2014 this is where the problem starts\ndeclare @alert bit,\n        @threshold int,\n        @command char(256),        -- Non-Unicode CHAR type\n        @time_behind_alert_value datetime,\n        @send_queue_alert_value int,\n        @redo_queue_alert_value int,\n        @average_delay_alert_value int,\n        @temp_time int\n\n-- The command construction \u2014 this is where the bypass happens\nset @command = N'sys.sp_dbmmonitorresults ''' \n             + replace(@database_name, N'''', N'''''')  -- Attempts to sanitize quotes\n             + N''',0,0'\n\n-- The execution\ninsert into @results exec (@command)<\/pre><\/div>\n\n\n\n<p>At first glance, it looks like the code is doing the right thing. The developer used <code>REPLACE<\/code> to double any single quotes in <code>@database_name<\/code> before embedding it in the SQL string &#8211; a classic and generally correct technique for preventing quote-based SQL injection. <\/p>\n\n\n\n<p>So, what&#8217;s the problem?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-problem-an-implicit-type-conversion\">The problem: an implicit type conversion<\/h3>\n\n\n\n<p><strong>The problem is the type of the <code>@command<\/code> variable: <code>CHAR(256)<\/code>.<\/strong><\/p>\n\n\n\n<p><code>CHAR<\/code> is a non-<a href=\"https:\/\/home.unicode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Unicode<\/a> type. <code>@database_name<\/code>, on the other hand, is declared as <code>sysname<\/code> &#8211; an alias for <code>NVARCHAR(128)<\/code>, which <em>is<\/em> a Unicode type.<\/p>\n\n\n\n<p>When the <code>NVARCHAR<\/code> expression on the right-hand side of the assignment is assigned to the <code>CHAR(256)<\/code> variable on the left, SQL Server must perform an <strong>implicit Unicode-to-non-Unicode conversion<\/strong>. This conversion is silent (no warning, no error), and is governed by the server&#8217;s collation and its best-fit character mapping rules.<\/p>\n\n\n\n<p>This implicit conversion is the key to the attack.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-attack-vector-explained-unicode-lookalike-characters\">The attack vector explained: Unicode lookalike characters<\/h2>\n\n\n\n<p>The Unicode standard contains many characters that are visually similar, or even nearly identical to common <a href=\"https:\/\/www.ascii-code.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">ASCII<\/a> characters. One such character is:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td>Character<\/td><td>Unicode Code Point<\/td><td>Name<\/td><td>Visual Appearance<\/td><\/tr><\/thead><tbody><tr><td>&#8216;<\/td><td>U+0027<\/td><td>Apostrophe (standard SQL quote)<\/td><td>&#8216;<\/td><\/tr><tr><td>\u02bc<\/td><td>U+02BC<\/td><td>Modifier Letter Apostrophe<\/td><td>\u02bc<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>These two characters look almost identical in most fonts but are completely different code points.<\/strong><\/p>\n\n\n\n<p>Now, consider what happens when an attacker crafts a database name containing \u02bc (<code>U+02BC<\/code>) instead of &#8216; (<code>U+0027<\/code>):<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-1-the-replace-call\">Step 1: The REPLACE call<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>replace(@database_name, N'''', N'''''')<\/code><\/pre>\n\n\n\n<p><code>REPLACE<\/code> compares characters by their exact code point. It&#8217;s searching for <code>U+0027<\/code>, while the injected character is <code>U+02BC<\/code> &#8211; clearly, they don&#8217;t match. So, the <code>REPLACE<\/code> call finds nothing to replace, and passes the character through unchanged.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-2-the-assignment-to-char-256\">Step 2: The assignment to CHAR(256)<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>set @command = N'sys.sp_dbmmonitorresults ''' \n             + replace(@database_name, N'''', N'''''') \n             + N''',0,0'<\/code><\/pre>\n\n\n\n<p>The right-hand side is a <code>NVARCHAR<\/code> expression. When assigned to <code>@command<\/code> (a <code>CHAR(256)<\/code> variable), SQL Server performs the implicit conversion. During this conversion, <code>U+02BC<\/code> (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Modifier_letter_apostrophe#:~:text=The%20modifier%20letter%20apostrophe%20(%CA%BC,primarily%20for%20various%20glottal%20sounds.\" target=\"_blank\" rel=\"noreferrer noopener\">Modifier Letter Apostrophe<\/a>) has no direct single-byte ASCII equivalent. <\/p>\n\n\n\n<p><strong>This isn&#8217;t a problem for SQL Server, with its <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/collations\/collation-and-unicode-support?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">collation<\/a> best-fit mapping silently converting it to <code>U+0027<\/code> &#8211; a real, standard apostrophe.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-step-3-the-result\">Step 3: The result<\/h3>\n\n\n\n<p>The <code>@command<\/code> variable now contains a real apostrophe that wasn&#8217;t there when <code>REPLACE<\/code> ran. This apostrophe breaks out of the string literal in the SQL command, creating an unmatched quote &#8211; the classic condition for SQL injection. Any SQL payload appended after the \u02bc in the database name is now executable <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">T-SQL<\/a>.<\/p>\n\n\n\n<section id=\"my-first-block-block_e1b78a08037948b92c1ea34f9ee4104a\" 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<h2 class=\"wp-block-heading\" id=\"h-a-closer-look-at-the-conversion-with-a-simplified-proof-of-concept\">A closer look at the conversion with a simplified proof of concept<\/h2>\n\n\n\n<p>The core vulnerability can be demonstrated with a much smaller proof of concept by isolating the vulnerable command construction logic.<\/p>\n\n\n\n<p>The vulnerable procedure attempted to escape regular single quotes in <code>@database_name<\/code>, but then assigned the generated Unicode command to a non-Unicode <code>char(256)<\/code> variable:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @database_name sysname;\nDECLARE @command char(256);\n\nSET @database_name = N'MyDatabaseName\u02bc; SELECT ''SQL Injection executed'' AS Result;--';\n\nSET @command = N'sys.sp_dbmmonitorresults '''\n             + REPLACE(@database_name, N'''', N'''''')\n             + N''',0,0';\n\nSELECT @command AS GeneratedCommand;\nGO<\/pre><\/div>\n\n\n\n<p>The important character in the payload is this one:<\/p>\n\n\n\n<p><code>\u02bc<\/code><\/p>\n\n\n\n<p>It is Unicode character <code>U+02BC<\/code>. It looks similar to a regular apostrophe, but it is not the same character as the SQL string delimiter <code>'<\/code>. As a result, the <code>REPLACE()<\/code> function does <em>not<\/em> escape it.<\/p>\n\n\n\n<p>After the assignment to <code>char(256)<\/code>, SQL Server performs an implicit Unicode-to-non-Unicode conversion. In the vulnerable scenario, the <code>U+02BC<\/code> character is converted into a regular apostrophe. The generated command becomes equivalent to:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">sys.sp_dbmmonitorresults 'PythianDBA'; SELECT 'SQL Injection executed' AS Result;--',0,0<\/pre>\n\n\n\n<p>At this point, the string passed to <code>sys.sp_dbmmonitorresults<\/code> is closed early, and the injected <code>SELECT<\/code> statement becomes a second command in the batch.<\/p>\n\n\n\n<p>A slightly expanded local demonstration can show the difference between the <strong>vulnerable<\/strong> and <strong>fixed <\/strong>behavior:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">\/*\n    Simplified demonstration of the vulnerable conversion behavior.\n*\/\nDECLARE @database_name sysname;\n\nSET @database_name = N'MyDatabaseName\u02bc; SELECT ''SQL Injection executed'' AS Result;--';\n\nPRINT N'-------------------------------------------';\nPRINT N'Original Unicode input:';\nPRINT @database_name;\nPRINT N'-------------------------------------------';\n\nPRINT N'Vulnerable behavior: command stored as CHAR';\nDECLARE @command_char char(256);\n\nSET @command_char = N'sys.sp_dbmmonitorresults '''\n                  + REPLACE(@database_name, N'''', N'''''')\n                  + N''',0,0';\n\nPRINT @command_char;\nPRINT N'-------------------------------------------';\n\nPRINT N'Fixed behavior: command stored as NVARCHAR';\nDECLARE @command_nvarchar nvarchar(4000);\n\nSET @command_nvarchar = N'sys.sp_dbmmonitorresults '''\n                      + REPLACE(@database_name, N'''', N'''''')\n                      + N''',0,0';\n\nPRINT @command_nvarchar;\nPRINT N'-------------------------------------------';\n\/*\n-------------------------------------------\nOriginal Unicode input:\nMyDatabaseName\u02bc; SELECT 'SQL Injection executed' AS Result;--\n-------------------------------------------\nVulnerable behavior: command stored as CHAR\nsys.sp_dbmmonitorresults 'MyDatabaseName'; SELECT ''SQL Injection executed'' AS Result;--',0,0                                                                                                                                                                  \n-------------------------------------------\nFixed behavior: command stored as NVARCHAR\nsys.sp_dbmmonitorresults 'MyDatabaseName\u02bc; SELECT ''SQL Injection executed'' AS Result;--',0,0\n-------------------------------------------\n*\/<\/pre><\/div>\n\n\n\n<p><strong>In the vulnerable version, the generated command can contain a real apostrophe introduced by the implicit conversion to <code>char<\/code>.<\/strong><\/p>\n\n\n\n<p><strong>In the fixed version, meanwhile, the command remains Unicode, so the <code>U+02BC<\/code> character stays <code>U+02BC<\/code>. It does <em>not<\/em> become a SQL string delimiter, and the injected text remains part of the database-name argument.<\/strong><\/p>\n\n\n\n<p>Microsoft fixed the issue by changing the command buffer to <code>nvarchar(4000)<\/code>, which prevents the Unicode-to-non-Unicode conversion that turned the <code>U+02BC<\/code> character into a real apostrophe.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-replace-can-t-save-you-here\">Why REPLACE can&#8217;t save you here<\/h2>\n\n\n\n<p><strong>This is the critical insight of this vulnerability:<\/strong> <strong>the sanitization runs before the conversion.<\/strong> <\/p>\n\n\n\n<p>So, by the time the dangerous character transformation has occurred during the variable assignment, <code>REPLACE<\/code> has already finished its work and returned. There&#8217;s no second chance to catch the newly created apostrophe.<\/p>\n\n\n\n<p><strong>This is a general principle worth remembering:<\/strong> <strong>any <code>REPLACE<\/code>-based quote sanitization applied to a <code>NVARCHAR<\/code> value <em>before<\/em> it&#8217;s assigned to a <code>CHAR<\/code>\/<code>VARCHAR<\/code> variable, is potentially bypassable via Unicode lookalike characters.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-s-the-impact-of-this-security-vulnerability\">What&#8217;s the impact of this security vulnerability?<\/h2>\n\n\n\n<p><strong>One important limitation of this vulnerability is that <code>sys.sp_dbmmonitorupdate<\/code> can only be executed by members of the <code>sysadmin<\/code> fixed server role.<\/strong><\/p>\n\n\n\n<p>The procedure explicitly performs the following security check before reaching the vulnerable dynamic SQL code path:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if (is_srvrolemember(N'sysadmin') &lt;&gt; 1 )\nbegin\n    raiserror(21089, 16, 1)\n    return 1\nend<\/pre>\n\n\n\n<p>This significantly limits the practical exploitability in standard configurations because, if you are already a sysadmin, you can execute arbitrary SQL anyway. However, the vulnerability still matters, as I&#8217;ll explain next.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-the-vulnerability-matters-3-key-reasons-explained\">Why the vulnerability matters (3 key reasons explained)<\/h2>\n\n\n\n<p><strong>Here are the 3 key reasons this SQL Server vulnerability matters:<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-it-enables-stealthy-audit-evasion\">It enables stealthy audit evasion<\/h3>\n\n\n\n<p><code>sys.sp_dbmmonitorupdate<\/code> is a Microsoft-signed system object. Many <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/extended-events\/extended-events?view=sql-server-ver17&amp;tabs=sqldb\" target=\"_blank\" rel=\"noreferrer noopener\">Extended Events <\/a>sessions, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-audit-server-configuration\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server Audit<\/a> configurations, and <a href=\"https:\/\/www.crowdstrike.com\/en-gb\/cybersecurity-101\/next-gen-siem\/security-information-and-event-management-siem\/\" target=\"_blank\" rel=\"noreferrer noopener\">SIEM<\/a> integrations whitelist known system procedures or reduce the verbosity of their logging. <\/p>\n\n\n\n<p>An attacker who is a sysadmin (perhaps through a compromised service account) can use this injection vector to execute arbitrary SQL in a way that looks, from the outside, like a routine monitoring procedure call <strong>concealing the true command from logging systems and administrators<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-cloud-platform-trust-can-be-abused\">Cloud platform trust can be abused<\/h3>\n\n\n\n<p>Cloud providers including <a href=\"https:\/\/www.red-gate.com\/simple-talk\/cloud\/azure\/\" target=\"_blank\" rel=\"noreferrer noopener\">Azure<\/a>, <a href=\"https:\/\/aws.amazon.com\/rds\/\" target=\"_blank\" rel=\"noreferrer noopener\">AWS RDS<\/a>, and <a href=\"https:\/\/cloud.google.com\/sql\" target=\"_blank\" rel=\"noreferrer noopener\">GCP Cloud SQL<\/a> often grant elevated permissions to specific system procedures to support platform automation features. These include: enabling CDC, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/beginners-guide-to-mysql-replication-part-1\/\" target=\"_blank\" rel=\"noreferrer noopener\">replication<\/a>, and database mirroring.<\/p>\n\n\n\n<p>In some configurations, these procedures may be executable by users who are not full sysadmins. If a cloud platform grants elevated execution rights to <code>sys.sp_dbmmonitorupdate<\/code> as part of a mirroring automation workflow, the privilege requirement changes entirely.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-automation-context-can-be-abused\">Automation context can be abused<\/h3>\n\n\n\n<p>If the procedure is invoked by an elevated service account on a schedule \u2014 common in monitoring setups \u2014 an attacker who can influence the name of a mirrored database could inject SQL that executes in that service account&#8217;s context. This might be possible, for example, through a compromised application with permission to create databases.<\/p>\n\n\n\n<p>Therefore, the sysadmin requirement should reduce the assessed severity &#8211; but should <em>not<\/em> cause the bug to be ignored entirely. The vulnerability demonstrates a real SQL injection flaw in trusted SQL Server code, caused by unsafe dynamic SQL construction and a Unicode-to-non-Unicode conversion after quote escaping.<\/p>\n\n\n\n<section id=\"my-first-block-block_7748c6fc5247255625b63d87375382d1\" 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\">Future-proof database monitoring with Redgate Monitor<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            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.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Future-proof database monitoring with Redgate Monitor\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-microsoft-fixed-the-vulnerability\">How Microsoft fixed the vulnerability<\/h2>\n\n\n\n<p>Microsoft&#8217;s fix is practical and easy. A single variable type change in the declaration block eliminates the vulnerability entirely.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-vulnerable-declaration\">Vulnerable declaration<\/h4>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">declare @alert bit,\n        @threshold int,\n        @command char(256),        -- Non-Unicode, triggers implicit conversion\n        @time_behind_alert_value datetime,\n        @send_queue_alert_value int,\n        @redo_queue_alert_value int,\n        @average_delay_alert_value int,\n        @temp_time int<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"h-fixed-declaration\">Fixed declaration<\/h4>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">declare @alert bit,\n        @threshold int,\n        @command nvarchar(4000),   -- Unicode, no implicit conversion\n        @time_behind_alert_value datetime,\n        @send_queue_alert_value int,\n        @redo_queue_alert_value int,\n        @average_delay_alert_value int,\n        @temp_time int<\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-4-key-technical-takeaways-from-this-vulnerability\">4 key technical takeaways from this vulnerability<\/h2>\n\n\n\n<p><strong>Here&#8217;s what you should know about this SQL Server vulnerability (the 4 key technical takeaways):<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-1-type-consistency-in-dynamic-sql-is-a-security-property\">1. Type consistency in dynamic SQL is a security property<\/h3>\n\n\n\n<p>When building <a href=\"https:\/\/www.geeksforgeeks.org\/sql-server\/dynamic-sql-in-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">dynamic SQL<\/a> strings, every variable in the chain must be the same type or you must explicitly account for what happens during conversion. Mixing <code>NVARCHAR<\/code> inputs with <code>CHAR<\/code>\/<code>VARCHAR<\/code> command buffers is a latent security defect waiting to be triggered.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-2-replace-based-sanitization-has-a-blind-spot\">2. REPLACE-based sanitization has a blind spot<\/h3>\n\n\n\n<p><code>REPLACE<\/code> operates on the string as-is at the moment it runs. If a subsequent operation (like a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Type_conversion\" target=\"_blank\" rel=\"noreferrer noopener\">type conversion<\/a>) transforms the string in a way that introduces new SQL metacharacters, <code>REPLACE<\/code> can&#8217;t help. The correct defense is to ensure that no such transformation can occur which means keeping everything <code>NVARCHAR<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-3-the-attack-requires-a-specific-precondition\">3. The attack requires a specific precondition<\/h3>\n\n\n\n<p>For this attack to work, the database name containing <code>U+02BC<\/code> must actually exist in SQL Server. While SQL Server <em>does<\/em> allow Unicode characters in database names (when using quoted identifiers), it also requires the attacker to first create a database with the crafted name. To do that, the <code>CREATE DATABASE<\/code> permission <em>or<\/em> sysadmin is required.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-4-signed-system-procedures-are-not-immune\">4. Signed system procedures are not immune<\/h3>\n\n\n\n<p>The presence of this vulnerability in a Microsoft-signed system procedure is a reminder that, while <a href=\"https:\/\/www.fortanix.com\/blog\/an-introduction-to-code-signing\" target=\"_blank\" rel=\"noreferrer noopener\">code signing<\/a> establishes authenticity and integrity, it doesn&#8217;t establish security correctness. A signed procedure can still contain logic errors, including injection vulnerabilities.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>The SQL injection vulnerability in <code>sys.sp_dbmmonitorupdate<\/code> is subtle, but instructive. The developer clearly thought about injection &#8211; the <code>REPLACE<\/code> call is evidence of that. However, the implicit conversion from <code>NVARCHAR<\/code> to <code>CHAR<\/code> created a blind spot that <code>REPLACE<\/code> couldn&#8217;t overcome. Consequently, a Unicode lookalike character was able to slip through the sanitization and emerge on the other side as a real SQL metacharacter.<\/p>\n\n\n\n<p><strong>For developers writing T-SQL that constructs dynamic SQL, the lesson is clear: keep your types consistent, use <code>NVARCHAR<\/code> throughout, and prefer <code>sp_executesql<\/code> with parameterization over string concatenation and <code>REPLACE<\/code>. No amount of <code>REPLACE<\/code>-based sanitization can protect you if a type conversion undoes your work <em>after<\/em> the fact.<\/strong><\/p>\n\n\n\n<section id=\"my-first-block-block_9937cce91446955a8f50ca8e0afad4df\" 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>","protected":false},"excerpt":{"rendered":"<p>How a Unicode lookalike character bypassed REPLACE-based sanitization in a trusted SQL Server system procedure &#8211; and what it teaches us about type consistency in dynamic SQL.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":111375,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,46,143524],"tags":[4168,4170,5765,4150,4151,159394],"coauthors":[6809],"class_list":["post-111349","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-data-security-privacy-compliance","category-sql-server","tag-database","tag-database-administration","tag-security-and-compliance","tag-sql","tag-sql-server","tag-sql-server-security-vulnerabilities"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111349","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=111349"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111349\/revisions"}],"predecessor-version":[{"id":111377,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111349\/revisions\/111377"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/111375"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=111349"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=111349"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=111349"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=111349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}