Поговорим о разногласиях между MyBatis и SQL-инъекциями

Java

Я разобрал кое-какую архитектуру Java и материалы интервью (микросервисы, кластеры, распределенное, промежуточное ПО и т.д.), а друзья, кому нужно, могут обратить внимание на официальный аккаунт [Внутренние дела Программиста], и получить самостоятельно без всяких рутин

более предпочтительно

введение

MyBatisЯвляется структурой уровня сохраняемости, междуJDBCа такжеHibernateмежду. Благодаря MyBatis снижается сложность рукописных операторов SQL, и пользователи могут гибко использовать операторы SQL и поддерживать расширенное сопоставление. Но запуск MyBatis связан не только с вопросами безопасности, многие разработчики думают, что SQL-инъекций при использовании MyBatis не будет, так ли это на самом деле?

Не будет ли SQL-инъекций при использовании MyBatis?Ответ, очевидно, НЕТ. MyBatis — это всего лишь фреймворк уровня сохраняемости, он не решает за вас проблемы безопасности. Конечно, если вы сможете следовать спецификациям и разрабатывать в соответствии с методом, рекомендованным фреймворком, вы, естественно, избежите проблемы SQL-инъекций. В этой статье MyBatis и SQL будут внедрены в эти обиды и недовольства. (Обратите внимание, что MyBatis, упомянутый в этой статье, по умолчанию относится к Mybatis3)

техническое образование

Источник написания этой статьи в основном связан с SQL-инъекцией, найденной в интрасети. Мы нашли запрос из интранетаkeywordВ параметрах есть SQL-инъекция, и кратко представляем предысторию требований.

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

public Criteria addKeywordTo(String keyword) {
  StringBuilder sb = new StringBuilder();
  sb.append("(display_name like '%" + keyword + "%' or ");
  sb.append("org like '" + keyword + "%' or ");
  sb.append("status like '%" + keyword + "%' or ");
  sb.append("id like '" + keyword + "%') ");
  addCriterion(sb.toString());
  return (Criteria) this;
}

Ясно, что потребность должна быть удовлетворенаdiaplay_name,org,statusтак же какid, но разработчик создал его здесь самaddKeywordToметод, с помощью которого создается нечеткое условие запроса, включающее несколько полей.

Существует интересное явление: большинство точек внедрения SQL-инъекций, обнаруженных в интранете, в основном模糊查询Место. Возможно, многие разработчики часто думают, что с нечеткими запросами нет проблем с SQL-инъекциями.

Проанализируйте, почему эта разработка написана таким образом, когда он не понимает, что в этом способе написания есть проблема с SQL-инъекцией, он может подумать, что этот способ написания самый беспроблемный, и тогда он может напрямую написать условия запроса. Приведенный выше код является ядром проблемы, давайте посмотрим на соответствующий файл xml:

 <sql id="Example_Where_Clause" >
    <where >
      <foreach collection="oredCriteria" item="criteria" separator="or" >
        <if test="criteria.valid" >
          <trim prefix="(" suffix=")" prefixOverrides="and" >
            <foreach collection="criteria.criteria" item="criterion" >
              <choose >
                <when test="criterion.noValue" >
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue" >
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue" >
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue" >
                  and ${criterion.condition}
                  <foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
    <select id="selectByExample" resultMap="BaseResultMap" parameterType="com.doctor.mybatisdemo.domain.userExample" >
    select
    <if test="distinct" >
      distinct
    </if>
    <include refid="Base_Column_List" />
    from user
    <if test="_parameter != null" >
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null" >
      order by ${orderByClause}
    </if>
  </select>

Вернемся и посмотрим на вершинуJAVAв кодеaddCriterionметод, этот методMyBatis generatorСгенерировано.

protected void addCriterion(String condition) {
    if (condition == null) {
        throw new RuntimeException("Value for condition cannot be null");
    }
    criteria.add(new Criterion(condition));
}

