9

使用什么函数可以使字符串安全地包含在 SQL 查询中?例如,撇号需要修正,毫无疑问还会出现其他问题。我想要一个坚如磐石的功能,并且可以在作恶者设计的任何可能输入的情况下工作。

现在,在群众告诉我使用查询参数和/或否决/关闭这个问题之前,请考虑以下几点:

  • 我被困在使用设计不佳的 API 的第 3 方库。API应该被调用如下:

    dataObjectVariable.FindWhere("WHERE RecordID = '(your string here)'");
    

    现在,我 100% 同意你的观点,这不是一个好的 API,因为它 (1) 将内部数据库字段名称公开给用户,这些名称是实现细节,(2) 没有提供使用参数的机会,这首先会避免这个问题地方,(3)真的,你可以说SQL本身是一个实现细节,不应该暴露。但我坚持使用这个 API,因为它需要与行业领先的系统之一集成。我们也无法真正要求他们更改 API。

  • 我在这个网站上搜索了与这个问题有关的其他问题,但发现答案往往强烈建议参数化查询。试图建议编写一个函数来清理字符串的答案通常被否决,没有经过深思熟虑等 - 我不确定我是否相信他们。

我只搜索字符串,而不是数字、日期等其他数据类型。同样,我 100% 了解使用参数化查询的好处,我希望我能使用它们,但我不能,因为我的手绑在这个上。

4

3 回答 3

4

我必须在我们的一个应用程序中使用类似的 API。这是我用来手动规避 SQL 注入的验证例程:



internal class SqlInjectionValidator
{

    internal static readonly List _s_keywords = new List
    {
        "alter",
        "begin",
        "commit",
        "create",
        "delete",
        "drop",
        "exec",
        "execute",
        "grant",
        "insert",
        "kill",
        "load",
        "revoke",
        "rollback",
        "shutdown",
        "truncate",
        "update",
        "use",
        "sysobjects"
    };

    private string _sql;
    private int _pos;
    private readonly Stack _literalQuotes = new Stack();
    private readonly Stack _identifierQuotes = new Stack();
    private int _statementCount;

    // Returns true if s does not contain SQL keywords.
    public SqlValidationStatus Validate(string s)
    {
        if (String.IsNullOrEmpty(s))
        {
            return SqlValidationStatus.Ok;
        }

        _pos = 0;
        _sql = s.ToLower();
        _literalQuotes.Clear();
        _identifierQuotes.Clear();
        _statementCount = 0;

        List chars = new List();

        SqlValidationStatus svs;
        while (_pos = _sql.Length)
            {
                break;
            }

            if (_statementCount != 0)
            {
                return SqlValidationStatus.SqlBatchNotAllowed;
            }

            char c = _sql[_pos];
            if (IsEmbeddedQuote(c))
            {
                _pos++;
                chars.Add(_sql[_pos]);
                _pos++;
                continue;
            }

            if (c != '\'' &&
                    IsQuotedString())
            {
                chars.Add(c);
                _pos++;
                continue;
            }

            if (c != ']' &&
                    c != '[' &&
                    c != '"' &&
                    IsQuotedIdentifier())
            {
                chars.Add(c);
                _pos++;
                continue;
            }

            switch (c)
            {
                case '[':
                    if (_identifierQuotes.Count != 0)
                    {
                        return SqlValidationStatus.MismatchedIdentifierQuote;
                    }
                    svs = DisallowWord(chars);
                    if (svs != SqlValidationStatus.Ok)
                    {
                        return svs;
                    }
                    _identifierQuotes.Push(c);
                    break;

                case ']':
                    if (_identifierQuotes.Count != 1 ||
                            _identifierQuotes.Peek() != '[')
                    {
                        return SqlValidationStatus.MismatchedIdentifierQuote;
                    }
                    svs = DisallowWord(chars);
                    if (svs != SqlValidationStatus.Ok)
                    {
                        return svs;
                    }
                    _identifierQuotes.Pop();
                    break;

                case '"':
                    if (_identifierQuotes.Count == 0)
                    {
                        svs = DisallowWord(chars);
                        if (svs != SqlValidationStatus.Ok)
                        {
                            return svs;
                        }
                        _identifierQuotes.Push(c);
                    }
                    else if (_identifierQuotes.Count == 1)
                    {
                        svs = DisallowWord(chars);
                        if (svs != SqlValidationStatus.Ok)
                        {
                            return svs;
                        }
                        _identifierQuotes.Pop();
                    }
                    else
                    {
                        return SqlValidationStatus.MismatchedIdentifierQuote;
                    }
                    break;

                case '\'':
                    if (_literalQuotes.Count == 0)
                    {
                        svs = DisallowWord(chars);
                        if (svs != SqlValidationStatus.Ok)
                        {
                            return svs;
                        }
                        _literalQuotes.Push(c);
                    }
                    else if (_literalQuotes.Count == 1 &&
                            _literalQuotes.Peek() == c)
                    {
                        _literalQuotes.Pop();
                        chars.Clear();
                    }
                    else
                    {
                        return SqlValidationStatus.MismatchedLiteralQuote;
                    }
                    break;

                default:
                    if (Char.IsLetterOrDigit(c) ||
                            c == '-')
                    {
                        chars.Add(c);
                    }
                    else if (Char.IsWhiteSpace(c) ||
                            Char.IsControl(c) ||
                            Char.IsPunctuation(c))
                    {
                        svs = DisallowWord(chars);
                        if (svs != SqlValidationStatus.Ok)
                        {
                            return svs;
                        }
                        if (c == ';')
                        {
                            _statementCount++;
                        }
                    }
                    break;
            }

            _pos++;
        }

