CVE-2024-39677 (published 2024-07-08T15:15Z)

NHibernate is an object-relational mapper for the .NET framework. A SQL injection vulnerability exists in some types implementing ILiteralType.ObjectToSQLString. Callers of these methods are exposed to the vulnerability, which includes mappings using inheritance with discriminator values; HQL queries referencing a static field of the application; users of the SqlInsertBuilder and SqlUpdateBuilder utilities, calling their AddColumn overload taking a literal value; and any direct use of the ObjectToSQLString methods for building SQL queries on the user side. This vulnerability is fixed in 5.4.9 and 5.5.2.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/nhibernate/nhibernate-core
commit_details: repo_url: https://github.com/nhibernate/nhibernate-core
author: Frédéric Delaporte
author_date: 2024-07-03 00:56:52+02:00
msg: Handle SQL injection vulnerabilities within ObjectToSQLString (#3547)

Co-authored-by: Alex Zaytsev <hazzik@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
repository: repo_url: https://github.com/nhibernate/nhibernate-core
repo_name: nhibernate/nhibernate-core
description: NHibernate Object Relational Mapper
repo_language: C#
owner: nhibernate
file_changes: filename: configuration.xml
change_type: ModificationType.MODIFY
diff: @@ -871,6 +871,20 @@ var session = sessions.OpenSession(conn);
                             </para>
                         </entry>
                     </row>
+                    <row>
+                        <entry>
+                            <literal>escape_backslash_in_strings</literal>
+                        </entry>
+                        <entry>
+                            Indicates if the database needs to have backslash escaped in string literals.
+                            The default value is dialect dependant. That is <literal>false</literal> for
+                            most dialects.
+                            <para>
+                                <emphasis role="strong">eg.</emphasis> 
+                                <literal>true</literal> | <literal>false</literal>
+                            </para>
+                        </entry>
+                    </row>
                     <row>
                         <entry>
                             <literal>show_sql</literal>
@@ -1515,12 +1529,6 @@ in the parameter binding.</programlisting>
                             <entry><literal>NHibernate.Dialect.PostgreSQLDialect</literal></entry>
                             <entry></entry>
                         </row>
-                        <row>
-                            <entry>PostgreSQL</entry>
-                            <entry><literal>NHibernate.Dialect.PostgreSQLDialect</literal></entry>
-                            <entry>
-                            </entry>
-                        </row>
                         <row>
                             <entry>PostgreSQL 8.1</entry>
                             <entry><literal>NHibernate.Dialect.PostgreSQL81Dialect</literal></entry>

programming_language: C#
filename: SapSQLAnywhere.cfg.xml
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@ for your own use before compiling tests in Visual Studio.
 		<property name="connection.connection_string">
 			UID=DBA;PWD=sql;Server=localhost;DBN=nhibernate;DBF=c:\nhibernate.db;ASTOP=No;Enlist=false;
 		</property>
-		<property name="dialect">NHibernate.Dialect.SybaseSQLAnywhere12Dialect</property>
+		<property name="dialect">NHibernate.Dialect.SapSQLAnywhere17Dialect</property>
 		<property name="query.substitutions">true=1;false=0</property>
 	</session-factory>
 </hibernate-configuration>

programming_language: C#
filename: FixtureByCode.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,399 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by AsyncGenerator.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.SqlTypes;
+using NHibernate.Type;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	using System.Threading.Tasks;
+	[TestFixture]
+	public class FixtureByCodeAsync : TestCaseMappingByCode
+	{
+
+		private readonly HashSet<string> _unsupportedProperties = new();
+
+		protected override HbmMapping GetMappings()
+		{
+			var mapper = new ModelMapper();
+			mapper.Class<Entity>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Property(x => x.Name);
+				rc.Property(x => x.FirstChar);
+				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
+				rc.Property(x => x.UriProperty);
+
+				rc.Property(x => x.ByteProperty);
+				rc.Property(x => x.DecimalProperty);
+				rc.Property(x => x.DoubleProperty);
+				rc.Property(x => x.FloatProperty);
+				rc.Property(x => x.ShortProperty);
+				rc.Property(x => x.IntProperty);
+				rc.Property(x => x.LongProperty);
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
+					rc.Property(x => x.SByteProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.SByteProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
+					rc.Property(x => x.UShortProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UShortProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
+					rc.Property(x => x.UIntProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UIntProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
+					rc.Property(x => x.ULongProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.ULongProperty));
+
+				rc.Property(x => x.DateTimeProperty);
+				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
+					rc.Property(x => x.DateTimeOffsetProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
+				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));
+
+				rc.Property(x => x.GuidProperty);
+			});
+
+			mapper.Class<BaseClass>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Discriminator(x => x.Column("StringDiscriminator"));
+				rc.Property(x => x.Name);
+				rc.Abstract(true);
+			});
+			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
+			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));
+
+			mapper.Import<CharEnum>();
+
+			return mapper.CompileMappingForAllExplicitlyAddedEntities();
+		}
+
+		private CultureInfo _backupCulture;
+		private CultureInfo _backupUICulture;
+
+		protected override void OnSetUp()
+		{
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithSingleQuote,
+					FirstChar = Entity.QuoteInitial,
+					CharacterEnum = CharEnum.SingleQuote,
+					UriProperty = Entity.UriWithSingleQuote
+				});
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithEscapedSingleQuote,
+					FirstChar = Entity.BackslashInitial,
+					CharacterEnum = CharEnum.Backslash,
+					UriProperty = Entity.UriWithEscapedSingleQuote
+				});
+
+			transaction.Commit();
+
+			_backupCulture = CultureInfo.CurrentCulture;
+			_backupUICulture = CultureInfo.CurrentUICulture;
+		}
+
+		protected override void OnTearDown()
+		{
+			if (_backupCulture != null)
+			{
+				CultureInfo.CurrentCulture = _backupCulture;
+				CultureInfo.CurrentUICulture = _backupUICulture;
+			}
+
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+			transaction.Commit();
+		}
+
+		private static readonly string[] StringInjectionsProperties =
+		{
+			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(StringInjectionsProperties))]
+		public void SqlInjectionInStringsAsync(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
+		}
+
+		private static readonly string[] SpecialNames =
+		{
+			"\0; drop table Entity; --",
+			"\b; drop table Entity; --",
+			"\n; drop table Entity; --",
+			"\r; drop table Entity; --",
+			"\t; drop table Entity; --",
+			"\x1A; drop table Entity; --",
+			"\"; drop table Entity; --",
+			"\\; drop table Entity; --"
+		};
+
+		[TestCaseSource(nameof(SpecialNames))]
+		public async Task StringsWithSpecialCharactersAsync(string name)
+		{
+			// We may not even be able to insert the entity.
+			var wasInserted = false;
+			try
+			{
+				using var s = OpenSession();
+				using var t = s.BeginTransaction();
+				var e = new Entity { Name = name };
+				await (s.SaveAsync(e));
+				await (t.CommitAsync());
+
+				wasInserted = true;
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The entity insertion failed with message {e}");
+			}
+
+			try
+			{
+				using var session = OpenSession();
+				Entity.ArbitraryStringValue = name;
+				var list = await (session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").ListAsync<Entity>());
+				if (wasInserted && list.Count != 1)
+					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The query has failed with message {e}");
+			}
+
+			// Check the db is not wrecked.
+			if (wasInserted)
+			{
+				using var session = OpenSession();
+				var list = await (session
+					.CreateQuery("from Entity e where e.Name = :name")
+					.SetString("name", name)
+					.ListAsync<Entity>());
+				Assert.That(list, Has.Count.EqualTo(1));
+			}
+			else
+			{
+				using var session = OpenSession();
+				var all = await (session.CreateQuery("from Entity e").ListAsync<Entity>());
+				Assert.That(all, Has.Count.GreaterThan(0));
+			}
+		}
+
+		[Test]
+		public async Task SqlInjectionInStringDiscriminatorAsync()
+		{
+			using var session = OpenSession();
+
+			await (session.SaveAsync(new Subclass1 { Name = "Subclass1" }));
+			await (session.SaveAsync(new Subclass2 { Name = "Subclass2" }));
+
+			// ObjectToSQLString is used for generating the inserts.
+			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");
+
+			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
+			{
+				var query = session.CreateQuery($"from {entityName}");
+				IList list = null;
+				Assert.That(async () => list = await (query.ListAsync()), Throws.Nothing, $"Unable to list entities of {entityName}");
+				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
+			}
+		}
+
+		private static readonly string[] CharInjectionsProperties =
+		{
+			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
+		};
+
+		[TestCaseSource(nameof(CharInjectionsProperties))]
+		public void SqlInjectionInCharAsync(string propertyName)
+		{
+			using var session = OpenSession();
+			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
+		}
+
+		private static readonly string[] CharEnumInjections =
+		{
+			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
+		};
+
+		[TestCaseSource(nameof(CharEnumInjections))]
+		public void SqlInjectionWithCharEnumAsync(string enumName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
+		}
+
+		private static readonly string[] UriInjections =
+		{
+			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(UriInjections))]
+		public void SqlInjectionWithUriAsync(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
+		}
+
+		private static readonly string[] NumericalTypesInjections =
+		{
+			nameof(Entity.ByteProperty),
+			nameof(Entity.DecimalProperty),
+			nameof(Entity.DoubleProperty),
+			nameof(Entity.FloatProperty),
+			nameof(Entity.ShortProperty),
+			nameof(Entity.IntProperty),
+			nameof(Entity.LongProperty),
+			nameof(Entity.SByteProperty),
+			nameof(Entity.UShortProperty),
+			nameof(Entity.UIntProperty),
+			nameof(Entity.ULongProperty)
+		};
+
+		[TestCaseSource(nameof(NumericalTypesInjections))]
+		public async Task SqlInjectionInNumericalTypeAsync(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			Entity.ArbitraryStringValue = "0; drop table Entity; --";
+			using (var session = OpenSession())
+			{
+				IQuery query;
+				// Defining that query is invalid and should throw.
+				try
+				{
+					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
+				}
+				catch (Exception ex)
+				{
+					// All good.
+					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
+					// Needed for the compiler who does not know "Pass" always throw.
+					return;
+				}
+
+				// The query definition has been accepted, run it.
+				try
+				{
+					await (query.ListAsync<Entity>());
+				}
+				catch (Exception ex)
+				{
+					// Expecting no exception at that point, but the test is to check if the injection succeeded.
+					Assert.Warn($"The wicked query execution has failed: {ex}");
+				}
+			}
+
+			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
+			using (var session = OpenSession())
+			{
+				IList<Entity> list = null;
+				Assert.That(async () => list = await (session.CreateQuery("from Entity e").ListAsync<Entity>()), Throws.Nothing);
+				Assert.That(list, Has.Count.GreaterThan(0));
+			}
+		}
+
+		private static readonly string[] DateTypesInjections =
+		{
+			nameof(Entity.DateTimeProperty),
+			nameof(Entity.DateProperty),
+			nameof(Entity.DateTimeOffsetProperty),
+			nameof(Entity.TimeProperty)
+		};
+
+		[TestCaseSource(nameof(DateTypesInjections))]
+		public void SqlInjectionWithDatetimeAsync(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			var wickedCulture = new CultureInfo("en-US");
+			if (propertyName == nameof(Entity.TimeProperty))
+				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
+			else
+				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
+			CultureInfo.CurrentCulture = wickedCulture;
+			CultureInfo.CurrentUICulture = wickedCulture;
+
+			using var session = OpenSession();
+
+			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
+				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
+			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+
+		private static readonly string[] GuidInjections =
+		{
+			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
+		};
+
+		[TestCaseSource(nameof(GuidInjections))]
+		public void SqlInjectionWithGuidAsync(string injection)
+		{
+			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+	}
+}

