Less 1-5
Less-1 GET - Error based - Single quotes - String(基于错误的GET单引号字符型注入)
注入语句
判断当前查询的数据库表共有几列
http://127.0.0.1/Less-1/?id=-1' order by 4 --+
-
其中
?
代表使用get方法传入参数,参数名称为id
。 -
将
id
赋值为-1
,也就是一个不成立的条件,使系统原本要查绚的东西不回显,因为在这个系统中只回显两个值,如果正常查询的话,注入的语句查询结果就不能回显。此处后面有详细说明。 -
-1
后面的'
作用是闭合后端php语言中传递参数原有的一个引号(传入的参数为字符型)。 -
后面的
order by 4
中通过改变其中数字的值,从1,2,3直到4依次尝试,这里尝试到4页面出现错误信息,代表数据库表共有三列。(order by 在sql语句中表示按照第几列排序输出,默认升序,后跟数字或列名) -
最后的
--+
的作用使注释掉php语言中传入参数最后的那个引号。sql语句中使用--
进行单行注释,这个方法在--
后加入一个空格才有效。而在某些http请求实现过程中+
发送到服务端后会被转化成空格。因此这里使用--+
进行注释,当然将+
换为空格的url编码%20
也可以。同时,mysql数据库中也可以使用#
进行注释,但http请求中并不包含#
,因此要将#
进行url编码为%23
。例:
create database database_1 -- 创建数据库database_1
create database database_1 #创建数据库database_1
查看哪一列的查询会回显
http://127.0.0.1/Less-1/?id=-1' union select 1,2,3 --+
假设存在数据库表
id | username | password |
---|---|---|
1 | Dumb | Dumb |
正常查询传入参数id=1
时既可以查到上述数据。
当注入语句为
http://127.0.0.1/Less-1/?id=1' union select 1,2,3 --+
时,得到结果为:
此时只回显了查询id=1
的部分。这时可以将id
传入为-1,使得查询不成立,这时的结果就为:
此时回显了union select
后面的内容。这里可以看到两个回显点分别为union select
的第二和第三个位置。
注意,因为原本查询的数据库表列数为3列,使用union select
语句查询的表也只能是3列,因为这两个查询的结果要进行拼接。也就是说这里只能填入union select 1,2,3
,而不能是union select 1,2
或union select 1,2,3,4
等等。
查询数据库各种信息
查询数据库名
http://127.0.0.1/Less-1/?id=-1' union select 1,2,database() --+
database()
函数,返回当前数据库名称。在第二个回显点,也就是union select
的第三个位置输入database()
函数。
可以看到当前数据库名称为security
查询数据库表名
http://127.0.0.1/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
-
group_concat()
函数,可以使多行数据在一行中显示。 -
information_schema
是mysql特有的库,存储各种数据库的信息 -
information_schema.tables
代表information_schema
数据库中的tables
表,详细表述了某个表属于哪个数据库,表类型,表引擎,创建时间等信息。其中table_schema
字段表示该表名属于哪个数据库。
查询列名
http://127.0.0.1/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
可以看到感兴趣的列名分别为username
、password
查询用户名
http://127.0.0.1/Less-1/?id=-1' union select 1,2,group_concat(username) from security.users--+
查询密码
http://127.0.0.1/Less-1/?id=-1' union select 1,2,group_concat(password) from security.users--+
Less-2 GET - Error based - Intiger based(GET方法,基于错误的整型数据注入)
这个在后端编写的时候通过get传入的参数为整型,那么在传入id
参数为-1
的时候就不用在-1
后面加上'
用于闭合前面的后端编写时前面的'
。其余的注入方式与上一关相同。
Less-3 GET - Error based - Single quotes with twist string (基于错误的GET单引号变形字符型注入)
这一关与第一关的区别在于传入参数在后端的闭合方式不同,第一关使用'
闭合,这一关使用')
闭合,其余注入方式没有区别。
Less-4 GET - Error based - Double Quotes - String (基于错误的GET双引号字符型注入)
这一关使用")
闭合,其余注入方式没有区别。
Less-5 GET - Double Injection - Single Quotes - String (双注入GET单引号字符型注入)
这一关使用报错注入,报错注入就是通过人为的引起数据库的报错,但是数据库在报错的同时会将查询的结果也呈现在报错中。
1. floor()
函数报错
利用floor( )函数进行报错注入时,需要和rand( )、count(*)、group by等函数或命令进行配合。其中:
-
floor(x):对参数x向下取整,比如floor(0.2)=0,floor(3.6)=3。
-
rand( ):生成一个0~1之间的随机浮点数。
-
count(*):统计某个表下总共有多少条记录。
-
group by x:按照(by)一定的规则(x)进行分组。
rand( )函数有两种形式:
无参数的rand( ):生成一个0~1之间的随机浮点数,无规律,每点一次运行,生成的数都会变;
有参数的rand(x):如rand(0),生成一个0~1之间的随机浮点数,相当于指定随机数生产的种子,这种情况产生的随机数与函数本身的计算次数相关,每次产生的值都是固定的,如下图所示,每次点一次运行生成的数据都无变化:
根据上述规律,每一次计算floor(rand(0)*2)的值也应是固定的,遵循0、1、1、0、1、1、0、0、1…这样的规律:
至于from
后面的表,应该填一个已知的确认表,一般会填上information_schema.schemata
,其实只要数据库中存在的表都可以,跟生成的结果没有任何关系。如果填上不存在的表名或填错了,会报错说表不存在。
关于count(*)
和group by
假设有表a
name | city | score |
---|---|---|
li | beijing | 80 |
wang | guangzhou | 70 |
zhou | shanghai | 85 |
yang | beijing | 75 |
sun | shanghai | 90 |
当我们执行select count(*) from a group by city时,数据库系统首先会生成一张空的虚拟表,由于group by city,第一次读取的值是“beijing”,系统紧接着会在虚拟表中寻找是否已经存在“beijing”,由于表是空的,因此直接插入一行数据,虚拟表变成:
key | count(*) |
---|---|
beijing | 1 |
紧接着,第二次读取的值是“guangzhou”,由于虚拟表中依旧没有key为“guangzhou”的字段,故插入;第三次读取是“shanghai”,继续插入;第四次读取是“beijing”,由于虚拟表中已经有了“beijing”,故将key为“beijing”的字段的count(*)的值加1,变为了2。剩下的以此类推,最后形成了这个虚拟表,也就是select count(*) from a group by city执行后的最终结果:
key | count(*) |
---|---|
beijing | 2 |
guangzhou | 1 |
shanghai | 2 |
构造报错语句
select count(*),(floor(rand(0)*2))x from information_schema.schemata group by x;
注:本例中所用的表为information_schema.schemata,换成别的表也是可以的,但必须保证其至少拥有3行数据!
按照上述过程,数据库首先会建立一个空的虚拟表:
key | count(*) |
---|---|
第一次group by x,也就是group by floor(rand(0)*2),由于floor(rand(0)*2)第一次运算的值是0,而空表中没有key=0的数据,故插入一行数据,插入数据的过程中需要再取一次group by后面的值(即再计算一次floor(rand(0)*2),此时为第二次计算floor(rand(0)*2),结果为1),取到了1,将其插入到空表,并将count(*)置1。
key | count(*) |
---|---|
1 | 1 |
第二次group by floor(rand(0)*2),此时为第三次计算floor(rand(0)*2),结果为1,刚好表中有了key=1的数据,故直接将其对应的count(*)加1即可。
key | count(*) |
---|---|
1 | 2 |
第三次group by floor(rand(0)*2),此时为第四次计算floor(rand(0)*2),结果为0。于是,系统在表中寻找是否有key=0的数据,发现并没有,故应当插入一条新记录。在插入时再一次计算floor(rand(0)2)(就像第一次group by那样),此时为第五次计算floor(rand(0)*2),结果为1,因此需要将key=1那一行的count(*)置1。但是,这里的矛盾暴露出来了——虚拟表中已经有了key=1的数据,对应的count()为2。对于这种情况,数据库系统会报出主键冗余的错误(Duplicate entry ‘1’ for key ‘group_key’),也就是所谓的floor报错。
构造注入语句
获取数据库名
http://127.0.0.1/Less-5/?id=1' union select 1,count(*),concat((select database()),floor(rand(0)*2))a from information_schema.tables group by a --+
-
concat()
函数用于将多个字符串连接在一起,变为一个字符串。这里将要查询的数据库名与floor()
函数计算出的结果拼接在一起,这样在报错时会以数据库名1
的形式显示出来。 -
这里不能使用上文中的
group_concat()
函数,该函数后接group by
字段可以指定按照个字段将字符串拼接,因为前面的count(*)
函数也用到了group by
字段,这里如果使用group_concat()
函数会产生冲突。
获取数据库security
中表名
http://127.0.0.1/Less-5/?id=1' union select 1,count(*),concat((select (select group_concat(table_name) from information_schema.tables where table_schema='security')),floor(rand(0)*2))a from information_schema.tables group by a--+
这里就将上个语句中的database()
函数换成查询表名payload即可,这里不再赘述。
2.extractvalue()
函数报错
http://127.0.0.1/Less-5/?id=1' and (extractvalue(1,concat(1,(select database())))) --+
extractvalue()
函数使用时向其传递两个参数,注入时在第一个参数处写入任意字符,在第二个参数处写入concat(1,payload)
即可。
3.updatexml()
函数报错
http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(1,(select database())),1) --+
与extractvalue()
函数类似,这里在传入三个参数,在第三个参数上写上任意字符,其余与上一个函数使用方法相同。