здесьaddCriterionМетод передает только строковый параметр, по факту здесь используется перегрузка, есть и другиеaddCriterionКоличество параметров, передаваемых в метод, отличается. Используемый здесь метод передает только один параметр, который понимается какcondition, поэтому просто добавьтеconditionизCriterion. Теперь посмотрите на xml вExample_Where_Clause, при обходеcriteriaКогда критерий имеет только условие и не имеет значения, будет введено только условие.criterion.noValue, поэтому формирование всей SQL-инъекции очень ясно.

<when test="criterion.noValue" >
    and ${criterion.condition}
</when>

Правильное написание

Поскольку приведенное выше неверно, каким должно быть правильное написание?

Во-первых, мы можем использовать очень простой и прямой метод, т.addKeywordToметод внутриkewordФильтрация, которая фактически позволяет избежать SQL-инъекций. путем регулярного сопоставленияkeywordВсе небуквенные или числовые символы в нем заменяются пустыми строками, поэтому, естественно, отсутствует возможность SQL-инъекций.

keyword = keyword.replaceAll("[^a-zA-Z0-9\s+]", "");

Но этот способ письма не является научным способом письма.У этого способа письма есть недостаток, то есть если выkeywordЧто мне делать, если мне нужно включить символы, то нужно ли рассматривать больше ситуаций, нужно ли добавлять больше логических суждений или есть возможность обхода? Итак, каким должно быть правильное написание? фактическиmybatis 官网уже даноComple QueriesПример:

 TestTableExample example = new TestTableExample();

  example.or()
    .andField1EqualTo(5)
    .andField2IsNull();

  example.or()
    .andField3NotEqualTo(9)
    .andField4IsNotNull();

  List<Integer> field5Values = new ArrayList<Integer>();
  field5Values.add(8);
  field5Values.add(11);
  field5Values.add(14);
  field5Values.add(22);

  example.or()
    .andField5In(field5Values);

  example.or()
    .andField6Between(3, 7);

Эквивалентный оператор SQL выше:

where (field1 = 5 and field2 is null)
     or (field3 <> 9 and field4 is not null)
     or (field5 in (8, 11, 14, 22))
     or (field6 between 3 and 7)

Теперь давайте начнем сaddKeywordToметод преобразования:

public void addKeywordTo(String keyword, UserExample userExample) {
  userExample.or().andDisplayNameLike("%" + keyword + "%");
  userExample.or().andOrgLike(keyword + "%");
  userExample.or().andStatusLike("%" + keyword + "%");
  userExample.or().andIdLike(keyword + "%");
}

Этот способ письма является относительно стандартным способом письма.or()метод создаст новыйCriteriaобъект, добавленный вoredCriteria, и вернуть этоCriteriaобъект, который можно связать, добавивCriterion. добавлено вот такCriteriaсостоит в том, чтобы содержатьconditionтак же какvalueДа, при выполнении условного запроса вы будете вводитьcriterion.singleValue, то параметр ключевого слова будет передан толькоvalueв, покаvalueчерез#{}входящий.

<when test="criterion.singleValue" >
  and ${criterion.condition} #{criterion.value}
</when>

Подводя итог, причина этой SQL инъекции в том, что разработка была написана не в соответствии со спецификацией, и я сделал колесо и написал метод для выполнения нечеткого запроса, но я не знал, что это приносит уязвимость SQL инъекции. фактически,Mybatis generatorДля каждого поля были сгенерированы расширенные методы, при условии их разумного использования проблем с внедрением SQL можно избежать.

在这里插入图片描述

Может ли использование #{} избежать внедрения SQL?

Если вы вдруг увидите этот вопрос, вы можете почувствовать себя колеблющимся? использовать#{}Можно ли полностью исключить внедрение SQL? Но если вы внимательно его проанализируете, то обнаружите, что ответ положительный. Расскажу о конкретных причинах.

