[网络安全] DVWA之CSRF攻击姿势及解题详析合集

发布时间 2023-06-16 22:07:53作者: 秋说

CSRF

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web应用程序安全漏洞,它利用了用户在已认证的网站中的身份,通过欺骗用户发起非预期的请求。 攻击者会构造一个恶意网页,使用户在浏览器中访问该网页时,自动向目标网站发送了未经用户授权的请求。

CSRF攻击的原理是利用了Web应用程序对用户请求的信任,攻击者构造一个恶意请求并诱使用户触发,从而达到攻击的目的。

常见的CSRF攻击包括修改用户密码、发送电子邮件、进行资金转账等。

Low level

在这里插入图片描述

源代码

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Get input
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

代码审计

  1. 首先检查$_GET['Change']是否存在,以确定是否有密码更改请求。
  2. 代码获取了输入的新密码和确认密码。
  3. 判断新密码和确认密码是否匹配。
  4. 如果匹配,则对新密码进行处理:
    • mysqli_real_escape_string函数用于对新密码进行数据库转义,以防止SQL注入攻击。
    • md5函数用于对新密码进行MD5哈希加密。
  5. 更新数据库中用户密码的语句被构建,并执行更新操作。
  6. 根据操作结果向用户提供相应的反馈信息。

此外,在代码中还调用了一个名为dvwaCurrentUser的函数,该函数返回当前用户的用户名。

姿势

分别输入1,回显如下:

在这里插入图片描述
发现参数以GET方式提交

于是可修改参数为password_new=2&password_conf=2

在这里插入图片描述
再打开链接:

在这里插入图片描述
由页面回显可知,密码修改成功
即成功实施了CSRF

Medium level

源代码

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Checks to see where the request came from
    if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
        // Get input
        $pass_new  = $_GET[ 'password_new' ];
        $pass_conf = $_GET[ 'password_conf' ];

        // Do the passwords match?
        if( $pass_new == $pass_conf ) {
            // They do!
            $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
            $pass_new = md5( $pass_new );

            // Update the database
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Feedback for the user
            echo "<pre>Password Changed.</pre>";
        }
        else {
            // Issue with passwords matching
            echo "<pre>Passwords did not match.</pre>";
        }
    }
    else {
        // Didn't come from a trusted source
        echo "<pre>That request didn't look correct.</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 

代码审计

  1. 首先,代码检查是否设置了名为Change的GET参数,以确定是否有密码更改请求。
  2. 代码使用stripos()函数检查请求来源是否在同一服务器上,以确保请求来自可信任的来源。
  3. 如果请求来源合法,则获取新密码和确认密码的输入。
  4. 检查新密码和确认密码是否匹配。
  5. 如果密码匹配,则对新密码进行处理:
    • 首先,使用mysqli_real_escape_string()函数对新密码进行数据库转义,以防止SQL注入攻击。
    • 然后,使用md5()函数对新密码进行MD5哈希加密。请注意,MD5已经不再被认为是一种安全的哈希算法。
  6. 构建更新数据库中用户密码的SQL语句,并执行更新操作。
  7. 根据操作结果向用户提供相应的反馈信息。

此外,在代码中还调用了一个名为dvwaCurrentUser()的函数,该函数返回当前用户的用户名。

姿势

与Low级别不同的是,Medium级别源代码中加入了该语句

stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) 

即判断 HTTP_REFERER中是否包含SERVER_NAME,即检查请求来源是否在同一服务器上。

HTTP_REFERERReferer参数值,即来源地址
SERVER_NAMEhost参数及主机ip名


思路:在dvwa的www目录下写入一个html文件,该文件包含CSRF链接,抓包使请求包包含该文件即可


故写一个html文件,命名为主机地址.html

内容为:

<img src=“http://127.0.0.1/dvwa/vulnerabilities/csrf/?password_new=2&password_conf=2&Change=Change#” border=“0” style=“display:none;”/>

在这里插入图片描述
主机地址可在 cmd 中输入 ipconfig 得到

抓包后修改参数:

在这里插入图片描述

发包后,从右侧回显可知,密码修改成功。

High level

源代码

<?php

$change = false;
$request_type = "html";
$return_message = "Request Failed";

if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
    $data = json_decode(file_get_contents('php://input'), true);
    $request_type = "json";
    if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
        array_key_exists("password_new", $data) &&
        array_key_exists("password_conf", $data) &&
        array_key_exists("Change", $data)) {
        $token = $_SERVER['HTTP_USER_TOKEN'];
        $pass_new = $data["password_new"];
        $pass_conf = $data["password_conf"];
        $change = true;
    }
} else {
    if (array_key_exists("user_token", $_REQUEST) &&
        array_key_exists("password_new", $_REQUEST) &&
        array_key_exists("password_conf", $_REQUEST) &&
        array_key_exists("Change", $_REQUEST)) {
        $token = $_REQUEST["user_token"];
        $pass_new = $_REQUEST["password_new"];
        $pass_conf = $_REQUEST["password_conf"];
        $change = true;
    }
}