        if (_literalQuotes.Count != 0)
        {
            return SqlValidationStatus.MismatchedLiteralQuote;
        }

        if (_identifierQuotes.Count != 0)
        {
            return SqlValidationStatus.MismatchedIdentifierQuote;
        }

        if (chars.Count > 0)
        {
            svs = DisallowWord(chars);
            if (svs != SqlValidationStatus.Ok)
            {
                return svs;
            }
        }

        return SqlValidationStatus.Ok;
    }

    // Returns true if the string representation of the sequence of characters in
    // chars is a SQL keyword.
    private SqlValidationStatus DisallowWord(List chars)
    {
        if (chars.Count == 0)
        {
            return SqlValidationStatus.Ok;
        }

        string s = new String(chars.ToArray()).Trim();
        chars.Clear();

        return DisallowWord(s);
    }

    private SqlValidationStatus DisallowWord(string word)
    {
        if (word.Contains("--"))
        {
            return SqlValidationStatus.CommentNotAllowed;
        }
        if (_s_keywords.Contains(word))
        {
            return SqlValidationStatus.KeywordNotAllowed;
        }
        if (_statementCount > 0)
        {
            return SqlValidationStatus.SqlBatchNotAllowed;
        }
        if (word.Equals("go"))
        {
            _statementCount++;
        }

        return SqlValidationStatus.Ok;
    }

    private bool IsEmbeddedQuote(char curChar)
    {
        if (curChar != '\'' ||
                !IsQuotedString() ||
                IsQuotedIdentifier())
        {
            return false;
        }

        if (_literalQuotes.Peek() == curChar &&
                Peek() == curChar)
        {
            return true;
        }

        return false;
    }

    private bool IsQuotedString()
    {
        return _literalQuotes.Count > 0;
    }

    private bool IsQuotedIdentifier()
    {
        return _identifierQuotes.Count > 0;
    }

    private char Peek()
    {
        if (_pos + 1 < _sql.Length)
        {
            return _sql[_pos + 1];
        }

        return '\0';
    }

}
于 2012-11-01T03:48:25.307 回答
0

我最终挖掘了一些,发现这个“蛮力”解决方案似乎有效。 如何使用 PHP 在 SQL Server 中转义字符串?

基本上,只需将字符串作为十六进制而不是字符串发送。例如,而不是发送:

WHERE RecordID = 'DEMO'

发送这个:

WHERE RecordID = 0x44454D4F

如果字符串为空,那么作为一种特殊情况,我只是发送''

这似乎是合理的安全;我想不出一种有效的攻击方式。

通常,请注意此解决方案,因为它可能不适用于非 ANSI 字符。但是,我发现我要比较的列是“varchar”而不是“nvarchar”,所以显然 Unicode 在他们设计数据库时并没有真正提上日程。我猜如果它是“nvarchar”,我需要发送一个 UTF-16 字符串(或任何与 nvarchar 一起使用的 Unicode 编码)。

于 2012-11-01T20:51:50.457 回答
0

假设您只期望纯文本(以获得良好的记录),并且如果您可以假设您只会收到普通字符(即键盘字符),您可以接受输入,转换为 ASCII,然后将其限制为您想要的字符(通过提供允许的字符列表并从中过滤),并确保字符串长度合适。

然后只用可接受的字符构建一个新字符串,并将该字符串传入。一旦你在那里,单引号绝对是你唯一关心的问题。

如果您可以提出这些要求,那么这应该适合您。没有办法注入时髦的 Unicode 字符,或者溢出 SQL 缓冲区,或者做任何其他人们为引起问题而做的疯狂事情——您将 100% 控制进入查询的字符。

于 2012-11-01T21:17:26.323 回答