这或许是你见到的最全SQL注入教程了! – 作者:随风kali

什么是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注入。

1601280169_5f7198a9a2b1512464077.png!small

这里有一个user id需要我们填写,那我们先填写一个1看看会发生什么。

1601280223_5f7198dfcab00ba138b39.png!small

这里查询到了user id为1号的信息,还记得我们前文说的验证SQL注入的方法吧,在1后面加个单引号,那我们输入1‘在试一试。

可以看到我们输入1’导致SQL语句不能正常解析最终导致程序报错

1601280405_5f71999538e4e044fc8d5.png!small

那我们初步判断这里是存在注入点的,那么我们可以去查看一下源代码来分析分析。

1601281374_5f719d5ef3e3c430517a4.png!small

可以看到这里的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语句种#是注释的意思

1601296702_5f71d93e8f0e4884ecb4c.png!small

输入后可以看到爆出我们的当前数据库名字

在知道一条数据后我们就可以通过如下语句进行查询

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”#

1601536518_5f758206045e79e18b75b.png!small

有看到users表,users里面一般存放的是用户的信息,所以我们再爆一下users表下面的列名信息

1′ union select 1,column_name from information_schema.columns where table_name=’users’#

1601537004_5f7583eccf3efb7bb989c.png!small

这里可以看到一些user、password字段估计就是存放用户和用户密码了,那我们再查看一下这些字段里面的内容。

1′ union select 1,concat(user,password) from users#

1601537260_5f7584ec94a0ea42962b3.png!small

这里就爆出了账号和密码的MD5值,然后我们可以把MD5值解密就可以查看到用户密码

1601537359_5f75854ff2a4d1dc170e0.png!small

Boolean注入

一、什么是Boolean注入

Boolean型的注入意思就是页面返回的结果是Boolean型的,通过构造SQL判断语句,查看页面的返回结果是否报错,页面返回是否正常等来判断哪些SQL判断条件时成立的,通过此来获取数据库中的数据。

二、Boolean注入实战

这里我们可以打开我们sqli-labs的第八关,这个靶场的源代码在之前的渗透测试之外网准备一文中发过了,不知道的可以再去看一下。

1601779252_5f7936342c84d44b228fd.png!small

第八关是一个Boolean注入的环境,首先我么先输入一个?id=1看一看会有什么情况

1601779321_5f793679812a2ff8fa6d1.png!small

这里出现了一个You are in……的字段

那么我们在输入一个?id=1’看看会出现什么

1601779401_5f7936c96de8e392f25a0.png!small

很显然,这里我们输入?id=1’ 查询语句是无法正常执行的,按照以往我们经验这里是会报错的,爆出一个语句查询错误的英文句子,但是这里没有。可是仔细观察,我们就会发现区别。错误的语句中是没有You are in…..这个字段的,那么我们就可以通过这个特性来进行我们的注入了。

三、boolean注入思想

在进行boolean我们是这样思考的。这里打个比方,站在你面前一个人,你问他叫什么名字,但是他是个哑巴不能说话,只会点头和摇头,这个时候你该怎么去得到他的名字呢。对,当然是我们去问他,然后他通过点头或摇头来告诉我们的正确与否。假设这个人名字叫张三,我问他,你姓李吗。他给我摇了个头,这个时候我知道了他不姓李,我继续问,直到我们他你姓张吗,他给我点了个头,这个时候我知道了。哦!原来他姓张,后面才字也是一样的手段。当然这样询问是工作量非常大的,在我们的boolean注入里面也是一样,所以我们在进行boolean注入的时候都会写一些自动化的注入工具来帮助我们完成注入。

四、开始boolean注入

再进行boolean注入之前我们还需要知道一些MySQL的一些内置函数

length(str):返回str字符串的长度。

substr(str, pos, len):将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
知道了这些我们就可以开始我么的注入了
我们输入?id=1′ and ascii(substr((select database()),1,1))>104 %23
这句话的意思是判断数据库的第一个字符是否是我们所给出的字符
发现给我们返回的是You are in…..字段
那就相当于给我们点了个头
1601780307_5f793a53cff95efac6f38.png!small
那这样我们就知道了数据库的第一个字符,接下来就是写自动化脚本慢慢拆解了。当然一些代码功底薄弱的师傅还可以借助sqlmap帮助进行注入工作。

一、什么是报错注入

顾名思义,报错注入就是通过页面爆出的错误信息,构造合适的语句来获取我们想要的数据,本章节讲述的注入,数据库为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看看是什么个样子

1602573137_5f855351bed1e451fdea5.png!small

出现了一个You are in…….的字样

那我们在后面再加一个单引号闭合看看出现什么

1602573198_5f85538e1fed1c2eab657.png!small

这里爆出了错误语句,这个时候我们就可以利用我们报错注入来进行注入了

我们可以利用如下的payload

?id=-1'union select count(*),count(*), concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a--+

这里就爆出了数据库名