programming_language: C#
filename: Entity.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,54 @@
+﻿using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	public class Entity
+	{
+		public virtual Guid Id { get; set; }
+		public virtual string Name { get; set; }
+		public virtual char FirstChar { get; set; }
+		public virtual CharEnum CharacterEnum { get; set; } = CharEnum.SimpleChar;
+		public virtual Uri UriProperty { get; set; }
+
+		public virtual byte ByteProperty { get; set; }
+		public virtual decimal DecimalProperty { get; set; }
+		public virtual double DoubleProperty { get; set; }
+		public virtual float FloatProperty { get; set; }
+		public virtual short ShortProperty { get; set; }
+		public virtual int IntProperty { get; set; }
+		public virtual long LongProperty { get; set; }
+		public virtual sbyte SByteProperty { get; set; }
+		public virtual ushort UShortProperty { get; set; }
+		public virtual uint UIntProperty { get; set; }
+		public virtual ulong ULongProperty { get; set; }
+
+		public virtual DateTime DateTimeProperty { get; set; } = StaticDateProperty;
+		public virtual DateTime DateProperty { get; set; } = StaticDateProperty;
+		public virtual DateTimeOffset DateTimeOffsetProperty { get; set; } = StaticDateProperty;
+		public virtual DateTime TimeProperty { get; set; } = StaticDateProperty;
+
+		public virtual Guid GuidProperty { get; set; } = Guid.Empty;
+
+		public const string NameWithSingleQuote = "'; drop table Entity; --";
+		public const string NameWithEscapedSingleQuote = @"\'; drop table Entity; --";
+
+		// Do not switch to property, the feature of referencing static fields in HQL does not work with properties.
+		public static string ArbitraryStringValue;
+
+		public const char QuoteInitial = '\'';
+		public const char BackslashInitial = '\\';
+
+		public static readonly Uri UriWithSingleQuote = new("https://somewhere/?sql='; drop table Entity; --");
+		public static readonly Uri UriWithEscapedSingleQuote = new(@"https://somewhere/?sql=\'; drop table Entity; --");
+
+		public static readonly DateTime StaticDateProperty = DateTime.Today;
+		public static readonly DateTimeOffset StaticDateTimeOffsetProperty = DateTimeOffset.Now;
+	}
+
+	public enum CharEnum
+	{
+		SimpleChar = 'A',
+		SingleQuote = '\'',
+		Backslash = '\\'
+	}
+}

