Как защититься от внедрения SQL, когда предложение WHERE создается динамически из формы поиска? - PullRequest
3 голосов
/ 12 ноября 2010

Я знаю, что единственный действительно правильный способ защиты запросов SQL от внедрения SQL в Java - это использование PreparedStatements.

Однако для такого утверждения требуется базовая структура (выбранные атрибуты, объединенные таблицы, структураусловие WHERE) не изменится.

У меня здесь есть приложение JSP, которое содержит форму поиска с примерно дюжиной полей.Но пользователю не нужно заполнять все из них - только тот, который ему нужен.Таким образом, мое условие WHERE каждый раз меняется.

Что мне делать, чтобы предотвратить внедрение SQL-кода?
Выходить из введенных пользователем значений?Написать класс-оболочку, который каждый раз создает PreparedStatement?Или что-то еще?

База данных - PostgreSQL 8.4, но я бы предпочел общее решение.

Заранее большое спасибо.

Ответы [ 5 ]

6 голосов
/ 12 ноября 2010

Вы видели JDBC NamedParameterJDBCTemplate ?

Класс NamedParameterJdbcTemplate добавляет поддержку программирования операторов JDBC с использованием именованных параметров (в отличие от программирования операторов JDBC с использованием только классического заполнителя ('? ') arguments.

Вы можете делать такие вещи, как:

String sql = "select count(0) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return namedParameterJdbcTemplate.queryForInt(sql, namedParameters);

и динамически создавать строку запроса, а затем аналогично создавать SqlParameterSource.

2 голосов
/ 12 ноября 2010

Я думаю, что по сути этот вопрос равен так же, как и другие вопросы, на которые я ссылался в моем комментарии выше, но я понимаю, почему вы не согласны - вы меняете то, что в вашем whereпредложение, основанное на том, что предоставил пользователь.

Однако это не то же самое, что использование пользовательских данных в запросе SQL, для которых вы определенно хотите использовать PreparedStatement.На самом деле это очень похоже на стандартную проблему необходимости использования оператора in с PreparedStatement (например, where fieldName in (?, ?, ?), но вы заранее не знаете, сколько ? вам понадобится).Вам просто нужно построить запрос динамически и динамически добавить параметры, основываясь на информации, предоставленной пользователем (но , а не , непосредственно включающей эту информацию в запрос).

Вот пример того, чтоЯ имею в виду:

// You'd have just the one instance of this map somewhere:
Map<String,String> fieldNameToColumnName = new HashMap<String,String>();
// You'd actually load these from configuration somewhere rather than hard-coding them
fieldNameToColumnName.put("title", "TITLE");
fieldNameToColumnName.put("firstname", "FNAME");
fieldNameToColumnName.put("lastname", "LNAME");
// ...etc.

// Then in a class somewhere that's used by the JSP, have the code that
// processes requests from users:
public AppropriateResultBean[] doSearch(Map<String,String> parameters)
throws SQLException, IllegalArgumentException
{
    StringBuilder           sql;
    String                  columnName;
    List<String>            paramValues;
    AppropriateResultBean[] rv;

    // Start the SQL statement; again you'd probably load the prefix SQL
    // from configuration somewhere rather than hard-coding it here.
    sql = new StringBuilder(2000);
    sql.append("select appropriate,fields from mytable where ");

    // Loop through the given parameters.
    // This loop assumes you don't need to preserve some sort of order
    // in the params, but is easily adjusted if you do.
    paramValues = new ArrayList<String>(parameters.size());
    for (Map.Entry<String,String> entry : parameters.entrySet())
    {
        // Only process fields that aren't blank.
        if (entry.getValue().length() > 0)
        {
            // Get the DB column name that corresponds to this form
            // field name.
            columnName = fieldNameToColumnName.get(entry.getKey());
                      // ^-- You'll probably need to prefix this with something, it's not likely to be part of this instance
            if (columnName == null)
            {
                // Somehow, the user got an unknown field into the request
                // and that got past the code calling us (perhaps the code
                // calling us just used `request.getParameterMap` directly).
                // We don't allow unknown fields.
                throw new IllegalArgumentException(/* ... */);
            }
            if (paramValues.size() > 0)
            {
                sql.append("and ");
            }
            sql.append(columnName);
            sql.append(" = ? ");
            paramValues.add(entry.getValue());
        }
    }

    // I'll assume no parameters is an invalid case, but you can adjust the
    // below if that's not correct.
    if (paramValues.size() == 0)
    {
        // My read of the problem being solved suggests this is not an
        // exceptional condition (users frequently forget to fill things
        // in), and so I'd use a flag value (null) for this case. But you
        // might go with an exception (you'd know best), either way.
        rv = null;
    }
    else
    {
        // Do the DB work (below)
        rv = this.buildBeansFor(sql.toString(), paramValues);
    }

    // Done
    return rv;
}

private AppropriateResultBean[] buildBeansFor(
    String sql,
    List<String> paramValues
)
throws SQLException
{
    PreparedStatement       ps      = null;
    Connection              con     = null;
    int                     index;
    AppropriateResultBean[] rv;

    assert sql != null && sql.length() > 0);
    assert paramValues != null && paramValues.size() > 0;

    try
    {
        // Get a connection
        con = /* ...however you get connections, whether it's JNDI or some conn pool or ... */;

        // Prepare the statement
        ps = con.prepareStatement(sql);

        // Fill in the values
        index = 0;
        for (String value : paramValues)
        {
            ps.setString(++index, value);
        }

        // Execute the query
        rs = ps.executeQuery();

        /* ...loop through results, creating AppropriateResultBean instances
         * and filling in your array/list/whatever...
         */
        rv = /* ...convert the result to what we'll return */;

        // Close the DB resources (you probably have utility code for this)
        rs.close();
        rs = null;
        ps.close();
        ps = null;
        con.close(); // ...assuming pool overrides `close` and expects it to mean "release back to pool", most good pools do
        con = null;

        // Done
        return rv;
    }
    finally
    {
        /* If `rs`, `ps`, or `con` is !null, we're processing an exception.
         * Clean up the DB resources *without* allowing any exception to be
         * thrown, as we don't want to hide the original exception.
         */
    }
}

