0


Java中的SQL注入简易分析

JDBC

什么是JDBC?JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。

使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐

│  ┌───────────────┐  │
   │   Java App    │
│  └───────────────┘  │
           │
│          ▼          │
   ┌───────────────┐
│  │JDBC Interface │<─┼─── JDK
   └───────────────┘
│          │          │
           ▼
│  ┌───────────────┐  │
   │  JDBC Driver  │<───── Vendor
│  └───────────────┘  │
           │
└ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ┘
           ▼
   ┌───────────────┐
   │   Database    │
   └───────────────┘

通过SQLInjection.java代码对于JDBC中对于数据的调用使用预编译和不使用预编译两种情况进行分析

不使用占位符拼接

Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
Statement stmt = conn.createStatement();
String sql = "select name from students where id =" + value;
ResultSet rs = stmt.executeQuery(sql);

不使用占位符拼接的关键代码如上,先通过Connection提供的createStatement()方法创建一个stmt对象,用于执行一个查询.然后执行stmt对象提供的executeQuery(sql)传入我们构造的SQL语句并获得返回的结果集,使用ResultSet来引用结果集.

而在执行的关键代码中,先是把sql语句传入

Statementlmpl.class

当中的

ResultSet executeQuery()

方法中,在

locallyScopedConn.execSQL()

中执行SQL并将执行结果放入到

this.result


请添加图片描述

请添加图片描述

通过追踪

execSQL()

方法可以追溯到

Connectionlmpl.class

文件,我们可以看到在将sql语句接收到方法中后,将语句交由MysqlIO来执行.
请添加图片描述

请添加图片描述

查看

sqlQueryDirect()

方法,通过拼装发送包信息,最后通过

Buffer resultPacket = this.sendCommand(3, (String)null, queryPacket, false, (String)null, 0);

中的

sendCommand()

方法将其发送出去
请添加图片描述

请添加图片描述

纵观在

Statementlmpl.class

当中的

ResultSet executeQuery()

方法中只是将我们的sql语句进行一步步的传递,大部分只进行了功能上的校验,在最后发送到数据库进行执行,通过names列表可以看到数据库中所有的名字都被读取了出来.
请添加图片描述

使用占位符(PreparedStatement)

String sql = "select name from students where id =?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
ResultSet resultSet = preparedStatement.executeQuery();

使用

PreparedStatement

预编译方法,对于要传递的id的值先使用?进行占位,并且把数据连同sql本身传给数据库,以此保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同.

我们传递数据

1 or 1=1

进行测试,以此来学习在

PrepareStatement

对于我们传入的数据的处理过程.

PrepareStatement的开启与关闭情况

在对

prepareStatement()

方法进行调试的时候,我们需要了解一个关于预编译的知识点.

预编译功能跟MySQL版本及 MySQL Connector/J(JDBC驱动)版本都有关,首先MySQL服务端是在4.1版本之后才开始支持预编译的,之后的版本都默认支持预编译,并且预编译还与 MySQL Connector/J(JDBC驱动)的版本有关, Connector/J 5.0.5之前的版本默认支持预编译, Connector/J 5.0.5之后的版本默认不支持预编译, 所以我们用的Connector/J 5.0.5驱动以后版本的话默认都是没有打开预编译的 (如果需要打开预编译,需要配置 useServerPrepStmts 参数)

因为我的测试环境为5.1.47,所以目前版本的预编译默认是关闭的
请添加图片描述

所以我们运行代码,在初始状态下的mysql查询日志是这样的
请添加图片描述

而在数据库链接中加入

useServerPrepStmts=true

后mysql的查询日志为

请添加图片描述

我们可以看到查询比之前多了一条Prepare数据,表示着预编译开启成功

我们回到代码调试中,当代码执行到

PreparedStatement preparedStatement = conn.prepareStatement(sql);

时,我们通过断点调试可以进入到

ConnectionImpl,java

prepareStatement()

方法当中.
请添加图片描述

当我们没有设置

useServerPrepStmts=true

时,在

prepareStatement()

方法当中

useServerPreparedStmts

属性为false,直接跳过当前代码块进入到最后的else代码块
请添加图片描述

请添加图片描述

最后并不会向数据库提交SQL预编译请求

而我们设置

useServerPrepStmts=true

后,再次调试代码,会发现

useServerPreparedStmts

属性为true,最后向数据库提交SQL预编译请求
请添加图片描述

请添加图片描述

我们继续调试代码到

ResultSet resultSet = preparedStatement.executeQuery()

,进入

setString()

方法

在没有开启预编译的情况下,会进入

PreparedStatement.class

,在其中的

isEscapeNeededForString()

方法中对于用户输入的数据中的非法字符进行转义,最后交由数据库端进行运行.

请添加图片描述

而开启了预编译的情况下,会进入

ServerPreparedStatement.class

,最后数据交由mysql端进行转义处理

在预编译情况下对于order by,like的危害

order by

ORDER BY关键字用于按升序或降序对结果集进行排序。

order by

后一般接字段名,而字段名是不能带引号的,比如

order by id

