SQL Unquoted идентификаторC#

Место общения программистов C#
Ответить
Anonymous
 SQL Unquoted идентификатор

Сообщение Anonymous »

Я работаю над приложением .NET, которое необходимо выполнить SQL, параметризованные объектами базы данных, такими как таблицы или столбцы. Приложение поддерживает как поставщики Microsoft SQL Server, так и поставщики Oracle Ado.net, в среде, где оно не может предположить что -либо о базе данных. Имена объектов DB могут приходить из любого места, обычно сама БД. Насколько мне известно, нет хорошего способа использовать параметры, в которых ожидается идентификатор без использования PL/SQL, которого я хотел бы избежать из-за дополнительных накладных расходов с точки зрения обслуживания и тому подобного.using (OracleCommand cmd = connection.CreateCommand())
{
// Potential SQL injection vulnerability?
cmd.CommandText = $"SELECT id, name FROM {tableName} WHERE name = :name";
// Unfortunately not valid :(
//cmd.CommandText = $"SELECT id, name FROM :tableName WHERE name = :name";
//cmd.Parameters.Add("tableName", tableName);
cmd.Parameters.Add("name", name);
return cmd.ExecuteReader();
}

Вот реальный код, который я написал с использованием Oracle PL/SQL:
// Wish we could use this:
//const string cmdText = "ALTER SESSION SET CURRENT_SCHEMA = :schemaName"

// Or perhaps something like this:
//const string cmdText = $"ALTER SESSION SET CURRENT_SCHEMA = {Something.Validate(schemaName)}"

// Alas:
const string cmdText = @"
DECLARE
v_schema_name VARCHAR2(128);
BEGIN
BEGIN
-- Try literal schema name first (handles quoted schemas)
v_schema_name := dbms_assert.schema_name(:schemaName);
EXCEPTION WHEN OTHERS THEN
-- Fall back to uppercase version for unquoted schemas
v_schema_name := dbms_assert.schema_name(UPPER(:schemaName));
END;

EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA=""' || v_schema_name || '""';
END;";

using (OracleCommand cmd = connection.CreateCommand())
{
cmd.CommandText = cmdText;
cmd.Parameters.Add("schemaName", schemaName);
cmd.ExecuteNonQuery();
}

Модератор этого сайта любезно пометил этот вопрос как дубликат вопроса Как безопасно параметризовать имена таблиц в C#, чтобы предотвратить SQL-инъекцию?, что привлекло мое внимание к тому, что SqlCommandBuilder.QuoteIdentifier можно использовать с SQL Server для безопасной динамической вставки идентификаторов, но только тогда, когда параметры сортировки сервера нечувствителен к регистру. В противном случае проверка идентификатора SQL Server на основе правил именования для «обычных идентификаторов» кажется довольно простой, за исключением проверки зарезервированных слов:

Идентификатор не должен быть зарезервированным словом Transact-SQL. SQL Server резервирует зарезервированные слова как в верхнем, так и в нижнем регистре. [...] Зарезервированные слова зависят от уровня совместимости базы данных. Этот уровень можно установить с помощью оператора уровня совместимости ALTER DATABASE.

Этот вопрос также был помечен как дубликат вопроса Как проверить допустимое имя таблицы Oracle с помощью sql/plsql, которого мне хотелось бы избежать из-за дополнительных накладных расходов, которые я продемонстрировал ранее. Правила идентификаторов Oracle еще более сложны, поскольку они зависят от версии БД, набора символов, с которым была установлена ​​БД, а также не могут быть зарезервированными словами. Естественно, я мог бы просто проверить идентификатор Oracle, не принимая во внимание кодировку символов (предполагая Unicode), но тогда меня беспокоят уязвимости, связанные с кодировкой, которые находятся за пределами моего понимания.
Итак, вопрос: необоснованны ли мои опасения? Если нет, то как мне правильно проверить (не заключенные в кавычки) идентификаторы?

Вот мой предварительный и непроверенный код проверки для справки (вероятно, есть ошибки, но это просто демонстрация моего наивного подхода к проверке идентификаторов):
using System;
using System.Collections.Generic;
using System.Text;