Прежде всего, нам нужно выяснить, что находится в MyBatis.#{}как заявлено. когда параметр передается#{}объявлены, параметры будут переданы черезPreparedStatementВыполнить, то есть выполнить предварительно скомпилированным образом. Вы должны быть знакомы с предварительной компиляцией, потому что вJDBCУже есть предварительно скомпилированные интерфейсы в .

Это также соответствует тому, о чем мы упоминали в начале статьи: Mybatis — это не ядро, которое может решить SQL-инъекцию, а прекомпиляция. Прекомпиляция может не только избежать операторов SQL, чтобы избежать внедрения SQL, но и повысить эффективность выполнения. Нижний уровень Mybatis реализован через JDBC. Взяв в качестве примера MyBatis 3.3.1, SqlRunner в jdbc предназначен для реализации определенных операторов SQL.

在这里插入图片描述

Возьмем для примера способ обновления, видно что он через JAVAPreparedStatementДля достижения предварительной компиляции операторов sql.

public int update(String sql, Object... args) throws SQLException {
    PreparedStatement ps = this.connection.prepareStatement(sql);

    int var4;
    try {
        this.setParameters(ps, args);
        var4 = ps.executeUpdate();
    } finally {
        try {
            ps.close();
        } catch (SQLException var11) {
            ;
        }

    }

    return var4;
}

Стоит отметить, что здесьPreparedStatementСтрого не эквивалентно предварительной компиляции. На самом деле предварительная компиляция делится на предварительную компиляцию на стороне клиента и прекомпиляцию на стороне сервера.MySql на стороне сервера после 4.1 уже поддерживает функцию предварительной компиляции.

многие основные持久层框架(MyBatis,Hibernate) На самом деле никакой реальной пользы от прекомпиляции нет.Предварительная компиляция требует, чтобы мы настроили ее в списке параметров.Если мы не включим ее вручную, прекомпиляция по умолчанию драйвера JDBC версии 5.0.5 и выше выключен.

Его необходимо включить через параметры конфигурации:

jdbc:mysql://localhost:3306/mybatis?&useServerPrepStmts=true&cachePrepStmts=true

Выполнение SQL базы данных состоит из нескольких этапов, как показано на рисунке ниже, но наша предварительная компиляция операторов SQL на стороне клиента выполняется перед их отправкой на сервер. Основным соображением на стороне сервера является производительность, которая не рассматривается в этой статье.

Конечно, могут быть некоторые различия в том, как каждая реализация базы данных прекомпилируется. Но для предотвращения SQL-инъекций в MyBatis просто используйте#{}Это нормально, потому что это реализует параметризацию операторов SQL и позволяет избежать прямого введения вредоносных операторов SQL и их выполнения.

在这里插入图片描述

Использование генератора MyBatis

Для использованияMyBatis,MyBatis generatorОпределенно важный инструмент для использования. MyBatis — это инструмент генерации кода для MyBatis и iBATIS, поддерживающий все версии MyBatis и iBATIS 2.2.0 и выше.

Потому что в реальной бизнес-разработке обязательно будет задействовано много таблиц, и написать соответствующие документы разработчикам самостоятельно невозможно. С помощью генератора MyBatis вы можете сгенерировать соответствующийPOJO 文件,SQL Map XMLфайл и дополнительный клиентский код JAVA.

Обычный способ использования генератора MyBatis — напрямую использовать Maven.mybatis-generator-maven-pluginПлагины, пока файлы конфигурации и информация, связанная с базой данных, подготовлены, соответствующий код может быть сгенерирован с помощью этого плагина.

