SQL 注入和 SQL 预编译技术 什么是 SQL 注入 所谓SQL注入(sql inject),就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的
顺晟科技
2021-08-28 09:40:14
204
SQL注入和SQL预编译技术
SQL注入是什么
所谓sql inject,就是通过在Web表单提交的查询字符串中插入sql命令,或者输入域名或页面请求,欺骗服务器执行恶意的SQL命令。具体来说,就是能够将(恶意的)SQL命令注入到后台数据库引擎中,通过使用现有的应用程序来执行。它可以通过以网页形式输入(恶意的)SQL语句,而不是按照设计者的意图执行SQL语句,来获取网站上存在安全漏洞的数据库。
SQL注入的例子
如下图,用户登录时,输入用户名和密码,然后将数据发送到后端进行查询。
sql='从用户中选择用户名和密码,其中用户名='用户名'和密码='密码' ' ';
上面的SQL语句有一个SQL注入的安全漏洞。
如果在用户表中有一条用户名为123456,密码为123456的记录,那么在前台页面提交表单时,用户输入的用户名和密码都是随机输入的,当然登录不可能成功。
但是,如果后台处理的SQL语句是这样写的,前台页面的用户名是随机输入的,用户输入的密码是aaa '或' 1'='1 ',那么登录时处理的SQL语句就相当于这样:
从用户名='123456 '和密码='aaa '或' 1'='1 '的用户中选择用户名和密码;
我们知道,1=1是真的,所以上面的SQL语句可以成功执行,这是一个SQL注入问题。
SQL注入的解决方案
出现上述SQL注入问题的原因是用户输入包含SQL语句,后端在执行SQL语句时直接将用户输入与查询SQL语句拼接在一起。
因此,简单地将用户输入的数据和后端的查询SQL语句连接起来是不可行的。我们需要将用户输入视为一个完整的字符串,忽略内部的SQL语句。当用户输入的密码为“AAA”或“1”=“1”时,用于处理登录的SQL语句实际上应该执行:
从用户名='123456 '和密码='aaa '或' 1'='1 '的用户中选择用户名和密码;
这样就可以避免SQL注入造成的安全漏洞。
SQL预编译技术
事实上,这种解决SQL注入问题的方案的关键点是分别处理用户输入的SQL语句和查询数据,而不是将它们同等地拼接为SQL语句的不同部分。在此基础上,SQL预编译技术应运而生。
通常,我们在数据库中接收并在执行后最终返回的一条SQL可以分为以下三个过程:
词汇和语义分析
优化SQL语句并制定执行计划
执行并返回结果
但是我们可以将用户需要输入的值替换为占位符,可以看作是对SQL语句进行模板化或者参数化,然后对这样的SQL语句进行预编译,然后在实际操作的过程中传递用户输入的数据。
使用这种SQL预编译技术不仅可以防止SQL注入,还可以缓存预编译的SQL语句,进而节省解析和优化SQL语句的过程,可以加快SQL查询的速度。
Gorm和Go端的SQL预编译
在Gorm中,为我们封装了SQL预编译技术,可供我们使用。
db=db。其中(' merchant_id=?',商品)
在执行这样的语句时,我们实际上使用了SQL预编译技术,其中预编译的SQL语句merchant_id=?而SQL查询的数据商品将单独传输到数据库后端进行处理。
db=db。其中(fmt。Sprintf('merchant_id=%s ',ComPanitD))
当你使用这种写法时,就意味着SQL是由用户组装的,而不是使用预编译技术,这可能会带来SQL注入的风险。
Gorm端的SQL预编译
//SQLCommon是gorm所需的最小数据库连接功能。由*sql.DB实现。
类型SQLCommon接口{
Exec(查询字符串,参数.接口{}) (sql。结果,错误)
.
}
执行端的SQL预编译
//src/database/sql/sql.go
函数(db *DB) execDC(ctx上下文。上下文,dc * driverConn,release func(错误),查询字符串,args[]接口{})(结果错误,错误错误){ 0
.
resi,err=ctxdriverxec(CTX,execerCtx,execer,query,nvdargs)
.
如果出错!司机ErrSkip 。{ 0
.
返回驱动结果{dc,resi},零
}
.
si,err=ctxDriverPrepare(ctx,dc.ci,query)
.
ds :=驱动器STM { locker : DC,si: si}
.
返回源于语句(ctx,dc.ci,ds,args.)
}
实际的实现最终还是落到了转到-SQL-驱动程序上,如下面代码所示转到-SQL-驱动程序支持开启预编译和关闭预编译,由mc.cfg。插值参数=假、真决定,可以看出戈尔牧中mc.cfg。插值参数=真,即开启了预编译
func (mc *mysqlConn) Exec(查询字符串,args[]驱动程序。值)(驱动程序。结果,错误){ 0
.
if len(args)!=0 {
如果!mc.cfg。插值{
返回零,司机ErrSkip .
}
已准备好,err :=mc。插值内存(查询,参数)
如果出错!=零
返回零,呃
}
查询=已准备好
}
.
err :=mc.exec(查询)
.
返回零,mc.markBadConn(err)
}
关系型数据库端的结构化查询语言预编译
在关系型数据库中是如何实现预编译的,MySQL在4.1后支持了预编译,其中涉及预编译的指令实例如下
可以通过准备预编译指令,设置传入数据,通过执行执行命令
MySQL PREPARE stmt 1 FROM ' SELECT SQRT(POW(?2) POW(?2)) AS斜边;
查询正常,0行受影响(0.00秒)
准备的声明
MySQL SET @ a=3;
查询正常,0行受影响(0.00秒)
MySQL SET @ b=4;
查询正常,0行受影响(0.00秒)
mysql EXECUTE stmt1 USING @a,@ b;
-
|斜边|
-
| 5 |
-
一行一组(0.00秒)
MySQL DECLARATE PRESENT STM t1
查询正常,0行受影响(0.00秒)
首先我们先简单回顾下客户端使用准备请求过程:
客户端发起准备命令将带"?"参数占位符的结构化查询语言语句发送到数据库,成功后返回stmtID。
具体执行结构化查询语言时,客户端使用之前返回的stmtID,并带上请求参数发起执行命令来执行SQL。
不再需要准备的语句时,关闭stmtID对应的准备语句。
这里展示不使用结构化查询语言预编译和使用结构化查询语言预编译时的关系型数据库的日志。
2020-06-30t z 10查询提交
2020-06-30t z 10查询选择*来自用户,其中merchant_id='123456 '
2020-06-30t查询提交
2020-06-30t准备从merchant_id=?
2020-06-30t从merchant_id='123456 '的用户执行选择*