1602574244_5f8557a443a7ee7369512.png!small

接下来就是表名、字段名等,那么这个payload实现的原理是什么呢。

几个关键函数的说明

rand(0)*2

rand() 可以产生一个在0和1之间的随机数。
图片[17]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

可见,每次产生的都不一样。当我们提供一个种子参数 0 后,再次查看:
图片[18]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

可以发现,每次产生的值都是一样的。也可以称之为伪随机(产生的数据都是可预知的)。
查看多个数据看一下。( test 是我之前创建的一个拥有9条数据的表)
图片[18]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

发现第一条数据与刚才查看的单个数据相符合,其它的数据也完全一样。
为什么要乘以 2 呢?这就要配合 floor 函数来说了。

floor(rand(0)*2)

floor() 返回小于等于该值的最大整数。
之前我们了解到,rand() 是返回 0 到 1 之间的随机数,那么乘 2 后自然是返回 0 到 2 之间的随机数,再配合 floor() 就可以产生确定的两个数了。也就是 0 和 1。
图片[20]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

为什么需要这两个数呢?

group by 与 count(*)

group by 主要用来对数据进行分组(相同的分为一组),这里与count() 结合使用。举个例子就一目了然了。
图片[20]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

可以观察到,这里对重复性数据进行了整合,然后计数。

重点来了,也就是在这个整合然后计数的过程中,中间发生了什么我们是必须要明白的。
经过网上查询,发现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 这两个数吗?
图片[22]-这或许是你见到的最全SQL注入教程了! – 作者:随风kali-安全小百科

当 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关

1603172666_5f8e793a5bd8796f43cfc.png!small

我们输入一个?id=-1′;select 1,2,3%23

可以看在-1’后面我们用了一个;来进行隔开,这就是相当于两个SQL语句了,产生我们的堆叠注入,我们可以回车看看会出现什么。

1603173136_5f8e7b10dfbb49095e9b3.png!small

然而这里什么都没有出现是因为什么原因呢?

这是因为堆叠注入即使第一条查询结果为空,也不会显示第二条语句执行结果,而且就算第二条语句出错了也不会报错。那么我们该如何去验证我们的堆叠注入呢。

那我们可以构造如下语句

?id=1′;insert into users(id,username,password) values(66,’suifeng’,’hack’)%2

这条语句的意思就是在数据库里面用insert插入我们的id,username和password

我这里插入的id是66

username是suifeng(随风)

password是hack(本来想打shuai(帅)的,哈哈)

执行后

1603173639_5f8e7d07699c744b970d1.png!small

那我们可以到数据库里面去看看

1603174061_5f8e7ead44f4411451255.png!small

这样就证明我们的堆叠注入是成功了的。

什么是二次注入

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。网站对我们输入的一些重要的关键字进行了转义,但是这些我们构造的语句已经写进了数据库,可以在没有被转义的地方使用。可能每一次注入都不构成漏洞,但是如果一起用就可能造成注入。
二次注入触发条件

1、用户向数据库插入恶意语句

2、数据库对自己存储的数据非常放心,直接取出恶意数据给用户

二次注入演示

我们来到sqli-labs的第24关

1604905348_5fa8e9841aafb29770522.png!small

这里是一个登陆框,回想我们之前文章所写的内容,碰到登陆框我们一般会试一试sql注入,看看有没有万能密码去绕过登陆框。那我们用一个admin’#  试一试

1604905826_5fa8eb621ed6c408b975e.png!small

1604905842_5fa8eb72b8535d975457b.png!small

可以看到这里被拦截了,进而得到这里是做了过滤防护措施的。很显然正常的sql注入已经注入不了了。这一关是一个二次注入,所以我们看看用二次注入来如何实现注入。

首先我们再来回顾一下我们二次注入所需要的条件

第一条是用户向数据库插入恶意语句,所以我们需要插入而已语句,那么登陆框已经做了过滤,我们该如何插入恶意语句呢。

这里有一个New User click here

1604906415_5fa8edaf2c6f5a06609ad.png!small

点击后有一个Register1604906461_5fa8eddd8d234e6cfa69b.png!small

这里我们可以通过这个注册来插入恶意语句。我们首先注册一个用户为:admin’#,密码为:123

1604906898_5fa8ef92e3be6f18a1107.png!small

注册成功后,我们可以到数据库里面去看看,到底有没有成功创建。

1604907593_5fa8f24945d54c74bcbba.png!small

可以看到这里是成功创建了admin’#这个用户的,这里在注意一个admin账号,这个时候他的账号是admin,密码也是admin。那我们先用账号admin密码admin进行登陆。

1604908818_5fa8f7129e70b96eb683c.png!small

这里可以修改密码,那我们利用注册的账户admin’#进行登录,再进到修改密码界面。

然后修改密码把原来的123密码修改为000000看看会发生什么。

1604909143_5fa8f857bf248df9cb067.png!small

发现我们预想的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

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论