programming_language: C#
filename: FixtureByCode.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,388 @@
+﻿using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.SqlTypes;
+using NHibernate.Type;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	[TestFixture]
+	public class FixtureByCode : TestCaseMappingByCode
+	{
+
+		private readonly HashSet<string> _unsupportedProperties = new();
+
+		protected override HbmMapping GetMappings()
+		{
+			var mapper = new ModelMapper();
+			mapper.Class<Entity>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Property(x => x.Name);
+				rc.Property(x => x.FirstChar);
+				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
+				rc.Property(x => x.UriProperty);
+
+				rc.Property(x => x.ByteProperty);
+				rc.Property(x => x.DecimalProperty);
+				rc.Property(x => x.DoubleProperty);
+				rc.Property(x => x.FloatProperty);
+				rc.Property(x => x.ShortProperty);
+				rc.Property(x => x.IntProperty);
+				rc.Property(x => x.LongProperty);
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
+					rc.Property(x => x.SByteProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.SByteProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
+					rc.Property(x => x.UShortProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UShortProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
+					rc.Property(x => x.UIntProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UIntProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
+					rc.Property(x => x.ULongProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.ULongProperty));
+
+				rc.Property(x => x.DateTimeProperty);
+				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
+					rc.Property(x => x.DateTimeOffsetProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
+				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));
+
+				rc.Property(x => x.GuidProperty);
+			});
+
+			mapper.Class<BaseClass>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Discriminator(x => x.Column("StringDiscriminator"));
+				rc.Property(x => x.Name);
+				rc.Abstract(true);
+			});
+			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
+			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));
+
+			mapper.Import<CharEnum>();
+
+			return mapper.CompileMappingForAllExplicitlyAddedEntities();
+		}
+
+		private CultureInfo _backupCulture;
+		private CultureInfo _backupUICulture;
+
+		protected override void OnSetUp()
+		{
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithSingleQuote,
+					FirstChar = Entity.QuoteInitial,
+					CharacterEnum = CharEnum.SingleQuote,
+					UriProperty = Entity.UriWithSingleQuote
+				});
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithEscapedSingleQuote,
+					FirstChar = Entity.BackslashInitial,
+					CharacterEnum = CharEnum.Backslash,
+					UriProperty = Entity.UriWithEscapedSingleQuote
+				});
+
+			transaction.Commit();
+
+			_backupCulture = CultureInfo.CurrentCulture;
+			_backupUICulture = CultureInfo.CurrentUICulture;
+		}
+
+		protected override void OnTearDown()
+		{
+			if (_backupCulture != null)
+			{
+				CultureInfo.CurrentCulture = _backupCulture;
+				CultureInfo.CurrentUICulture = _backupUICulture;
+			}
+
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+			transaction.Commit();
+		}
+
+		private static readonly string[] StringInjectionsProperties =
+		{
+			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(StringInjectionsProperties))]
+		public void SqlInjectionInStrings(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
+		}
+
+		private static readonly string[] SpecialNames =
+		{
+			"\0; drop table Entity; --",
+			"\b; drop table Entity; --",
+			"\n; drop table Entity; --",
+			"\r; drop table Entity; --",
+			"\t; drop table Entity; --",
+			"\x1A; drop table Entity; --",
+			"\"; drop table Entity; --",
+			"\\; drop table Entity; --"
+		};
+
+		[TestCaseSource(nameof(SpecialNames))]
+		public void StringsWithSpecialCharacters(string name)
+		{
+			// We may not even be able to insert the entity.
+			var wasInserted = false;
+			try
+			{
+				using var s = OpenSession();
+				using var t = s.BeginTransaction();
+				var e = new Entity { Name = name };
+				s.Save(e);
+				t.Commit();
+
+				wasInserted = true;
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The entity insertion failed with message {e}");
+			}
+
+			try
+			{
+				using var session = OpenSession();
+				Entity.ArbitraryStringValue = name;
+				var list = session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").List<Entity>();
+				if (wasInserted && list.Count != 1)
+					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The query has failed with message {e}");
+			}
+
+			// Check the db is not wrecked.
+			if (wasInserted)
+			{
+				using var session = OpenSession();
+				var list = session
+					.CreateQuery("from Entity e where e.Name = :name")
+					.SetString("name", name)
+					.List<Entity>();
+				Assert.That(list, Has.Count.EqualTo(1));
+			}
+			else
+			{
+				using var session = OpenSession();
+				var all = session.CreateQuery("from Entity e").List<Entity>();
+				Assert.That(all, Has.Count.GreaterThan(0));
+			}
+		}
+
+		[Test]
+		public void SqlInjectionInStringDiscriminator()
+		{
+			using var session = OpenSession();
+
+			session.Save(new Subclass1 { Name = "Subclass1" });
+			session.Save(new Subclass2 { Name = "Subclass2" });
+
+			// ObjectToSQLString is used for generating the inserts.
+			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");
+
+			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
+			{
+				var query = session.CreateQuery($"from {entityName}");
+				IList list = null;
+				Assert.That(() => list = query.List(), Throws.Nothing, $"Unable to list entities of {entityName}");
+				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
+			}
+		}
+
+		private static readonly string[] CharInjectionsProperties =
+		{
+			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
+		};
+
+		[TestCaseSource(nameof(CharInjectionsProperties))]
+		public void SqlInjectionInChar(string propertyName)
+		{
+			using var session = OpenSession();
+			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
+		}
+
+		private static readonly string[] CharEnumInjections =
+		{
+			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
+		};
+
+		[TestCaseSource(nameof(CharEnumInjections))]
+		public void SqlInjectionWithCharEnum(string enumName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
+		}
+
+		private static readonly string[] UriInjections =
+		{
+			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(UriInjections))]
+		public void SqlInjectionWithUri(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
+		}
+
+		private static readonly string[] NumericalTypesInjections =
+		{
+			nameof(Entity.ByteProperty),
+			nameof(Entity.DecimalProperty),
+			nameof(Entity.DoubleProperty),
+			nameof(Entity.FloatProperty),
+			nameof(Entity.ShortProperty),
+			nameof(Entity.IntProperty),
+			nameof(Entity.LongProperty),
+			nameof(Entity.SByteProperty),
+			nameof(Entity.UShortProperty),
+			nameof(Entity.UIntProperty),
+			nameof(Entity.ULongProperty)
+		};
+
+		[TestCaseSource(nameof(NumericalTypesInjections))]
+		public void SqlInjectionInNumericalType(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			Entity.ArbitraryStringValue = "0; drop table Entity; --";
+			using (var session = OpenSession())
+			{
+				IQuery query;
+				// Defining that query is invalid and should throw.
+				try
+				{
+					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
+				}
+				catch (Exception ex)
+				{
+					// All good.
+					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
+					// Needed for the compiler who does not know "Pass" always throw.
+					return;
+				}
+
+				// The query definition has been accepted, run it.
+				try
+				{
+					query.List<Entity>();
+				}
+				catch (Exception ex)
+				{
+					// Expecting no exception at that point, but the test is to check if the injection succeeded.
+					Assert.Warn($"The wicked query execution has failed: {ex}");
+				}
+			}
+
+			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
+			using (var session = OpenSession())
+			{
+				IList<Entity> list = null;
+				Assert.That(() => list = session.CreateQuery("from Entity e").List<Entity>(), Throws.Nothing);
+				Assert.That(list, Has.Count.GreaterThan(0));
+			}
+		}
+
+		private static readonly string[] DateTypesInjections =
+		{
+			nameof(Entity.DateTimeProperty),
+			nameof(Entity.DateProperty),
+			nameof(Entity.DateTimeOffsetProperty),
+			nameof(Entity.TimeProperty)
+		};
+
+		[TestCaseSource(nameof(DateTypesInjections))]
+		public void SqlInjectionWithDatetime(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			var wickedCulture = new CultureInfo("en-US");
+			if (propertyName == nameof(Entity.TimeProperty))
+				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
+			else
+				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
+			CultureInfo.CurrentCulture = wickedCulture;
+			CultureInfo.CurrentUICulture = wickedCulture;
+
+			using var session = OpenSession();
+
+			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
+				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
+			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+
+		private static readonly string[] GuidInjections =
+		{
+			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
+		};
+
+		[TestCaseSource(nameof(GuidInjections))]
+		public void SqlInjectionWithGuid(string injection)
+		{
+			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+	}
+}

