sql 整改措施 注入_记一次Sql注入 解决方案

本文通过一个具体的Sql注入实例,探讨了其危害,并详细解释了如何通过预编译SQL语句和使用PreparedStatement来防止Sql注入。重点分析了PreparedStatement的工作原理,包括参数化查询和字符串格式化,以确保数据安全。
摘要由CSDN通过智能技术生成

老大反馈代码里面存在sql注入,这个漏洞会导致系统遭受攻击,定位到对应的代码,如下图所示

image

like 进行了一个字符串拼接,正常的情况下,前端传一个 cxk 过来,那么执行的sql就是

select * from test where name like '%cxk%';

好像没有什么问题,但是,如果被攻击了传了个 cxk%'; DELETE FROM test WHERE name like '%cxk

那么 这条sql 将会拼接成

select * from test where name like '%cxk%'; DELETE FROM test WHERE name like '%cxk%';

执行不会报错,结果是 name like cxk 的数据全部删除,这个还是比较温柔的sql注入,如果是 drop table ,那不是要原地爆炸?

既然Sql 注入危害这么大,那么怎么防范呢?

采用sql语句预编译和绑定变量,是最简单,也是最有效的方案.

那什么是预编译呢?

like this

select * from test where name like ? args: WrappedArray(JdbcValue(%cxk%))

先是 select * from test where name like ? 这样预编译好,然后传进来的数据以参数化的形式执行sql,就可以防止sql 注入。

为什么这样可以防止sql 注入呢?

分别给两种场景测试

1.likeString

代码如下

def likeString(name:String) ={

dataSource.rows[Test](sql" select * from test where name like "+s"'%${name}%'")

}

输入 cxk%'; DELETE FROM test WHERE name like '%cxk

打断点可以看到

image

将statement 中的值复制出来 到navicat 中,可以看到

image

那么jdbc 执行就会 直接执行,然后把cxk 删了。

2.like

代码如下

def like(name:String) ={

// cxu

// %cku%

dataSource.rows[Test](sql" select * from test where name like ${name.likeSql}")

}

implicit class StringBuildSqlLikeImplicit(s:String){

def likeSql: String ={

s"%${s}%"

}

def likeLeftSql: String = {

s"%${s}"

}

def likeRightSql: String ={

s"${s}%"

}

}

输入 cxk%'; DELETE FROM test WHERE name like '%cxk

打断点可以看到

image

image

可以看到 statement = select * from test where name like ** NOT SPECIFIED **

PreparedStatement.setString(1,'cxk%'; DELETE FROM test WHERE name like '%cxk')

之后,

statement =

select * from test where name like '%cxk\'; DELETE FROM test WHERE name like \'%cxk%'

将statement 中的值复制出来 到navicat 中,可以看到

image

string 内部的; % 被格式化, 这样执行的话,内部的sql 就以字符串的形式存在,这样避免了Sql 注入。

那 PreparedStatement 是这么做到的呢, 主要的原因是 PreparedStatement.setString(int parameterIndex, String x)

这里面会对x 进行一个格式化

判断是否需要格式化

image

贴上源码

private boolean isEscapeNeededForString(String x, int stringLength) {

boolean needsHexEscape = false;

for (int i = 0; i < stringLength; ++i) {

char c = x.charAt(i);

switch (c) {

case 0: /* Must be escaped for 'mysql' */

needsHexEscape = true;

break;

case '\n': /* Must be escaped for logs */

needsHexEscape = true;

break;

case '\r':

needsHexEscape = true;

break;

case '\\':

needsHexEscape = true;

break;

case '\'':

needsHexEscape = true;

break;

case '"': /* Better safe than sorry */

needsHexEscape = true;

break;

case '\032': /* This gives problems on Win32 */

needsHexEscape = true;

break;

}

if (needsHexEscape) {

break; // no need to scan more

}

}

return needsHexEscape;

}

可以看到 它会对传进来的参数判断,如果含有一些非法字符会判断传过来的值需要格式化, 那它是怎么格式化的呢? 我们看下源码

// setString 里的部分源码

if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {

needsQuoted = false; // saves an allocation later

StringBuilder buf = new StringBuilder((int) (x.length() * 1.1));

buf.append('\'');

//

// Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure...

//

for (int i = 0; i < stringLength; ++i) {

char c = x.charAt(i);

switch (c) {

case 0: /* Must be escaped for 'mysql' */

buf.append('\\');

buf.append('0');

break;

case '\n': /* Must be escaped for logs */

buf.append('\\');

buf.append('n');

break;

case '\r':

buf.append('\\');

buf.append('r');

break;

case '\\':

buf.append('\\');

buf.append('\\');

break;

case '\'':

buf.append('\\');

buf.append('\'');

break;

case '"': /* Better safe than sorry */

if (this.usingAnsiMode) {

buf.append('\\');

}

buf.append('"');

break;

case '\032': /* This gives problems on Win32 */

buf.append('\\');

buf.append('Z');

break;

case '\u00a5':

case '\u20a9':

// escape characters interpreted as backslash by mysql

if (this.charsetEncoder != null) {

CharBuffer cbuf = CharBuffer.allocate(1);

ByteBuffer bbuf = ByteBuffer.allocate(1);

cbuf.put(c);

cbuf.position(0);

this.charsetEncoder.encode(cbuf, bbuf, true);

if (bbuf.get(0) == '\\') {

buf.append('\\');

}

}

// fall through

default:

buf.append(c);

}

}

buf.append('\'');

parameterAsString = buf.toString();

}

从这里可以看出,会讲' 加一个'' 那么原传入的Sring 就会被格式化成上文所说。

打断点我们可以看到

image

这一块是jdbc PreparedStatement 对SQL注入的防范。

从网上我还看到了一些这样的

那么,什么是所谓的“precompiled SQL statement”呢?

回答这个问题之前需要先了解下一个SQL文在DB中执行的具体步骤:

1.Convert given SQL query into DB format -- 将SQL语句转化为DB形式(语法树结构)

2.Check for syntax -- 检查语法

3.Check for semantics -- 检查语义

4.Prepare execution plan -- 准备执行计划(也是优化的过程,这个步骤比较重要,关系到你SQL文的效率,准备在后续文章介绍)

5.Set the run-time values into the query -- 设置运行时的参数

6.Run the query and fetch the output -- 执行查询并取得结果

打断点调试的时候看到,PreparedStatement 最终还是会转化成statement 然后执行,

jdbc 这么做应该是做应该是为了 mysql 的缓存机制,我们知道,mysql 进行select 查询的时候,会有一个缓存机制,如果执行语句一致的话,就会拿mysql 的缓存直接获取数据,如果以参数形式传到mysql 的话,这样就没有办法命中缓存了(个人看法,错误请佐证)。

综上所述,SQL注入,用PreparedStatement 防治是可以防治的,代码中也尽量用 PreparedStatement 这种形式。

题外话,那么这个是jdbc 的做法,那其他的框架是怎么解决SQL 注入的呢?

PHP 防治Sql注入

1.通过函数去对一些特殊字符进行处理 例如 addslashes(

str)

2.预编译的做法

Node 防治SQL 注入

1.使用escape()对传入参数进行编码,

2.使用connection.query()的查询参数占位符:(预编译)

3.使用escapeId()编码SQL查询标识符:

4.使用mysql.format()转义参数:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值