本人小白,说的不对的话,请多多指教。
第一章是sql注入的练习,所以在练习中大胆尝试输入可能就会有意想不到的输出。
首先点击查看,进入题目。
然后点击传送门,就正式开始我们的练习了。
注:下面图中的url地址栏中 %20 是空格, %27 是'单引号。
进入网站后,如下图所示。首先我们可以看到url地址栏中有两个信息非常显眼--前面的ip地址以及后面紧跟着的端口号。但是这个信息对于这道题目来说貌似没有什么作用。那我们继续看页面里的内容,发现只有一个地方可以点击,也就是这个a标签。
点击a标签之后,果然出现了不同寻常的变化,如下图。首先是地址栏中在ip和端口后面多了 /?id=1 ,其次在页面中也显示出了一篇文章。根据所学的知识,自然而然就联想到,我点击这个a标签之后,浏览器以get方式发送了一个请求报文,而服务器收到请求后,让对应的后台程序在数据库中查询出了一篇文章,然后将这篇文章返回给了浏览器。那后台程序是根据什么去查询到这篇文章的呢?我们可以清晰地看到url地址栏中"?"后面的参数 id=1 ,这个也是在点击a标签之后才有的,那么两者之间一定存在着某种联系。那就尝试改变id的值看看会有什么不同的变化。
我将id的值改成了2,如下图,果然发现了不同寻常的变化。页面上很明显地发生了一个极大的变化,刚才的文章消失了。如此,我便猜测,id的值应该是源代码中某个变量的值,后台程序根据这个值去数据库查询一些内容,那我是不是可以在这个地方动些手脚,搞一搞事情,没准他的源代码里就没有防注入(本来就没有,大家都心知肚明,哈哈哈)。
于是,我就直接在 id=1 后面拼接上了 and 1=1。为什么要写这句话,因为我猜测他的后台程序中可能是用存放id值的变量拼接在数据库查询语句里,去数据库查询的。那么正常情况下,他的查询语句应该是这样的--select '某字段' from '某个表' where id='某个值'。而这个id的值是一个数字,那么应该不会被引号包围,因此直接拼接即可。而众所周知,1=1一定是true,某个值and true那么结果还应该是这个值,即不会对查询结果造成影响。如下图所示,确实没有影响。证明我们的猜测思路大致是正确的。
那我们只需进一步验证,因此我直接在 id=1 后面拼接上了 and 1=2,如果我们的猜测是正确的,那么现在返回的页面应该不再有文章显示,因为1=2是false,任何值and false都是false。如下图,果然如此。到这里,我们就证明了这个网站的后台程序存在着sql注入。
接下来,我们就可以利用sql注入获取我们需要的信息。但是如何获取呢?我们现在能改变的只有id的值,也就是可以改变查询语句。如何改变才能获得我们需要的信息?当然,我想到了联合查询这个方法十分符合现状,因为我完全可以屏蔽他本身的查询结果,只显示我需要的查询结果。但问题是,联合查询需要前后查询得到的结果集的字段数一致。那我又该怎么得到当前这张表的字段数呢?当然是通过order by子句,因为order by是根据指定字段将查询得到的结果集进行排序,当order by 3时,其实就是根据第三个字段进行排序,那么要是不存在第三个字段会怎么样呢?当然是查询出错,不会返回结果集了,那么页面的文章也就不会显示了。基于这个原理,我们可以猜测得出当前页面的查询结果集的字段数。
如下图,我先order by 6,发现文章不显示,那么就意味着当前结果的字段数一定小于6。
继续尝试比6更小的数,最后得到当order by 2的时候,页面正常显示,这就意味着这个查询结果集只有两个字段,如下图。如此一来,我们就可以利用联合查询,获得我们需要的信息了。
首先我们需要获取该页面当前使用数据库系统的版本信息,有什么用呢?后面会讲到。如何获取,可以用select version()这个语句来获取,如下图。但是下图中我是这么写的-- id=1 and 1=2 union select 1,version(),为什么是这样呢?
首先 and 1=2是为了将前面的查询结果屏蔽掉,也就是让他查询出错,其实也可以直接写id=2,因为这样也是查不到结果(上文测试过)的。其实就是要不让id等于一个数据库中不存在的值,比如2,要不就是上面的and 1=2也就是结果为false就可以。那么为什么要屏蔽掉前面的结果呢?因为我们发现,一个id对应一篇文章,而每次请求都只需要返回一篇文章,那么后台程序中很有可能只将查询得到的结果集mysqli_fetch_row()了一次,也就是只读取了结果集中一行的信息,如果真的是这个情况,不屏蔽他正常的查询结果,那我们需要的信息就永远无法显示出来了。因此,以防万一,先屏蔽之前的正常结果。
其次,select 1,version()。为什么version前面会有一个1,这就是之前提到的联合查询必须保证前后查询的得到的字段数一致,而我们之前用order by子句测试发现,正常查询应该是得到两个字段,因此后面的查询结果也必须是两个字段,这个1完全是为了凑够字段数,可以改成其他数字(union前后对应位置的字段类型必须类似),但是不能将1和version调换位置,因为union得到的结果集默认列名是前者查询得到结果集的列名,如果调换位置,显示的就是1而不是版本号了。
如下图,得到版本号为5.5.53。
得到版本号之后,我们可以用类似的手段,通过select database()得到当前的数据库名为maoshe。如下图。
这个时候,我们就需要去得到当前数据库下的表名了,有了表名,一切都好操作。怎么得到表明呢?这个时候我们刚才的得到的版本号就有作用了,在mysql5.0之后可以用查询系统库的方式得到需要的表名,即information_schema中存放了该数据库系统所建的所有库名,表名,列名。而这个网站的数据库版本号是5.5.53符合条件。因此select 1,table_name from infromation_schema.tables where table_schema='maoshe',这句语句的意思就是在系统库存放表名的表中(infromation_schema.tables)查询表所属的数据库为maoshe的所有表名(table_name),并返回。这个语句也可以换成select 1,table_name from infromation_schema.tables where table_schema=database(),这样的话上一步得到数据库名的操作就没有必要做了。
这样,我们就看到了如下图所示的表名admin。但是有一个问题,admin这个表名的字面意思就是管理员表,存放的应该是管理员的账号密码等信息,而我们之前测试过,存放id和文章信息的表只有两个字段,因此这个maoshe数据库至少有两张表,一张是admin,另一张就是存放文章信息的表,但是这张存放文章的表为什么没有显示出来呢?这里又得回到上文提到的,这个网站的后台程序中可能只执行了一次mysqli_fetch_row()函数,因此只返回了第一行的信息,即表名admin,而存放文章信息的表名虽然也被查询出来了,但是它没有从结果集中被读出。
得到admin之后,就离结束不远了。既然表名是admin,那么里面肯定有password字段(要是没有岂不是很尴尬)。输入select 1,password from admin之后,答案出现了--hellohack。如下图。
最后将答案填入flag完成解题。
谢谢观看。
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.