namespace DbProviderAbstractions
{
public abstract class DbProvider
{
///
/// Validates whether the given identifier can be used unquoted.
///
/// The identifier to validate.
///
/// true if is a valid unquoted identifier,
/// false if must be quoted.
///
///
/// If is already quoted,
/// this method shall return false
/// (an identifier containing quotes is not a valid unquoted identifier).
///
public abstract bool Validate(string identifier);

///
/// Throw if a given identifier is not valid as an unquoted identifier.
///
/// The identifier to validate.
/// The unmodified identifier.
/// When the identifier contains special characters that must be quoted.
public string Unquoted(string identifier)
{
return Validate(identifier) ? identifier : throw new ArgumentOutOfRangeException("Nooooooo!");
}
}

public class SqlProvider : DbProvider
{
///
/// SQL Server reserved words as of SQL Server 2022.
///
public static readonly ISet ReservedWords = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"ABSOLUTE", "ACTION", "ADA", "ADD", "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "AS", "ASC", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", "AUTHORIZATION", "AVG",
"BACKUP", "BEFORE", "BEGIN", "BETWEEN", "BINARY", "BIT", "BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BREADTH", "BREAK", "BROWSE", "BULK", "BY",
"CALL", "CALLED", "CARDINALITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHAR_LENGTH", "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CHECKPOINT", "CLASS", "CLOB", "CLOSE", "CLUSTERED", "COALESCE", "COLLATE", "COLLATION", "COLLECT", "COLUMN", "COMMIT", "COMPLETION", "COMPUTE", "CONDITION", "CONNECT", "CONNECTION", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONTAINS", "CONTAINSTABLE", "CONTINUE", "CONVERT", "CORR", "CORRESPONDING", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE",
"DATA", "DATABASE", "DATE", "DAY", "DBCC", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DENY", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DESTROY", "DESTRUCTOR", "DETERMINISTIC", "DIAGNOSTICS", "DICTIONARY", "DISCONNECT", "DISK", "DISTINCT", "DISTRIBUTED", "DOMAIN", "DOUBLE", "DROP", "DUMP", "DYNAMIC",
"EACH", "ELEMENT", "ELSE", "END", "END-EXEC", "EQUALS", "ERRLVL", "ESCAPE", "EVERY", "EXCEPT", "EXCEPTION", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXTERNAL", "EXTRACT",
"FALSE", "FETCH", "FILE", "FILLFACTOR", "FILTER", "FIRST", "FLOAT", "FOR", "FOREIGN", "FORTRAN", "FOUND", "FREE", "FREETEXT", "FREETEXTTABLE", "FROM", "FULL", "FULLTEXTTABLE", "FUNCTION", "FUSION",
"GENERAL", "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", "GROUPING",
"HAVING", "HOLD", "HOLDLOCK", "HOST", "HOUR",
"IDENTITY", "IDENTITY_INSERT", "IDENTITYCOL", "IF", "IGNORE", "IMMEDIATE", "IN", "INCLUDE", "INDEX", "INDICATOR", "INITIALIZE", "INITIALLY", "INNER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", "INTERSECTION", "INTERVAL", "INTO", "IS", "ISOLATION", "ITERATE",
"JOIN",
"KEY", "KILL",
"LABEL", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEADING", "LEFT", "LESS", "LEVEL", "LIKE", "LIKE_REGEX", "LIMIT", "LINENO", "LN", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", "LOWER",
"MAP", "MATCH", "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MOD", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MULTISET",
"NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NOCHECK", "NONCLUSTERED", "NONE", "NORMALIZE", "NOT", "NULL", "NULLIF", "NUMERIC",
"OBJECT", "OCCURRENCES_REGEX", "OCTET_LENGTH", "OF", "OFF", "OFFSETS", "OLD", "ON", "ONLY", "OPEN", "OPENDATASOURCE", "OPENQUERY", "OPENROWSET", "OPENXML", "OPERATION", "OPTION", "OR", "ORDER", "ORDINALITY", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "OVERLAY",
"PAD", "PARAMETER", "PARAMETERS", "PARTIAL", "PARTITION", "PASCAL", "PATH", "PERCENT", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "PIVOT", "PLAN", "POSITION", "POSITION_REGEX", "POSTFIX", "PRECISION", "PREFIX", "PREORDER", "PREPARE", "PRESERVE", "PRIMARY", "PRINT", "PRIOR", "PRIVILEGES", "PROC", "PROCEDURE", "PUBLIC",
"RAISERROR", "RANGE", "READ", "READS", "READTEXT", "REAL", "RECONFIGURE", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELATIVE", "RELEASE", "REPLICATION", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNS", "REVERT", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWCOUNT", "ROWGUIDCOL", "ROWS", "RULE",
"SAVE", "SAVEPOINT", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SECURITYAUDIT", "SELECT", "SEMANTICKEYPHRASETABLE", "SEMANTICSIMILARITYDETAILSTABLE", "SEMANTICSIMILARITYTABLE", "SENSITIVE", "SEQUENCE", "SESSION", "SESSION_USER", "SET", "SETS", "SETUSER", "SHUTDOWN", "SIMILAR", "SIZE", "SMALLINT", "SOME", "SPACE", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLCA", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATEMENT", "STATIC", "STATISTICS", "STDDEV_POP", "STDDEV_SAMP", "STRUCTURE", "SUBMULTISET", "SUBSTRING", "SUBSTRING_REGEX", "SUM", "SYMMETRIC", "SYSTEM", "SYSTEM_USER",
"TABLE", "TABLESAMPLE", "TEMPORARY", "TERMINATE", "TEXTSIZE", "THAN", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TOP", "TRAILING", "TRAN", "TRANSACTION", "TRANSLATE", "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TRY_CONVERT", "TSEQUAL",
"UESCAPE", "UNDER", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UNPIVOT", "UPDATE", "UPDATETEXT", "UPPER", "USAGE", "USE", "USER", "USING",
"VALUE", "VALUES", "VAR_POP", "VAR_SAMP", "VARCHAR", "VARIABLE", "VARYING", "VIEW",
"WAITFOR", "WHEN", "WHENEVER", "WHERE", "WHILE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHIN GROUP", "WITHOUT", "WORK", "WRITE", "WRITETEXT",
"XMLAGG", "XMLATTRIBUTES", "XMLBINARY", "XMLCAST", "XMLCOMMENT", "XMLCONCAT", "XMLDOCUMENT", "XMLELEMENT", "XMLEXISTS", "XMLFOREST", "XMLITERATE", "XMLNAMESPACES", "XMLPARSE", "XMLPI", "XMLQUERY", "XMLSERIALIZE", "XMLTABLE", "XMLTEXT", "XMLVALIDATE",
"YEAR",
"ZONE",
};

///
public override bool Validate(string identifier)
{
char first = identifier[0];
bool validFirstChar = char.IsLetter(first)
|| first == '_'
|| first == '@'
|| first == '#';

bool invalid = string.IsNullOrEmpty(identifier)
|| ReservedWords.Contains(identifier)
|| Encoding.Unicode.GetByteCount(identifier) > 128
|| !validFirstChar;

if (invalid)
{
return false;
}

foreach (char c in identifier)
{
if (char.IsLetterOrDigit(c))
{
continue;
}

switch (c)
{
case '@':
case '$':
case '#':
case '_':
continue;
}

return false;
}

return true;
}
}