programming_language: C#
filename: Hierarchy.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,18 @@
+﻿using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	public class BaseClass
+	{
+		public virtual Guid Id { get; set; }
+		public virtual string Name { get; set; }
+	}
+
+	public class Subclass1 : BaseClass
+	{
+	}
+
+	public class Subclass2 : BaseClass
+	{
+	}
+}

programming_language: C#
filename: SqlInsertBuilderFixture.cs
change_type: ModificationType.MODIFY
diff: @@ -56,7 +56,7 @@ public void Commented()
 			insert.SetTableName("test_insert_builder");
 
 #pragma warning disable CS0618 // Type or member is obsolete
-			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.String);
+			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.AnsiString);
 #pragma warning restore CS0618 // Type or member is obsolete
 			insert.SetComment("Test insert");
 			string expectedSql =

programming_language: C#
filename: TestDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -105,7 +105,7 @@ public bool SupportsEmptyInsertsOrHasNonIdentityNativeGenerator
 		/// </summary>
 		public virtual bool SupportsNonDataBoundCondition => true;
 
-		public bool SupportsSqlType(SqlType sqlType)
+		public virtual bool SupportsSqlType(SqlType sqlType)
 		{
 			try
 			{

programming_language: C#
filename: Oracle10gTestDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,6 @@
-﻿using System.Runtime.InteropServices;
+﻿using System.Data;
+using System.Runtime.InteropServices;
+using NHibernate.SqlTypes;
 
 namespace NHibernate.Test.TestDialects
 {
@@ -15,6 +17,16 @@ public Oracle10gTestDialect(Dialect.Dialect dialect) : base(dialect)
 
 		public override bool SupportsAggregateInSubSelect => true;
 
+		public override bool SupportsSqlType(SqlType sqlType)
+		{
+			// The Oracle dialects define types for DbType the Oracle driver does not support.
+			return sqlType.DbType switch
+			{
+				DbType.UInt16 or DbType.UInt32 or DbType.UInt64 => false,
+				_ => base.SupportsSqlType(sqlType)
+			};
+		}
+
 		/// <inheritdoc />
 		/// <remarks>Canceling a query hangs under Linux with OracleManagedDataClientDriver 21.6.1.</remarks>
 		public override bool SupportsCancelQuery => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

programming_language: C#
filename: SapSQLAnywhere17TestDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,7 @@
-﻿namespace NHibernate.Test.TestDialects
+﻿using System.Data;
+using NHibernate.SqlTypes;
+
+namespace NHibernate.Test.TestDialects
 {
 	public class SapSQLAnywhere17TestDialect : TestDialect
 	{
@@ -40,8 +43,18 @@ public SapSQLAnywhere17TestDialect(Dialect.Dialect dialect)
 		public override bool HasBrokenTypeInferenceOnSelectedParameters => true;
 
 		/// <summary>
-		/// Does not support SELECT FOR UPDATE 
+		/// Does not support SELECT FOR UPDATE.
 		/// </summary>
 		public override bool SupportsSelectForUpdate => false;
+
+		public override bool SupportsSqlType(SqlType sqlType)
+		{
+			// The Anywhere dialects define types for DbType the Anywhere driver does not support.
+			return sqlType.DbType switch
+			{
+				DbType.SByte => false,
+				_ => base.SupportsSqlType(sqlType)
+			};
+		}
 	}
 }

programming_language: C#
filename: ByteType.cs
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@
 using System.Collections;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: Int16Type.cs
change_type: ModificationType.MODIFY
diff: @@ -10,11 +10,12 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {

programming_language: C#
filename: Int32Type.cs
change_type: ModificationType.MODIFY
diff: @@ -10,11 +10,12 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {

programming_language: C#
filename: Int64Type.cs
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: TicksType.cs
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,7 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: TimeAsTimeSpanType.cs
change_type: ModificationType.MODIFY
diff: @@ -10,11 +10,12 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {

programming_language: C#
filename: TimeSpanType.cs
change_type: ModificationType.MODIFY
diff: @@ -10,11 +10,12 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {

programming_language: C#
filename: UInt16Type.cs
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: UInt32Type.cs
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: UInt64Type.cs
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 

programming_language: C#
filename: Environment.cs
change_type: ModificationType.MODIFY
diff: @@ -119,6 +119,12 @@ public static string Version
 		/// <summary> Enable formatting of SQL logged to the console</summary>
 		public const string FormatSql = "format_sql";
 
+		/// <summary>
+		/// Indicates if the database needs to have backslash escaped in string literals.
+		/// </summary>
+		/// <remarks>The default value is dialect dependent.</remarks>
+		public const string EscapeBackslashInStrings = "escape_backslash_in_strings";
+
 		// Since v5.0.1
 		[Obsolete("This setting has no usages and will be removed in a future version")]
 		public const string UseGetGeneratedKeys = "jdbc.use_get_generated_keys";

programming_language: C#
filename: DB2Dialect.cs
change_type: ModificationType.MODIFY
diff: @@ -5,6 +5,7 @@
 using NHibernate.Dialect.Function;
 using NHibernate.Dialect.Schema;
 using NHibernate.SqlCommand;
+using NHibernate.SqlTypes;
 
 namespace NHibernate.Dialect
 {
@@ -296,6 +297,30 @@ public override string ForUpdateString
 
 		public override long TimestampResolutionInTicks => 10L; // Microseconds.
 
+		/// <inheritdoc />
+		public override string ToStringLiteral(string value, SqlType type)
+		{
+			if (value == null)
+				throw new System.ArgumentNullException(nameof(value));
+			if (type == null)
+				throw new System.ArgumentNullException(nameof(value));
+
+			// See https://www.ibm.com/docs/en/db2/11.5?topic=elements-constants#r0000731__title__7
+			var literal = new StringBuilder(value);
+			var isUnicode = type.DbType == DbType.String || type.DbType == DbType.StringFixedLength;
+			if (isUnicode)
+				literal.Replace(@"\", @"\\");
+
+			literal
+				.Replace("'", "''")
+				.Insert(0, '\'')
+				.Append('\'');
+
+			if (isUnicode)
+				literal.Insert(0, "U&");
+			return literal.ToString();
+		}
+
 		#region Overridden informational metadata
 
 		public override bool SupportsNullInUnique => false;

programming_language: C#
filename: Dialect.cs
change_type: ModificationType.MODIFY
diff: @@ -208,6 +208,7 @@ public virtual void Configure(IDictionary<string, string> settings)
 			DefaultCastLength = PropertiesHelper.GetInt32(Environment.QueryDefaultCastLength, settings, 4000);
 			DefaultCastPrecision = PropertiesHelper.GetByte(Environment.QueryDefaultCastPrecision, settings, null) ?? 29;
 			DefaultCastScale = PropertiesHelper.GetByte(Environment.QueryDefaultCastScale, settings, null) ?? 10;
+			EscapeBackslashInStrings = PropertiesHelper.GetBoolean(Environment.EscapeBackslashInStrings, settings, EscapeBackslashInStrings);
 		}
 
 		#endregion
@@ -1354,14 +1355,6 @@ public virtual CaseFragment CreateCaseFragment()
 			return new ANSICaseFragment(this);
 		}
 
-		/// <summary> The SQL literal value to which this database maps boolean values. </summary>
-		/// <param name="value">The boolean value </param>
-		/// <returns> The appropriate SQL literal. </returns>
-		public virtual string ToBooleanValueString(bool value)
-		{
-			return value ? "1" : "0";
-		}
-
 		internal static void ExtractColumnOrAliasNames(SqlString select, out List<SqlString> columnsOrAliases, out Dictionary<SqlString, SqlString> aliasToColumn, out Dictionary<SqlString, SqlString> columnToAlias)
 		{
 			columnsOrAliases = new List<SqlString>();
@@ -2076,6 +2069,55 @@ public virtual string ConvertQuotesForCatalogName(string catalogName)
 
 		#endregion
 
+		#region Literals support
+
+		/// <summary>The SQL literal value to which this database maps boolean values.</summary>
+		/// <param name="value">The boolean value.</param>
+		/// <returns>The appropriate SQL literal.</returns>
+		public virtual string ToBooleanValueString(bool value)
+			=> value ? "1" : "0";
+
+		/// <summary>
+		/// <see langword="true" /> if the database needs to have backslash escaped in string literals.
+		/// </summary>
+		/// <remarks><see langword="false" /> by default in the base dialect, to conform to SQL standard.</remarks>
+		protected virtual bool EscapeBackslashInStrings { get; set; }
+
+		/// <summary>
+		/// <see langword="true" /> if the database needs to have Unicode literals prefixed by <c>N</c>.
+		/// </summary>
+		/// <remarks><see langword="false" /> by default in the base dialect.</remarks>
+		protected virtual bool UseNPrefixForUnicodeStrings => false;
+
+		/// <summary>The SQL string literal value to which this database maps string values.</summary>
+		/// <param name="value">The string value.</param>
+		/// <param name="type">The SQL type of the string value.</param>
+		/// <returns>The appropriate SQL string literal.</returns>
+		/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> or
+		/// <paramref name="type"/> is <see langword="null" />.</exception>
+		public virtual string ToStringLiteral(string value, SqlType type)
+		{
+			if (value == null)
+				throw new ArgumentNullException(nameof(value));
+			if (type == null)
+				throw new ArgumentNullException(nameof(type));
+
+			var literal = new StringBuilder(value);
+			if (EscapeBackslashInStrings)
+				literal.Replace(@"\", @"\\");
+
+			literal
+				.Replace("'", "''")
+				.Insert(0, '\'')
+				.Append('\'');
+
+			if (UseNPrefixForUnicodeStrings && (type.DbType == DbType.String || type.DbType == DbType.StringFixedLength))
+				literal.Insert(0, 'N');
+			return literal.ToString();
+		}
+
+		#endregion
+
 		#region Union subclass support
 
 		/// <summary> 

programming_language: C#
filename: IngresDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -62,6 +62,11 @@ public IngresDialect()
 		/// <inheritdoc />
 		public override int MaxAliasLength => 32;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for Ingres,
+		/// <see href="https://docs.actian.com/ingres/11.0/index.html#page/SQLRef/String_Literals.htm#ww110572" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;

programming_language: C#
filename: MsSql2000Dialect.cs
change_type: ModificationType.MODIFY
diff: @@ -755,6 +755,10 @@ public override bool SupportsSqlBatches
 		/// </summary>
 		public override int? MaxNumberOfParameters => 2097;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Server.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;

programming_language: C#
filename: MySQLDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -530,6 +530,16 @@ public override long TimestampResolutionInTicks
 		/// </remarks>
 		public override bool SupportsConcurrentWritingConnectionsInSameTransaction => false;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for MySQL,
+		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for MySQL,
+		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;

programming_language: C#
filename: Oracle8iDialect.cs
change_type: ModificationType.MODIFY
diff: @@ -102,6 +102,7 @@ public override void Configure(IDictionary<string, string> settings)
 
 			// If changing the default value, keep it in sync with OracleDataClientDriverBase.Configure.
 			UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, settings, false);
+
 			RegisterCharacterTypeMappings();
 			RegisterFloatingPointTypeMappings();
 		}
@@ -561,6 +562,10 @@ public override long TimestampResolutionInTicks
 		/// <inheritdoc />
 		public override int MaxAliasLength => 30;
 
+		/// <inheritdoc />
+		/// <remarks>Returns the same value as <see cref="UseNPrefixedTypesForUnicode" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => UseNPrefixedTypesForUnicode;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList

programming_language: C#
filename: SybaseASA9Dialect.cs
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@
 namespace NHibernate.Dialect
 {
 	/// <summary>
-	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0
+	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0. (Renamed SQL Anywhere from its version 10.)
 	/// </summary>
 	/// <remarks>
 	/// <p>
@@ -188,5 +188,15 @@ private static int GetAfterSelectInsertPoint(SqlString sql)
 			}
 			return 0;
 		}
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
 	}
 }

programming_language: C#
filename: SybaseSQLAnywhere10Dialect.cs
change_type: ModificationType.MODIFY
diff: @@ -969,5 +969,15 @@ public override IDataBaseSchema GetDataBaseSchema(DbConnection connection)
 		/// <inheritdoc />
 		/// <remarks>SQL Anywhere has a micro-second resolution.</remarks>
 		public override long TimestampResolutionInTicks => 10L;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
 	}
 }

programming_language: C#
filename: AbstractCharType.cs
change_type: ModificationType.MODIFY
diff: @@ -51,9 +51,7 @@ public override void Set(DbCommand cmd, object value, int index, ISessionImpleme
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return '\'' + value.ToString() + '\'';
-		}
+			=> dialect.ToStringLiteral(value.ToString(), SqlType);
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.

programming_language: C#
filename: AbstractDateTimeType.cs
change_type: ModificationType.MODIFY
diff: @@ -176,7 +176,7 @@ public override object FromStringValue(string xml)
 		public override object DefaultValue => BaseDateValue;
 
 		/// <inheritdoc />
-		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
-			"'" + (DateTime) value + "'";
+		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
+			=> dialect.ToStringLiteral(((DateTime) value).ToString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }

programming_language: C#
filename: AbstractStringType.cs
change_type: ModificationType.MODIFY
diff: @@ -134,10 +134,9 @@ public object StringToObject(string xml)
 
 		#region ILiteralType Members
 
+		/// <inheritdoc />
 		public string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + (string)value + "'";
-		}
+			=> dialect.ToStringLiteral((string)value, SqlType);
 
 		#endregion
 

programming_language: C#
filename: ByteType.cs
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@
 using System.Collections;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -54,7 +55,7 @@ public override string Name
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((byte)value).ToString(CultureInfo.InvariantCulture);
 		}
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml

