DVWA靶场实战(七)——SQL Injection

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    DVWA靶场实战(七)

    七、SQL Injection:

    1.漏洞原理:

      SQL Inject中文叫做SQL注入,是发生在web端的安全漏洞,主要是实现非法操作,例如欺骗服务器执行非法查询,他的危害在于黑客会有恶意获取,甚至篡改数据库信息,绕过登录验证,原理是针对程序员编写数据库程序的疏忽,通过执行SQL语句,实现目的性攻击,他的流程可以分为判断数据库类型、判断数据库版本、判断注入点、判断注入类型、判断数据字段数、判断显示位、获取数据库中的信息。

      简单来说就是通过web表单把SQL命令提交到数据库,由于管理员没有细致的过滤用户输入的数据,造成字符串拼接,进而恶意的SQL语句被执行,造成数据库信息泄露、网页篡改、数据库被恶意操作等后果。

    2.SQL Injection分类:

      从注入参数类型分类:数字型注入、字符型注入、搜索型注入

      从注入方法分:报错注入、布尔盲注、时间盲注、联合查询注入、堆叠注入、内联查询注入、宽字节注入

      从提交方式分:GET注入、POST注入、COOKIE注入、HTTP头注入

    3.SQL注入漏洞的利用思路:

    (1)第一步:

      寻找可能的漏洞站点,也就是目标站点。

    (2)第二步:

      通过站点的后缀名来判断网站的使用的是哪一种数据库,简单的判断可以观察脚本后缀,如果是“.asp”为后缀,则数据库可能是access,如果是“.aspx”为后缀可能是MsSql,如果是“.php”为后缀的可能是mysql数据库,如果是“.jsp”,可能是orcale数据库。

    (3)第三步:

      是寻找站点存在的注入点,可以再URL中进行尝试输入参数后在拼接上引号,通过回显可以判断该站点传输方式,为GET或POST方式POST需要查看表单数据,POST注入在表单中提交引号,而cookie注入可以通过burpsuite工具来判断注入点。

    (4)第四步:

      判断注入点的类型,如果加减法运算按照是否是数字型注入,如果单引号和页面报错信息来进一步判断是哪一种的字符型注入。

    (5)第五步:

      闭合我们输入的SQL语句,通过注释的方式来获取数据,有以下几种情况:

        ①当页面有回显但是没有显示位的时候,可以选择报错注入,常见的报错注入利用函数有那么几个:floor()、exp()、updatexml()、exteractvalue()等函数。

        ②当页面没有明确的回显信息的时候,但是输入正确和输入错误的时候页面不相同的情况下,可以考虑报错注入,报错注入常见的函数有:ascii()、substr()、length()、concat()等函数。

        ③当页面没有会回显也没有报错信息的时候,可以使用时间盲注去获取数据库中的数据,时间盲注常见的函数有:sleep()。

        其他:当然还有很多其他的注入方式,比如:宽字节注入、base64注入、cookie注入、http头部注入、二次注入、堆叠注入等。

    4.漏洞危害:

      (1)获取企业内部、个人未授权的隐私信息,或一些机密数据。

      (2)页面内容伪造篡改。

      (3)数据库、服务器、网络(内网、局域网)受到攻击,严重时可导致服务器瘫痪,无法正常运行。

    5.防御以及修复措施:

      (1)对数据库进行严格的监控。

      (2)对用户提交的数据进行严格的把关,多次筛选过滤。

      (3)对用户内数据内容进行加密。

      (4)代码层面最佳防御sql漏洞方案:采用sql语句预编译和绑定变量,是防御sql注入的最佳方式。

    6.注意事项:

      我们在进行查询的时候可能会遇到union联合查询我们利用Navicat Premium 15链接我们的dvwa的MySQL数据库,然后我们找到dvwa然后右键右键点击选择“设计表”。接下来我们将“first_name”、“last_name”、“user”等全部将默认排序的“utf8_unicode_ci”修改为“utf8_general_ci”。修改完成然后保存,完成修改。就可以正常使用union联合查询语句了。

    7.实战:

    (1)Low:

      代码分析:

    <?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
                    $html .= "<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
                        $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                } else {
                    echo "Error in fetch ".$sqlite_db->lastErrorMsg();
                }
                break;
        } 
    }
    
    ?> 

      可以看出来基本上没有防护,所以我们直接开始攻击了。

      首先利用语句“1’and’1’=’1”和“1’and’1’=’2”来判断是否有注入点。以下为两种语句的不同回显,可以判断出是有注入点的。

      判断完注入点我们尝试获取他的判断他的列数,利用“1’order by 1#”语句替换“1”来判断行数,这里我们“#”或者“--+”的作用是注释掉原本语句后面的内容,让我们能够自如的进行查询。然后我们最后查询到“1’order by 3#”就弹出报错,就说明列数应该为两列。

     

      判断完列数后,我们开始利用联合查询语句,这里使用“-1’union select 1,2#”,来判断显示位。我们这里可以看见回显了另外一个语句,就说明显示位为2,一般靶场中比如sqli-labs那种就会需要将前面的“1”改为“-1”,也就是“-1’union select 1,2#”才会正常回显我们查询的内容。这里我们也可以,之后的演示排除干扰就将第一行结果屏蔽了。

     

      这里我们收集数据库名称利用database()函数,它的作用是返回数据库名,我们在后续查询中是需要数据库才能进行的所以利用语句“-1’union select 1,database()#”接下来进行查询,得到结果数据库名称为“dvwa”。

      我们收集完以上的数据库信息后,我们再查询一下数据库的版本,因为mysql数据库注入大概是以5.0版本为分界线,5.0以下没有information表,5.0以上则都有information表。这里的MySQL版本为5.7.26,那么我们就可以查询information表来找到数据库的表名。

     

      接下来,我们查询dvwa数据库有哪些表名,利用联合查询语句“-1'union select 1,table_name from information_schema.tables where table_schema='dvwa'#”来进行对表名的查询。得到两个表名“guestbook”和“users”。

     

      我们查询好表名后,我们查询列名,利用语句“-1'union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users')#”得到“user_id,first_name,last_name,user,password,avatar,last_login,failed_login

    ”的结果,这个时候我们就可以知道我们应该查询“user”和“password”。

      然后我们查询好表名为“users”和列名“user”、列名“password”后,我们利用查询语句“-1’union select user,password from users”,然后查询得到账号和密码,密码是MD5加密需要自己解密查询。

     

    (2)Medium:

      代码分析:

    <?php
    
    if( isset( $_POST[ 'Submit' ] ) ) {
        // Get input
        $id = $_POST[ 'id' ];
    
        $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
    
        switch ($_DVWA['SQLI_DB']) {
            case MYSQL:
                $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
                    $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
                break;
            case SQLITE:
                global $sqlite_db_connection;
    
                $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
                        $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                } else {
                    echo "Error in fetch ".$sqlite_db->lastErrorMsg();
                }
                break;
        }
    }
    
    // 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提交方式,还使用了转义预防SQL注入。

      我们开始攻击,首先我们打开BP进行截包,得到如下的数据包。并将其发送到“Repeater”,方便接下来的注入回显操作。

     

      我们同样利用“1 and 1=1”和“1 and 1=2”来判断,是否这里为注入点,观察两者的有差异且页面未报错,就说明这里是注入点。

     

      这里我们可以看到,同样是在“3”的时候,我们这里用“order by 3”查询返回报错,所以我们这里判断前面只查询两列。利用语句“1 union select 1,2#”来进行验证,返回正确,验证成功。

     

      此时同样判断完列数后,我们同时对版本和数据库名称进行查询,利用语句“1 union select database(),version()#”。此时观察回显,发现同时回显了数据库名称和数据库版本dvwa和5.7.26。

      在查询完数据库后,我们先查询表名,然后查询列名,利用语句“1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=’dvwa’)#”,但发现报错,没有任何反应,将“dvwa”改为“database()”后查询得到“users”表名后。后来发现是对单引号进行了转义。所以我们将单引号连同users一起写为16进制表示为“0x75736572”。

      查询列名“1 union select (select group_concat(column_name) from information_schema.columns where table_name=0x75736572),(select group_concat(table_name) from information_schema.tables where table_schema=database())”,得到关键列名“user”和“password”。

     

      最后我们利用,“1 union select user,password from users#”语句,得到最后的结果。

     

    (3)High:

      代码分析:

    <?php
    
    if( isset( $_SESSION [ 'id' ] ) ) {
        // Get input
        $id = $_SESSION[ 'id' ];
    
        switch ($_DVWA['SQLI_DB']) {
            case MYSQL:
                // 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
                    $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
    
                ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);        
                break;
            case SQLITE:
                global $sqlite_db_connection;
    
                $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
                #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
                        $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                } else {
                    echo "Error in fetch ".$sqlite_db->lastErrorMsg();
                }
                break;
        }
    }
    
    ?> 

      使用了session 获取id 值,闭合方式单引号闭合。

      虽然用了session来获取id值,但是同样也是数字型注入,利用同样的方法,然后最后用“-1’union select user,password from users #”,即可得到答案。

     

    (4)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 )) {
            $id = intval ($id);
            switch ($_DVWA['SQLI_DB']) {
                case MYSQL:
                    // 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
                        $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                    break;
                case SQLITE:
                    global $sqlite_db_connection;
    
                    $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );
                    $stmt->bindValue(':id',$id,SQLITE3_INTEGER);
                    $result = $stmt->execute();
                    $result->finalize();
                    if ($result !== false) {
                        // There is no way to get the number of rows returned
                        // This checks the number of columns (not rows) just
                        // as a precaution, but it won't stop someone dumping
                        // multiple rows and viewing them one at a time.
    
                        $num_columns = $result->numColumns();
                        if ($num_columns == 2) {
                            $row = $result->fetchArray();
    
                            // Get values
                            $first = $row[ 'first_name' ];
                            $last  = $row[ 'last_name' ];
    
                            // Feedback for end user
                            $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                        }
                    }
    
                    break;
            }
        }
    }
    
    // Generate Anti-CSRF token
    generateSessionToken();
    
    ?>

      此为防御模板,CSRF、检测 id 是否是数字,prepare 预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6