Обратите внимание, как мы используем информацию, предоставленную пользователем (поля, которые они заполнили), но мы никогда не помещали ничего, что они фактически предоставили напрямую в SQLмы выполнили, мы всегда проходили через PreparedStatement.

1 голос
/ 12 ноября 2010

Лучшее решение - использовать посредник, который выполняет проверку и привязку данных и действует как посредник между JSP и базой данных.

Может быть список имен столбцов, но он конечный и счетный.Позвольте JSP беспокоиться о том, чтобы выбор пользователя был известен среднему уровню;пусть средний уровень связывается и проверяется перед отправкой в ​​базу данных.

0 голосов
/ 22 декабря 2015

Вот полезный метод для этого конкретного случая, когда в вашем WHERE есть несколько пунктов, но вы заранее не знаете, какие из них вам нужно применить.

Будет ли ваш пользователь искать по названию?

select id, title, author from book where title = :title

Или по автору?

select id, title, author from book where author = :author

Или и то и другое?

select id, title, author from book where title = :title and author = :author

Достаточно плохо столько 2 поля.Количество комбинаций (и, следовательно, различных PreparedStatements) увеличивается экспоненциально с количеством условий.Правда, скорее всего, у вас достаточно места в вашем пуле PreparedStatement для всех этих комбинаций, и для программного построения предложений в Java вам просто необходима одна ветвь if для каждого условия.Тем не менее, это не так красиво.

Вы можете исправить это аккуратно, просто составив SELECT, который выглядит одинаково, независимо от того, требуется ли каждое отдельное условие.

Мне вряд ли стоит упоминать, что вы используете PreparedStatement, как подсказывают другие ответы, и NamedParameterJdbcTemplate хорошо, если вы используете Spring.

Вот оно:

select id, title, author
from book
where coalesce(:title, title) = title
and coalesce(:author, author) = author

Затем вы предоставляете NULL для каждого неиспользованного условия.coalesce() - это функция, которая возвращает свой первый ненулевой аргумент.Таким образом, если вы передадите NULL для :title, первым предложением будет where coalesce(NULL, title) = title, что оценивается как where title = title, что, будучи всегда истинным, не влияет на результаты.

В зависимости от того, как оптимизатор обрабатываеттакие запросы, вы можете получить удар по производительности.Но, вероятно, не в современной базе данных.

(Хотя, похоже, эта проблема , а не такая же, как проблема предложения IN (?, ?, ?), когда вы не знаете количество значений в списке, поскольку здесь у вас do есть фиксированное количество возможных предложений, и вам просто нужно активировать / деактивировать их по отдельности.)

0 голосов
/ 12 ноября 2010

Я не уверен, существует ли метод quote (), который широко использовался в PDO PHP. Это позволит вам более гибко подходить к построению запросов.

Также одной из возможных идей могло бы стать создание специального класса, который обрабатывал бы критерии фильтра и сохранял бы в стек все заполнители и их значения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...