什么是SQL注入
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
这里以一个PHP的SQL语句代码为例
$SQL = “select * from ‘某字段’ where id = $id”;
由于这里的参数id可以控制,且这个id被待进了数据库查询,所以一些意图不轨的人可以通过拼接SQL语句来进行攻击。
产生SQL注入需要的条件
SQL注入的产生需要两个条件
1.我们传递给后端的参数是可以控制的
2.参数内容会被带入到数据库查询
验证是否存在SQL注入
还是以这个代码为例 $SQL = “select * from ‘某字段’ where id = $id”;
在这里我们可以控制的参数是id这个参数,所以我们输入1‘的时候。这时查询语句执行的内容就为
select * from ‘某字段’ where id = 1’
由于后面有一个单引号,这样的语句不符合数据库语法的规范,所以会报错,从而判断出该处是否存在SQL注入。
SQL注入的分类
SQL注入分为很多种,有联合注入、布尔注入、报错注入、时间注入、堆叠注入、二次注入、宽字节注入、cookie注入等等等。当然这些注入所产生的原理都是一样,如上文所讲。在接下来的文章呢,笔者也会对这些注入结合实例进行全部写出来。
SQL注入的防御
方案一
采用预编译技术
INSERT INTO MyGuests (firstname, lastname, email) VALUES(?, ?, ?)
使用预编译的SQL语句,SQL语句的语义不会是不会发生改变的。预编译语句在创建的时候就已经将指定的SQL语句发送给了DBMS,完成了解析,检查,编译等工作,所以攻击者无法改变SQL语句的结构,只是把值赋给?,然后将?这个变量传给SQL语句。当然还有一些通过预编译绕过某些安全防护的操作,大家感兴趣可以去搜索一下。
方案二
严格控制数据类型
在java、c等强类型语言中一般是不存在数字型注入的,因为在接受到用户输入id时,代码一般会做一个int id 的数据类型转换,假如我们输入的是字符串的话,那么这种情况下,程序就会报错。但是在PHP、ASP这些没有强调处理数据类型的语言,一般我们看到的接收id的代码都是如下等代码。
$id = $_GET[‘id’];
$SQL = “select * from ‘某字段’ where id = $id;”;
这样的代码攻击者就可以通过构造id参数运用联合查询等手法进行SQL注入,假如这里我们加入一个检查数字类型函数is_numeric()这样就可以防止数字型注入了。
方案三
对特殊的字符进行转义
数字型注入可以通过检查数据类型防止,但是字符型不可以,那么怎么办呢,最好的办法就是对特殊的字符进行转义了。比如在MySQL中我们可以对” ‘ “进行转义,这样就防止了一些恶意攻击者来闭合语句。当然我们也可以通过一些安全函数来转义特殊字符。如addslashes()等,但是这些函数并非一劳永逸,攻击者还可以通过一些特殊的方式绕过。
首先我们先分析一下DVWA靶场low等级的SQL注入。
这里有一个user id需要我们填写,那我们先填写一个1看看会发生什么。
这里查询到了user id为1号的信息,还记得我们前文说的验证SQL注入的方法吧,在1后面加个单引号,那我们输入1‘在试一试。
可以看到我们输入1’导致SQL语句不能正常解析最终导致程序报错
那我们初步判断这里是存在注入点的,那么我们可以去查看一下源代码来分析分析。
可以看到这里的SQL语句,然后可以看到后文代码没有对我们的id参数进行任何的过滤处理,这里就满足了我们的注入要求,有可控变量id且没有做任何过滤,语句被带入到数据库进行查询。知道了此处存在SQL
注入,那么我们该如何进行利用呢。
以下以MYSQL数据库为例子
首先呢在学习SQL注入之前我们需要知道的几个函数
database():当前网站使用的数据库
version():当前MYSQL使用的版本
user():当前MYSQL的用户
当我们不知道任何条件时,我们就要如下查询
select 要查询的字段名 from 库名.表名
那再根据我们看到的SQL语句,这里我们就可以利用一下联合查询
1' union select 1,database()#
这里有几个需要注意的地方
1.我们的1后面需要单引号进行闭合
2.运用union select联合查询我们后面所要查询的字段个数要和之前一致,这里有两个字段,一个是1,还一个是database()
3.最后一个注意点就是最后我们要用#注释掉后面的内容,因为在MYSQL语句种#是注释的意思
输入后可以看到爆出我们的当前数据库名字
在知道一条数据后我们就可以通过如下语句进行查询
select 要查询的字段名 from 库名.表名 where 已知条件字段名字=已知条件的值
又因为mysql 5.0以上版本自带数据库,information_schema记录有当前mysql下所有数据库名,表名,列名信息。下列是存放各信息的名字
information_schema.schemata:记录数据库信息的表
information_schema.tables:记录表名信息的表
information_schema.columns:记录列名信息的表
schema_name 数据库名
table_name 表名
column_name 列名
table_schema 数据库名
其中的’ . ‘是为下一级的意思,知道了这些我们就可以构造一下SQL语句
上面我们已经知道了数据库名字为dvwa所以我们就可以爆出dvwa里面的表名
1’union select 1,table_name from information_schema.tables where table_schema=”dvwa”#
有看到users表,users里面一般存放的是用户的信息,所以我们再爆一下users表下面的列名信息
1′ union select 1,column_name from information_schema.columns where table_name=’users’#
这里可以看到一些user、password字段估计就是存放用户和用户密码了,那我们再查看一下这些字段里面的内容。
1′ union select 1,concat(user,password) from users#
这里就爆出了账号和密码的MD5值,然后我们可以把MD5值解密就可以查看到用户密码
Boolean注入
一、什么是Boolean注入
Boolean型的注入意思就是页面返回的结果是Boolean型的,通过构造SQL判断语句,查看页面的返回结果是否报错,页面返回是否正常等来判断哪些SQL判断条件时成立的,通过此来获取数据库中的数据。
二、Boolean注入实战
这里我们可以打开我们sqli-labs的第八关,这个靶场的源代码在之前的渗透测试之外网准备一文中发过了,不知道的可以再去看一下。
第八关是一个Boolean注入的环境,首先我么先输入一个?id=1看一看会有什么情况
这里出现了一个You are in……的字段
那么我们在输入一个?id=1’看看会出现什么
很显然,这里我们输入?id=1’ 查询语句是无法正常执行的,按照以往我们经验这里是会报错的,爆出一个语句查询错误的英文句子,但是这里没有。可是仔细观察,我们就会发现区别。错误的语句中是没有You are in…..这个字段的,那么我们就可以通过这个特性来进行我们的注入了。
三、boolean注入思想
在进行boolean我们是这样思考的。这里打个比方,站在你面前一个人,你问他叫什么名字,但是他是个哑巴不能说话,只会点头和摇头,这个时候你该怎么去得到他的名字呢。对,当然是我们去问他,然后他通过点头或摇头来告诉我们的正确与否。假设这个人名字叫张三,我问他,你姓李吗。他给我摇了个头,这个时候我知道了他不姓李,我继续问,直到我们他你姓张吗,他给我点了个头,这个时候我知道了。哦!原来他姓张,后面才字也是一样的手段。当然这样询问是工作量非常大的,在我们的boolean注入里面也是一样,所以我们在进行boolean注入的时候都会写一些自动化的注入工具来帮助我们完成注入。
四、开始boolean注入
再进行boolean注入之前我们还需要知道一些MySQL的一些内置函数
length(str):返回str字符串的长度。
一、什么是报错注入
顾名思义,报错注入就是通过页面爆出的错误信息,构造合适的语句来获取我们想要的数据,本章节讲述的注入,数据库为MySQL。
二、出现报错注入的原因
首先是应用系统未关闭数据库报错函数,对于一些SQL语句的错误,直接回显在了页面上,部分甚至直接泄露数据库名和表名;
其次,必不可少就是后台未对MySQL相应的报错函数进行过滤
三、集中常见的报错注入用到的函数
1.updatexml()
2.floor()
3.extractvalue()
4.exp()
但这里使用最为常见的还是updatexml()函数
四、updatexml()的使用方法
updatexml()的语法为
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
如果找不到相应的xpath路径,updatexml函数就会报出错误
concat()函数。此函数用于连接字符串;由于updatexml()函数xpath路径需要连接特殊字符,被连接的特殊字符需要进行16进制编码
concat_ws()函数。语法concat_ws(0x7e,database(),use()),会把查出来的库名和用户通过~连接起来完成报错
下面我门可以进入到sqli-lab的第五关
输入?id=1看看是什么个样子
出现了一个You are in…….的字样
那我们在后面再加一个单引号闭合看看出现什么
这里爆出了错误语句,这个时候我们就可以利用我们报错注入来进行注入了
我们可以利用如下的payload
?id=-1'union select count(*),count(*), concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
这里就爆出了数据库名
接下来就是表名、字段名等,那么这个payload实现的原理是什么呢。
几个关键函数的说明
rand(0)*2
rand() 可以产生一个在0和1之间的随机数。
可见,每次产生的都不一样。当我们提供一个种子参数 0 后,再次查看:
可以发现,每次产生的值都是一样的。也可以称之为伪随机(产生的数据都是可预知的)。
查看多个数据看一下。( test 是我之前创建的一个拥有9条数据的表)
发现第一条数据与刚才查看的单个数据相符合,其它的数据也完全一样。
为什么要乘以 2 呢?这就要配合 floor 函数来说了。
floor(rand(0)*2)
floor() 返回小于等于该值的最大整数。
之前我们了解到,rand() 是返回 0 到 1 之间的随机数,那么乘 2 后自然是返回 0 到 2 之间的随机数,再配合 floor() 就可以产生确定的两个数了。也就是 0 和 1。
为什么需要这两个数呢?
group by 与 count(*)
group by 主要用来对数据进行分组(相同的分为一组),这里与count() 结合使用。举个例子就一目了然了。
可以观察到,这里对重复性数据进行了整合,然后计数。
重点来了,也就是在这个整合然后计数的过程中,中间发生了什么我们是必须要明白的。
经过网上查询,发现mysql遇到该语句时会建立一个虚拟表。该虚拟表有两个字段,一个是分组的 key
,一个是计数值 count(*)
。也就对应于上个截图中的 prod_price 和 count(*)。
然后在查询数据的时候,首先查看该虚拟表中是否存在该分组,如果存在那么计数值加1,不存在则新建该分组。
报错分析
rand()的特殊性
select count(*) from test group by floor(rand(0)*2);
而又因为 rand 函数的特殊性(如果使用rand()的话,该值会被计算多次)。
在这里的意思就是,group by 进行分组时,floor(rand(0)*2)
执行一次(查看分组是否存在),如果虚拟表中不存在该分组,那么在插入新分组的时候 floor(rand(0)*2)
就又计算了一次。(其实在上述 rand(0) 产生多个数据的时候,也能观察出来。只要 rand(0) 被调用,一定会产生新值)。
这样,所有的理论细节就全部明朗了。
报错
还记得我们之前产生的疑问,为什么要用 floor(rand(0)*2
产生 0 和 1 这两个数吗?
当 group by 对其进行分组的时候,首先遇到第一个值 0 ,发现 0 不存在,于是需要插入分组,就在这时,floor(rand(0)*2)
再次被触发,生成第二个值 1 ,因此最终插入虚拟表的也就是第二个值 1 ;然后遇到第三个值 1 ,因为已经存在分组 1 了,就直接计数加1(这时1的计数变为2);遇到第四个值 0 的时候,发现 0 不存在,于是又需要插入新分组,然后floor(rand(0)*2)
又被触发,生成第五个值 1 ,因此这时还是往虚拟表里插入分组 1 ,但是,分组 1 已经存在了!所以报错!
总结
可见,floor(rand(0)*2
的作用就是产生预知的数字序列01101
,然后再利用 rand()
的特殊性和group by
的虚拟表,最终引起了报错。
什么是堆叠注入
Stacked injections(堆叠注入)从名词的含义就可以看到应该是一堆sql 语句(多条)一起执行。而在真实的运用中也是这样的, 我们知道在 mysql 中, 主要是命令行中, 每一条语句结尾加; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做stacked injection。说直白点就是将一堆sql语句叠加在一起执行,使用分号结束上一个语句再叠加其他语句一起执行。
堆叠注入的原理
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为: Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
堆叠注入演示
我们可以来到sqli-lab的第38关
我们输入一个?id=-1′;select 1,2,3%23
可以看在-1’后面我们用了一个;来进行隔开,这就是相当于两个SQL语句了,产生我们的堆叠注入,我们可以回车看看会出现什么。
然而这里什么都没有出现是因为什么原因呢?
这是因为堆叠注入即使第一条查询结果为空,也不会显示第二条语句执行结果,而且就算第二条语句出错了也不会报错。那么我们该如何去验证我们的堆叠注入呢。
那我们可以构造如下语句
?id=1′;insert into users(id,username,password) values(66,’suifeng’,’hack’)%2
这条语句的意思就是在数据库里面用insert插入我们的id,username和password
我这里插入的id是66
username是suifeng(随风)
password是hack(本来想打shuai(帅)的,哈哈)
执行后
那我们可以到数据库里面去看看
这样就证明我们的堆叠注入是成功了的。
什么是二次注入
1、用户向数据库插入恶意语句
2、数据库对自己存储的数据非常放心,直接取出恶意数据给用户
二次注入演示
我们来到sqli-labs的第24关
这里是一个登陆框,回想我们之前文章所写的内容,碰到登陆框我们一般会试一试sql注入,看看有没有万能密码去绕过登陆框。那我们用一个admin’# 试一试
可以看到这里被拦截了,进而得到这里是做了过滤防护措施的。很显然正常的sql注入已经注入不了了。这一关是一个二次注入,所以我们看看用二次注入来如何实现注入。
首先我们再来回顾一下我们二次注入所需要的条件
第一条是用户向数据库插入恶意语句,所以我们需要插入而已语句,那么登陆框已经做了过滤,我们该如何插入恶意语句呢。
这里有一个New User click here
点击后有一个Register
这里我们可以通过这个注册来插入恶意语句。我们首先注册一个用户为:admin’#,密码为:123
注册成功后,我们可以到数据库里面去看看,到底有没有成功创建。
可以看到这里是成功创建了admin’#这个用户的,这里在注意一个admin账号,这个时候他的账号是admin,密码也是admin。那我们先用账号admin密码admin进行登陆。
这里可以修改密码,那我们利用注册的账户admin’#进行登录,再进到修改密码界面。
然后修改密码把原来的123密码修改为000000看看会发生什么。
发现我们预想的admin’#密码没有被修改,但是admin这个账号的密码被修改成了000000。这是为啥呢。
原SQL语句:UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' 修改密码sql语句:UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' 最后真正执行的sql语句:UPDATE users SET PASSWORD='$pass' where username='admin'
这里就可以看出我们的注释符号把后面给注释掉了,所以修改的是admin的密码。这就是二次注入的威力。
二次注入的防御
1、对外部提交数据谨慎
2、数据库取数据时,不能轻易相信查询出的数据,要做到同样的转义或是甄别
来源:freebuf.com 2020-11-28 20:29:31 by: 随风kali
请登录后发表评论
注册