programming_language: C#
filename: CharBooleanType.cs
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,6 @@
 using System.Data.Common;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using NHibernate.Util;
 
 namespace NHibernate.Type
 {
@@ -57,9 +56,7 @@ private string ToCharacter(object value)
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ToCharacter(value) + "'";
-		}
+			=> dialect.ToStringLiteral(ToCharacter(value), SqlType);
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.

programming_language: C#
filename: DateTimeOffSetType.cs
change_type: ModificationType.MODIFY
diff: @@ -155,8 +155,6 @@ public override object FromStringValue(string xml)
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((DateTimeOffset) value) + "'";
-		}
+			=> dialect.ToStringLiteral(((DateTimeOffset) value).ToString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }

programming_language: C#
filename: DateType.cs
change_type: ModificationType.MODIFY
diff: @@ -93,8 +93,8 @@ public override string ToLoggableString(object value, ISessionFactoryImplementor
 		public override object DefaultValue => customBaseDate;
 
 		/// <inheritdoc />
-		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
-			"\'" + ((DateTime)value).ToShortDateString() + "\'";
+		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
+			=> dialect.ToStringLiteral(((DateTime) value).ToShortDateString(), SqlTypeFactory.GetAnsiString(50));
 
 		// Since v5
 		[Obsolete("Its only parameter, BaseValue, is obsolete.")]

programming_language: C#
filename: DecimalType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -66,7 +67,7 @@ public override object FromStringValue(string xml)
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((decimal)value).ToString(CultureInfo.InvariantCulture);
 		}
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml

programming_language: C#
filename: DoubleType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -66,7 +67,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((double)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: EnumCharType.cs
change_type: ModificationType.MODIFY
diff: @@ -171,8 +171,6 @@ public override object FromStringValue(string xml)
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return '\'' + GetValue(value).ToString() + '\'';
-		}
+			=> dialect.ToStringLiteral(GetValue(value).ToString(), SqlType);
 	}
 }

programming_language: C#
filename: GuidType.cs
change_type: ModificationType.MODIFY
diff: @@ -88,7 +88,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return "'" + value + "'";
+			return dialect.ToStringLiteral(value.ToString(), SqlTypeFactory.GetAnsiString(50));
 		}
 	}
 }

programming_language: C#
filename: Int16Type.cs
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,11 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
@@ -114,7 +115,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((short)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: Int32Type.cs
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,11 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
@@ -114,7 +115,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((int)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: Int64Type.cs
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -114,7 +115,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((long)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: SByteType.cs
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -116,7 +117,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: SingleType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -91,7 +92,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((float)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: TicksType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -100,7 +101,7 @@ public new object StringToObject(string xml)
 		/// <inheritdoc />
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((DateTime)value).Ticks.ToString() + '\'';
+			return '\'' + ((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }

programming_language: C#
filename: TimeAsTimeSpanType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,11 @@
 ﻿using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
@@ -141,7 +142,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
+			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }

programming_language: C#
filename: TimeSpanType.cs
change_type: ModificationType.MODIFY
diff: @@ -1,10 +1,11 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
@@ -128,7 +129,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
+			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }

programming_language: C#
filename: TimeType.cs
change_type: ModificationType.MODIFY
diff: @@ -171,8 +171,6 @@ public override object DefaultValue
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((DateTime)value).ToShortTimeString() + "'";
-		}
+			=> dialect.ToStringLiteral(((DateTime) value).ToShortTimeString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }

programming_language: C#
filename: UInt16Type.cs
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -114,7 +115,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((ushort)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: UInt32Type.cs
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -114,7 +115,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((uint)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: UInt64Type.cs
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
@@ -113,7 +114,7 @@ public override object DefaultValue
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((ulong)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }

programming_language: C#
filename: UriType.cs
change_type: ModificationType.MODIFY
diff: @@ -84,9 +84,7 @@ public override object FromStringValue(string xml)
 		}
 
 		public string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((Uri)value).OriginalString + "'";
-		}
+			=> dialect.ToStringLiteral(((Uri) value).OriginalString, SqlType);
 
 		/// <inheritdoc />
 		public override object Assemble(object cached, ISessionImplementor session, object owner)

programming_language: C#
filename: nhibernate-configuration.xsd
change_type: ModificationType.MODIFY
diff: @@ -176,6 +176,14 @@
 										<xs:enumeration value="default_flush_mode" />
 										<xs:enumeration value="use_sql_comments" />
 										<xs:enumeration value="format_sql" />
+										<xs:enumeration value="escape_backslash_in_strings">
+											<xs:annotation>
+												<xs:documentation>
+													Indicates if the database needs to have backslash escaped in string literals. The default is
+													dialect dependent.
+												</xs:documentation>
+											</xs:annotation>
+										</xs:enumeration>
 										<xs:enumeration value="collectiontype.factory_class" />
 										<xs:enumeration value="order_inserts" />
 										<xs:enumeration value="order_updates" />

programming_language: SQL