DVWA练习之SQL Injection
本博客将记录在web安全问题中一种常见的漏洞——“SQL注入”漏洞的实践演练。首先阐述SQL注入漏洞的概念,原理,最后展示基于DVWA的各种等级的SQL注入漏洞。
SQL注入
概念:指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,尽管如此,SQL注入仍是现在最常见的Web漏洞之一。
当我们手动进行SQL注入是常有以下步骤:
1.判断是否存在注入,确定数据库POC
2.确定SQL查询语句中的字段数
3.确定显示的字段顺序
4.获取当前数据库
5.获取数据库中的表
6.获取表中的列名
7.下载数据
DVWA-SQL注入
DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。
部署好DVWA后,登陆DVWA首先选择练习的难度等级。DVWA中共分为四种安全级别:Low,Medium,High,Impossible。然后选择练习的模块,这里我们选择SQL Injection(SQL注入)模块。
Low等级
选好等级后,进入SQL Injection(SQL注入)模块。
首先进行正常操作。输入User ID为1。
在尝试输入1’。
提示报错,说明存在SQL注入漏洞。接下来我们就要判断SQL语句的POC了。SQL常有三种POC:
(1)….where user_id = $id
(2)….where user_id = ‘$id’
(3)….where user_id = “$id”
首先我们尝试输入1 or 1=1
结果和输入1时相同,说明POC不对。
我们在尝试输入1' or '10'='10
返回了多个结果,说明POC正确。
我们在看看最后一种输入1" or "10"="10
其结果也和输入1时相同。
确定了POC后我们要在确定查询的字段数。使用order by来逐步确定字段数。
首先输入1' order by 1--
注意–后面有一个空格!这里字段数肯定大于1,只是为了展示下正确时的结果。
我们在输入1' order by 10--
这里显示错误说明字段数小于10,我们使用二分法逐步缩小范围,最终确定查询字段数为2。
确定字段数后,我们还要确定字段的顺序。我们输入1' union select 1,2 --
说明字段顺序为First name,Surname。
然后我们就要获取我们的数据库了。输入1' union select 1,database() --
通过结果可知数据库名为dvwa。
我们在获取数据库中的表名。输入1' union select 1,table_name from information_schema.tables where table_schema='dvwa'--
通过结果我们得到两个表名guestbook和users。
很明显表users中是我们想要得到的信息。我们还要确定表users中的字段名。输入1' union select 1,column_name from information_schema.columns where table_name='users'--
其中我们比较想要得到的是用户名和密码,也对应这user和password。我们输入1' union select user,password from users--
其中hash密码直接到www.cmd5.com
进行cmd5解密即可!
Medium等级
我们修改为Medium等级后发现页面变化很大,没有了原来输入数据的窗口而是换成了下拉单的形式。没有了输入窗口也就意味着Low等级的方法肯定不行了。因此我们只能抓包再进行处理了。
抓到数据包后依然是先尝试确定POC。首先将id的参数改为1 or 12=12
发现数据包发送成功了,返回了多个值,说明数据库的POC为第一种。
我们尝试下1' or '12'='12
,响应包显示失败。1" or "12"="12
同理也是失败。
然后我们要确定字段数等操作和Low等级相同,只不过这里是在OWASP ZAP中进行的而已。
但是当我们修改的id的参数中有单引号’时会报错,可以看出单引号‘被转义了。
此时我们可以使用HEX编码将dvwa编码为0x64767761,就可以去掉单引号直接输入了,返回结果也是成功的。
我们可以查看Medium等级源代码:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Display values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
//mysql_close();
}
?>
发现Medium级别还有使用mysql_real_escape_string函数对特殊符号进行转义。
High等级
High的等级又有了很大的不同。他会弹出另一个页面,要在弹出的页面上输入数据。
那我们还是先按Low等级方法进行尝试,发现High等级与Low等级一模一样。也就是说两者唯一的区别就是,High等级是两个页面操作,而Low等级是一个页面。影响就是High等级使用自动方法sqlmap时会有些复杂。
我们查看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 = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>
High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。但是虽然添加了LIMIT 1,但是我们可以通过--
将其注释掉。
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级别的代码检测了id的数据类型使用预编译绑定id变量,有效防御SQL注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。