<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="MysqlTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressAllComments" value="false" />
            <property name="suppressDate" value="false" />
        </commentGenerator>

        <!-- 数据库链接URL、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybaits_test"
                        userId="xxx"
                        password="xxx">
        </jdbcConnection>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="true" />
        </javaTypeResolver>

        <javaModelGenerator targetPackage="com.doctor.mybatisdemo.domain" targetProject="src/main/java/">
            <property name="constructorBased" value="false" />
            <property name="enableSubPackages" value="false" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <sqlMapGenerator targetPackage="myBatisGeneratorDemoConfig" targetProject="src/main/resources">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <javaClientGenerator type="XMLMAPPER" targetPackage="com.doctor.mybatisdemo.dao" targetProject="src/main/java/">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

<!--         要生成那些表(更改tableName和domainObjectName就可以) -->
        <table tableName="user" domainObjectName="user"/>
    </context>
</generatorConfiguration>

在这里插入图片描述

Здесь я хочу подчеркнуть настройку ключевого параметра, а именноtargetRuntimeпараметр. Для этого параметра есть два элемента конфигурации, а именноMyBatis3а такжеMyBatis3Simple, MyBatis3 является элементом конфигурации по умолчанию.MyBatis3Simpleбудет генерировать только базовый CRUD, аMyBatis3Будут сгенерированы условные добавления, удаления и изменения, и все условия инкапсулированы в XXXexample.

При использовании MyBatis3,enableSelectByExample,enableDeleteByExample,enableCountByExampleтак же какenableUpdateByExampleКогда эти свойства имеют значение true, генерируется соответствующий динамический оператор. Вот почему генерируется наш Example_Where_Clause выше.

Если используется элемент конфигурации MyBatis3Simple, сгенерированный XML-файл карты SQL будет очень простым, включая только некоторые основные методы, и не будет генерировать вышеуказанные динамические методы. Можно сказать, что при использовании MyBatis3Simple дополнительной модификации нет, т.к. все переменные в нем передаются через#{}Введено, невозможно иметь проблему инъекции SQL.

Однако в реальном бизнесе часто возникают сложные условия запросов, а исходные файлы конфигурации обычно используются для разработки, поэтому использование MyBatis3 или MyBatis3Simple по-прежнему требует определенных проблем и особых соображений. Однако, если вы используете конфигурацию по умолчанию, вам нужно быть осторожным: помните, что параметры, переданные извне, скорее всего, будут небезопасными и не могут быть напрямую введены для обработки. Имея это в виду, вы можете в основном избежать проблемы SQL-инъекций.

Суммировать

В этой статье рассматривается использование MyBatis, вызванное уязвимостью SQL-инъекции в интрасети.#{}как это работает иMybatis generatorДалее было рассмотрено использование нескольких аспектов.

Можно резюмировать следующие пункты:

  • Не используйте сплайсинг без использования сплайсинга, которого также следует избегатьSQL 注入основные принципы
  • В использовании${}При передаче переменных обязательно обратите внимание на введение и фильтрацию переменных и избегайте передачи внешних переменных напрямую через ${}
  • не себя造轮子, особенно с точки зрения безопасности, на самом деле фреймворк уже обеспечивает стандартный подход в этом вопросе. Если он разработан в соответствии со спецификацией, он не вызовет проблем с внедрением SQL.
  • Обратите внимание, что в MyBatistargetRuntimeконфигурации, если вам не нужен сложный условный запрос, рекомендуется использовать его напрямуюMyBatis3Simple. Это может лучше напрямую устранить риск, потому что, как только есть точка риска, существует вероятность проблемы.

Автор: madneal@Ping Группа безопасности банковских приложений,Посмотреть исходный текст


Сегодня так много нужно сказать, если эта статья была вам полезна, я надеюсь получить от вас лайк 👍

Ваше одобрение является движущей силой для моего письма!


Я разобрал кое-какую архитектуру Java и материалы интервью (микросервисы, кластеры, распределенное, промежуточное ПО и т.д.), а друзья, кому нужно, могут обратить внимание на официальный аккаунт [Внутренние дела Программиста], и получить самостоятельно без всяких рутин