Как я могу автоматически генерировать операторы SQL, не вызывая предупреждение SQL-инъекций Findbugs?
Например, в моем коде есть несколько мест, где количество вопросительных знаков в моем исходном запросе изменяется в зависимости от некоторой особенности данных. Я не размещаю пользовательские данные в SQL, но я корректирую количество вопросительных знаков. Единственный способ сделать это - конкатенация строк / форматирование / etc, которые вызывают это предупреждение от Findbugs:
Подготовленный оператор генерируется из непостоянной строки
Есть ли способ (без запаха кода) автоматически генерировать SQL, не заставляя Findbugs думать, что это уязвимость?
Пример кода для того, что у меня сейчас:
final static String BASE_SQL = "SELECT * FROM customers WHERE id IN (questionMarkPlaceholder)";
[...]
final String questionMarks = "?" + StringUtils.repeat(", ?", customers.length - 1);
final String sql = BASE_SQL.replaceFirst("questionMarkPlaceholder", questionMarks);
statement = conn.prepareStatement(sql);
int counter = 1;
for (Customer customer : customers) {
statement.setString(counter, customer.getId());
counter++;
}
resultSet = statement.executeQuery();
2 ответа
Есть некоторые постоянные API, которые не требуют ручной конкатенации строк для динамического числа параметров. Hibernate Criteria и Querydsl имеют DSL, которые позволяют избежать этой проблемы.
В вашем конкретном случае запрос не является уязвимым, поскольку значения не поступают из пользовательского ввода. Предупреждение FindBugs можно считать ложноположительным. Если вы определите, что значения могут управляться удаленным пользователем, вы можете использовать служебный класс ESAPI Encoder.encodeForSQL.
Лучший способ, который я видел, это использовать несколько предопределенных запросов (это может или не может работать в вашей ситуации). Есть два преимущества использования заполнителей параметров с подготовленными утверждениями. Одним из них является защита от внедрения SQL, но вы также получаете выигрыш в производительности от повторного использования скомпилированного запроса. Подход определяет несколько размеров партий:
private static final String QUERY_SIZE_SINGLE = "SELECT * FROM customers WHERE id=?";
private static final String QUERY_SIZE_SMALL = "SELECT * FROM customers WHERE id IN (?,?,?)";
private static final String QUERY_SIZE_MEDIUM = "SELECT * FROM customers WHERE id IN (?,?,?,?,?,?)";
private static final String QUERY_SIZE_LARGE = "SELECT * FROM customers WHERE id IN (?,?,?,?,?,?,?,?,?,?,?,?)"
private static final int SINGLE = 1;
private static final int SMALL = 3;
private static final int MEDIUM = 6;
private static final int LARGE = 12;
//in your query code...
int remainingCustomers = customers.size();
while (remainingCustomers > 0){
PreparedStatement ps;
if (remainingCustomers > LARGE){
ps = con.prepareStatement(QUERY_SIZE_LARGE);
//loop through the first LARGE parameters and set them
remainingCustomers -= LARGE;
}else if (remainingCustomers > MEDIUM){
///so on
...
}//end if
//execute the statement
//add results to a temporary list holding the results
}//end while
По сути, идея заключается в том, что вы группируете запросы в зависимости от количества клиентов. Размеры запросов, которые я выбрал, абсолютно произвольны. В идеале вы должны выбирать эти числа в зависимости от того, какие значения являются общими для вашего приложения, чтобы минимизировать количество обращений к базе данных. Этот подход позволяет вам воспользоваться преимуществами подготовленных операторов при минимизации циклических обращений к базе данных (т. Е. Медленных операций ввода-вывода).
Примечание. Хотелось бы взять кредит на этот подход, но я обнаружил его при поиске ответа на похожую проблему и больше не помню, где я его нашел...