,如果使用预编译,id在预编译的过程中会被setString()方法自动加引号,而如果带上引号之后就成了

order by 'id'

,现在id就是一个字符串而不是一个字段名了,会产生语法错误.

请添加图片描述

请添加图片描述

可以看到拼接后的sql语句中

order by

的参数就是字符串,我们在数据库中运行查看
请添加图片描述

请添加图片描述

可以看出经过引号包裹的语句没有起作用

所以在开发过程中,不能参数化的位置,不管怎么拼接,最终都是和使用"+"号拼接字符串的功效一样:拼成了sql语句但没有防sql注入的效果.

我们可以通过构造if语句来对

order by

以后的语句进行构造进行SQL注入
请添加图片描述

所以需要对order by参数进行特殊的过滤

like

在使用like时,通过平常的sql语句进行构造

select * from students where name like '%?%'

会报错,所以有时我们看到的代码中会出现拼接形态的like语句,此时就很有可能出现sql注入漏洞
请添加图片描述

正确的like预编译构造方法如下图所示,需要在

setString()

方法中将%构造出来
请添加图片描述

Mybatis

Mybatis解析执行过程

引用一下先知社区R17a大佬的过程图:
请添加图片描述

以查询SQL分析,主要步骤如下:
1.SqlSession创建过程:

SqlSessionFactoryBuilder().build(inputStream)

创建一个

SqlSession

,创建的时候会进行配置文件解析生成Configuration属性实例,解析时会将mapper解析成MapperStatement加到Configuration中,MapperStatement是执行SQL的必要准备,SqlSource是MapperStatement的属性,实例化前会先创建动态和非动态SqlSource即

DynamicSqlSource

RawSqlSource

DynamicSqlSource

对应解析

$

以及动态标签如foreach,RawSqlSource创建时解析

#

并将

#{}

换成占位符

?

2.执行准备过程:

DefaultSqlSession.selectOne()

执行sql(如果是从接口

getMapper

方式执行,首先会从

MapperProxy

动态代理获取

DefaultSqlSession

执行方法

selectxxx|update|delete|insert)

,首先从Configuration获取

MapperStatement

,执行

executor.query()

。executor执行的第一步会先通过

MapperStatement.getBoundSql()

获取SQL,此时如果

MapperStatement.SqlSource

是动态即

DynamicSqlSource

,会先解析其中的动态标签比如

${}

会换成具体传入的参数值进行拼接,获取到SQL之后调用

executor.doQuery()

,如果存在预编译首先会调用JDBC处理预编译的SQL,最终通过

PreparedStatementHandler

调用JDBC执行SQL;

3.JDBC执行SQL并返回结果集

调试代码(${}和#{}使用的不同)

在通过mybatis数据操作的过程中,在

XMLScriptBuilder.parseScriptNode()

处会因为

${}

#{}

使用的不同执行不同的方法

请添加图片描述

在进入

parseScriptNode()

后,先通过

parseDynamicTags()

方法中的

TextSqlNode.isDynamic()

判断是否存在

${}

标志来区分动态和非动态SqlSource

请添加图片描述

TextSqlNode.isDynamic()

首先会通过

DynamicCheckerTokenParser()

中的

GenericTokenParser()

创建一个

${}

标识符解析

请添加图片描述

继续下一步调用

GenGenericTokenParser.parse

对我们的SQL语句进行校验

${}分析

请添加图片描述

parse()

中可以看到如果在我们sql语句中发现了

${

那么继续执行,如果没有就直接返回,而在继续执行的最后调用了

builder.append(this.handler.handleToken(expression.toString()))

,在

handler.handleToken

中将

isDynamic

更改为了true
请添加图片描述

请添加图片描述

isDynamic

为true,会实例化一个

DynamicSqlSource

对象,返回

sqlSource

请添加图片描述

#{}分析

从上面关于${}的分析可以知道,如果我们的sql语句构造为#{},那么将在

XMLScriptBuilder.parseScriptNode

方法中使用

RawSqlSource

来构造

sqlSource

在过程中同样经过

GenGenericTokenParser.parse

对我们的SQL语句进行校验,在其中将

#{}

替换成了

?

请添加图片描述

请添加图片描述

like

在mybatis中错误的like使用语句为

select * from user where name like "%${id}%"

通过构造

id = 1%" or 1=1 #

使最后在数据库执行

select * from user where name like "%1%" or 1=1 # %"

请添加图片描述

正确的构造方法应该为

SELECT * FROM user where name like concat('%',#{name}, '%')

order by

至于

oeder by

的话,和JDBC中的分析相似,如果order by后面跟的变量的话,应该进行校验和过滤

参考

https://www.liaoxuefeng.com/wiki/1252599548343744/1321748435828770
https://juejin.cn/post/6844903490058190862
https://xz.aliyun.com/t/10593
https://xz.aliyun.com/t/10686

标签: java web安全

本文转载自: https://blog.csdn.net/Destiny_one/article/details/126156823
版权归原作者 鸣蜩十四 所有, 如有侵权,请联系我们删除。

“Java中的SQL注入简易分析”的评论:

还没有评论