if ($change) {
    // Check Anti-CSRF token
    checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );

    // Do the passwords match?
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
        $pass_new = md5( $pass_new );

        // Update the database
        $insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysqli_query($GLOBALS["___mysqli_ston"],  $insert );

        // Feedback for the user
        $return_message = "Password Changed.";
    }
    else {
        // Issue with passwords matching
        $return_message = "Passwords did not match.";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);

    if ($request_type == "json") {
        generateSessionToken();
        header ("Content-Type: application/json");
        print json_encode (array("Message" =>$return_message));
        exit;
    } else {
        echo "<pre>" . $return_message . "</pre>";
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

代码审计

  1. 首先,代码检查请求是否为POST方法,并且内容类型为application/json。如果满足条件,则将请求解析为JSON格式,并获取相应的参数。
  2. 如果不满足JSON请求条件,则检查非JSON请求的参数。
  3. 如果满足密码更改的条件,则进行以下操作:
    • 检查防跨站请求伪造(Anti-CSRF)令牌。
    • 检查新密码和确认密码是否匹配。
    • 对新密码进行数据库转义和MD5哈希加密。
    • 构建更新数据库中用户密码的SQL语句,并执行更新操作。
    • 根据操作结果向用户提供相应的反馈信息。
  4. 关闭数据库连接。
  5. 根据请求类型,生成新的Anti-CSRF令牌并返回响应。

姿势

由代码审计得:

generateSessionToken()函数用于生成一个随机的token,并将其存储在会话(session)中,确保每个用户都有一个唯一的token。

checkToken()函数用于验证传递给服务器的token是否与存储在会话中的token匹配。这个函数在处理密码更改请求之前被调用,以确保只有合法的请求被处理。

通过这两个步骤,服务器会先验证token的有效性,只有在验证成功后才会处理用户的密码更改请求。

所以在发起请求之前应获取服务器返回的user_token,再利用user_token绕过验证。

method 1

在Burp中安装CSRF Token Tracker

在这里插入图片描述

添加主机名、抓包得到的token名即token值
在这里插入图片描述
再重新抓包,重放包,任意修改密码均成功,插件里的token值会自动更新。

method 2

利用 DVWA 中的 xss(stored) ,实现token的弹出。

在 xss(stored) 页面中输入1,1并抓包

在这里插入图片描述
修改textName参数为<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

此时放包并关闭拦截,页面弹出token:80edcbac43cd9594f29999a0692a608f3

在这里插入图片描述
接着抓CSRF页面的包:

在这里插入图片描述
修改密码及token,放包:

在这里插入图片描述

由页面回显可知,成功修改密码。

Impossible level

源代码

<?php

if( isset( $_GET[ 'Change' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

    // Get input
    $pass_curr = $_GET[ 'password_current' ];
    $pass_new  = $_GET[ 'password_new' ];
    $pass_conf = $_GET[ 'password_conf' ];

    // Sanitise current password input
    $pass_curr = stripslashes( $pass_curr );
    $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
    $pass_curr = md5( $pass_curr );

    // Check that the current password is correct
    $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
    $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
    $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
    $data->execute();

    // Do both new passwords match and does the current password match the user?
    if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
        // It does!
        $pass_new = stripslashes( $pass_new );
        $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
        $pass_new = md5( $pass_new );

        // Update database with new password
        $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
        $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
        $data->execute();

        // Feedback for the user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with passwords matching
        echo "<pre>Passwords did not match or current password incorrect.</pre>";
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

代码审计

  1. 首先,代码通过检查$_GET['Change']是否存在来判断是否有密码更改请求。
  2. 接下来,代码调用了checkToken函数来验证Anti-CSRF令牌。checkToken函数会比较请求中的user_token和会话中的session_token是否匹配,以确认请求的合法性。
  3. 代码获取了输入的当前密码、新密码和确认密码,并对当前密码进行了一系列处理:
    • stripslashes函数用于去除当前密码中的反斜杠。
    • mysqli_real_escape_string函数用于对当前密码进行数据库转义,防止SQL注入攻击。
    • md5函数用于将当前密码进行MD5哈希加密。
  4. 代码执行了数据库查询,检查用户输入的当前密码是否正确。
  5. 如果新密码和确认密码相匹配,并且当前密码正确,则更新数据库中的密码为新密码。
  6. 最后,根据操作结果输出相应的反馈信息。

此外,在代码中还调用了两个额外的函数:

  • generateSessionToken函数用于生成并设置Anti-CSRF令牌,以确保每次渲染表单时都会生成一个新的令牌,并将其存储在会话中。
  • dvwaCurrentUser函数返回当前用户的用户名。

总结

以上为 [网络安全] DVWA之CSRF攻击姿势及解题详析合集,考察CSRFBurp使用PHP代码审计等相关知识。

我是秋说,我们下次见。