public class OracleProvider : DbProvider
{
///
/// Oracle reserved words as of 19c.
///
public static readonly ISet ReservedWords = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"ALL", "ALTER", "AND", "ANY", "AS", "ASC", "AT",
"BEGIN", "BETWEEN", "BY",
"CASE", "CHECK", "CLUSTERS", "CLUSTER", "COLAUTH", "COLUMNS", "COMPRESS", "CONNECT", "CRASH", "CREATE", "CURSOR",
"DECLARE", "DEFAULT", "DESC", "DISTINCT", "DROP",
"ELSE", "END", "EXCEPTION", "EXCLUSIVE",
"FETCH", "FOR", "FROM", "FUNCTION",
"GOTO", "GRANT", "GROUP",
"HAVING",
"IDENTIFIED", "IF", "IN", "INDEX", "INDEXES", "INSERT", "INTERSECT", "INTO", "IS",
"LIKE", "LOCK",
"MINUS", "MODE",
"NOCOMPRESS", "NOT", "NOWAIT", "NULL",
"OF", "ON", "OPTION", "OR", "ORDER", "OVERLAPS",
"PROCEDURE", "PUBLIC",
"RESOURCE", "REVOKE",
"SELECT", "SHARE", "SIZE", "SQL", "START", "SUBTYPE",
"TABAUTH", "TABLE", "THEN", "TO", "TYPE",
"UNION", "UNIQUE", "UPDATE",
"VALUES", "VIEW", "VIEWS",
"WHEN", "WHERE", "WITH",
};

///
public override bool Validate(string identifier)
{
bool invalid = string.IsNullOrEmpty(identifier)
|| ReservedWords.Contains(identifier)
|| Encoding.Unicode.GetByteCount(identifier) > 128
|| !char.IsLetter(identifier[0]);

if (invalid)
{
return false;
}

foreach (char c in identifier)
{
if (char.IsLetterOrDigit(c))
{
continue;
}

switch (c)
{
case '_':
case '$':
case '#':
continue;
}

return false;
}

return true;
}
}
}


Подробнее здесь: https://stackoverflow.com/questions/797 ... validation
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»