Mybatis-plus SQL インジェクションと SQL インジェクションの防止
1. SQL インジェクションとは何ですか?
SQL インジェクションは、データ駆動型アプリケーションを攻撃するために使用されるコード インジェクション手法です。実行された SQL ステートメントに悪意のある SQL ステートメントが挿入され、クエリ結果を変更します (例: OR 1=1 or;drop table sys_user; など)。
2. mybatis は SQL インジェクションをどのように防ぐか
mybatis で作成する SQL ステートメントは、XML 内でのみ完了できます。SQL を作成するときは、#{} と ${} の 2 つの式を使用します。#{} と ${} の違いは何ですか? 以下では、2 つの SQL ステートメントの例を使用して説明します。
<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER
<where>
USER_ID= #{userName,jdbcType=VARCHAR}
</where>
</select>
<select id="selectUserByUserName" parameterType="java.lang.String" resultType="com.domain.UserInfo">
SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER
<where>
USER_NAME= ${userName,jdbcType=VARCHAR}
</where>
</select>
- SQL ステートメントで使用される最初の
#{}
方法では、#{}
受信データが文字列の場合、" "
値を二重引用符で囲みます。
例:例えばuserNameで渡された値を取得し9;DROP TABLE SYS_USER;
た場合、テーブル削除のコマンドを渡しても値照合の完了文字列とみなされ実行されない#{}
結果が得られます。。USER_NAME="9;DROP TABLE SYS_USER;"
9;DROP TABLE SYS_USER;
${}
値を取得する2番目のSQLメソッドはUSER_NAME=9;DROP TABLE SYS_USER;
、${}
SQL文の後ろに値を直接繋いでSQLにするため、SQL文の後ろに直接値を繋ぎ合わせるため、${}
SQLインジェクションのリスクがあります。使用する際は手作業による処理に注意してください。
1. #{} と ${} の違い
#{}
: これは JDBC プリコンパイル済みステートメントとして解析され、 a#{}
はパラメータ プレースホルダ? として解析され、#{}
受信データを 1 つとして扱い字符串
、自動的に受信するデータに 1 を追加します双引号
。たとえばWHERE USER_NAME =#{username}
、 、渡された値が の場合9
、SQL に解析された値はWHERE USER_NAME =“9”
、渡された値が の場合12345678
、解析された SQL はWHERE USER_NAME =“12345678”
、${}
純粋な文字列置換の場合のみ、${}
渡された変数は SQL に直接接続されます。例:WHERE USER_NAME = ${username}
に渡された値が の場合9
、SQL に解析された値 渡された値が のWHERE USER_NAME =9;
場合;DROP TABLE SYS_USER;
、解析された SQL は次のとおりです。SELECT USER_ID, USER_NAME, PWD, USER_PHONE FROM SYS_USER WHERE USER_NAME="9;DROP TABLE SYS_USER;
したがって、ORDER BY または GROUP BY と同様に、${} を使用できます。#{}
メソッドの最下層はプリコンパイルされたメソッド PreparedStatement を採用しており、SQL インジェクションはコンパイル時に発生するため、SQL インジェクションを大幅に防ぐことができますが、メソッドの最下層は${}
Statement のみであり、SQL インジェクションを防ぐことはできません。
$
このメソッドは通常、テーブル名を渡すなど、データベース オブジェクトを渡すために使用されます。
2. PreparedStatement と Statement の違い
① PreparedStatement が SQL コマンドを実行すると、そのコマンドはデータベースによって解析およびコンパイルされてからコマンド バッファに置かれ、その後、同じ SQL コマンドが実行されるたびにバッファ内でコンパイル コマンドが発行されると、コマンド バッファに格納されます。再利用できるように、再度解析およびコンパイルされることはありません。コンパイル中に、PreparedStatement は各 #{} マークのシンボルをパラメータ パラメータ プレースホルダ? に解析し、渡された変数がパラメータとして使用され、SQL インジェクション攻撃を防ぐために SQL ステートメントは変更されません。''
②ステートメントは SQL コマンドをデータベースに直接送信して操作します。SQL インジェクションは実行時に発生するため、SQL インジェクション攻撃を傍受することはできません。Statement は SQL コマンドを毎回解析してコンパイルするため、大規模なデータベースのオーバーヘッドが増加するため、PreparedStatement ほど効率的ではありません。
3. プリコンパイルとは何ですか
プリコンパイルとは、コード テキストの置換作業を行うことです。これは、編集プロセス全体の最初の作業です。#include に含まれるファイルコードのコピー、#define マクロ定義の置き換え、条件付きコンパイルなど、# で始まる処理命令はコンパイルの準備作業です。主に # で始まるプリコンパイル済み命令について扱います。プリコンパイル済み命令は、プログラムが正式にコンパイルされる前にコンパイラによって実行される操作を示し、プログラム内のどこにでも配置できます。また、SQL インジェクションは実行時にのみ発生します。
4. mybaits-plus SQL インジェクションの理由
Mybatisplus の PaginationInterceptor は、主にデータベースの物理ページングを処理し、メモリ ページングを回避するために使用されます。
PaginationInterceptorのソースコードを解析すると見つかる
Orderby シナリオでの SQL インジェクション
前述したように、Orderby 動的クエリはプリコンパイルできないため、Orderby はページネーションで使用されます。そのため、セキュリティ チェックに合格しない場合はインジェクションのリスクがあります。PaginationInnerInterceptorは主にcom.baomidou.mybatisplus.extension.plugins.pagination.pageオブジェクトにプロパティを設定することでorderbyを実装します主に以下の関数の呼び出しですSQLスプライシングを直接利用するためカラム名を確保する必要があります並べ替えのために次を調べます:
page.setAscs();
page.setDescs();
ソースコード:
ページネーションは文字列の結合によって行われるため、SQL インジェクションのリスクがあることがわかります。
public static String concatOrderBy(String originalSql, IPage<?> page, boolean orderBy) {
if (!orderBy || !ArrayUtils.isNotEmpty(page.ascs()) && !ArrayUtils.isNotEmpty(page.descs())) {
return originalSql;
} else {
StringBuilder buildSql = new StringBuilder(originalSql);
String ascStr = concatOrderBuilder(page.ascs(), " ASC");
String descStr = concatOrderBuilder(page.descs(), " DESC");
if (StringUtils.isNotEmpty(ascStr) && StringUtils.isNotEmpty(descStr)) {
ascStr = ascStr + ", ";
}
if (StringUtils.isNotEmpty(ascStr) || StringUtils.isNotEmpty(descStr)) {
buildSql.append(" ORDER BY ").append(ascStr).append(descStr);
}
return buildSql.toString();
}
}
3. Mybatis-plus は SQL インジェクションをどのように防ぐか
ページング コントローラーを使用する場合、受信ページング プラグインの ascs と descs をチェックして、不正な文字があるかどうかを確認します。不正な文字がある場合は、パラメーターに不正な列名が含まれていることを示すプロンプトが表示されます: create_time aaaa 例
:
チェックフィールドの用途:
package com.koal.util;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.koal.exception.BizException;
import com.koal.web.ErrorCode;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Pattern;
/**
* @author sunrj
*/
public class RegexUtils {
/**
* 对Page校验防止sql注入
*
* @param
*/
public static void verifyPageFileld(Page page) {
//asc校验
Optional.ofNullable(page.ascs()).ifPresent(ascs -> {
Arrays.asList(ascs).forEach(asc -> {
boolean rightfulString = RegexUtils.isRightfulString(asc);
if (!rightfulString) {
throw new BizException(ErrorCode.COMMON_VERIFY_ERROR.getCode(), "ascs参数中含有非法的列名:" + asc);
}
});
});
//desc校验
Optional.ofNullable(page.descs()).ifPresent(descs -> {
Arrays.asList(descs).forEach(desc -> {
boolean rightfulString = RegexUtils.isRightfulString(desc);
if (!rightfulString) {
throw new BizException("10011", "desc参数中含有非法的列名:" + desc);
}
});
});
}
/**
* 判断是否为合法字符(a-zA-Z0-9-_)
*
* @param text
* @return
*/
public static boolean isRightfulString(String text) {
return match(text, "^[A-Za-z0-9_-]+$");
}
/**
* 正则表达式匹配
*
* @param text 待匹配的文本
* @param reg 正则表达式
* @return
*/
private static boolean match(String text, String reg) {
if (StringUtils.isBlank(text) || StringUtils.isBlank(reg)) {
return false;
}
return Pattern.compile(reg).matcher(text).matches();
}
}
コントローラーはページ内のフィールドをチェックします。
@GetMapping
@ApiOperation(value = "查询用户列表", notes = "查询用户列表")
public ServerResponse<IPage<Account>> queryAccount(Page<Account> page) {
//校验page中的字段,防止sql注入
RegexUtils.verifyPageFileld(page);
return ServerResponse.successMethod(accountService.query(page));
}
結果:
POST http://127.0.0.1:8080/account?current=1&size=10&ascs=create_time;DROP TABLE tb_account;
结果:
{
"code": "10011",
"msg": "ascs参数中含有非法的列名:create_time;DROP TABLE ag_account_info;",
"timestamp": 1653547051505
}