CSRF攻击和防御

CSRF概念:

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用;尽管听起来像跨站脚本(XSS)但是CSRF与XSS攻击不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站;与XSS攻击相比,CSRF攻击往往不大流行(防范容易忽视),所以比XSS攻击更具有危险性;

可以这样理解:CSRF攻击者盗用了你的身份并以你的名义发送恶意请求,对服务器来说这个服务器是完全合法的,但是却完成了攻击者期望的操作,比如以你的名义发邮件,发消息,盗取你的账号,转账等;如:

WebA为存在CSRF漏洞的网站,WebB为CSRF攻击者构建的恶意网站,UserC为WebA网站的合法用户;

CSRF攻击介绍和防御:

        CSRF攻击的攻击原理及过程如下:

            1、用户C打开浏览器,访问受信任网站A,输入用户名和密码登陆网站A;

            2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求;

            3、用户未退出网站A之前,在同一浏览器中,访问了网站B;

            4、网站B接受到用户请求后,返回一些攻击性的代码,并发出一个请求要求访问网站A;

            5、浏览器在接受到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,想网站A发出请求;网站A并不知到该请求其实是由网站A发起的,所以会根据用户的Cookie信息以用户的权限处理该请求,导致来自网站B的恶意代码被执行,造成严重后果;

        CSRF攻击示例:

            受害者Bob在银行有一笔存款,通过对银行的网站发送请求http://localhost/eight_month/test/csrf/withdraw.php?money=1000000&for=bluce可以使Bob把1000000的存款转到bluce的账号下,通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的session,并且该session的用户Bob已经成功登录;代码示例:

<?php
header('content-type:text/html;charset=utf-8');
session_start();//先匹配session,
if(isset($_SESSION['username'])){
	
	$money=$_POST['money'];
	$for=$_POST['for'];
	var_dump($money);
	var_dump($for);
	echo 'has login   '.$username=$_SESSION['username'];
	$dbms='mysql';     //数据库类型
	$host='localhost'; //数据库主机名
	$dbName='yii2basic';    //使用的数据库
	$user='root';      //数据库连接用户名
	$pass='root';          //对应的密码
	$dsn="$dbms:host=$host;dbname=$dbName";
	try {
	    $pdo = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一个PDO对象
	    echo "连接成功<br/>";
		 try {  
	        $pdo->beginTransaction(); // 开启一个事务  
	        $row = null;  
	        $sql1="update `user` set  money=money-$money where username='$username'";
	        echo $sql1;
	        //die;
	        $row = $pdo->exec($sql1); // 执行第一个 SQL  
	        if (!$row)   
	            throw new PDOException('提示信息或执行动作'); // 如出现异常提示信息或执行动作  
	        $sql2="update `user` set  money=money+$money where username='$for'";
	        $row = $pdo->exec($sql2); // 执行第二个 SQL  
	        if (!$row)   
	            throw new PDOException('提示信息或执行动作');  
	        $pdo->commit();  
	    } catch (PDOException $e) {  
	        $pdo->rollback(); // 执行失败,事务回滚  
	        exit($e->getMessage());  
	    }  

		$dbh = null;
	} catch (PDOException $e) {
	    die ("Error!: " . $e->getMessage() . "<br/>");
	}

}else{
	echo "to login";
}
注意:这之前用户已经登陆,
session已经有用户信息

黑客Mallory自己在该银行也有账户,他知道上文中的URL可以把钱进行转账操作,Mallory可以自己发送一个请求给银行http://localhost/eight_month/test/csrf/withdraw.php?money=1000000&for=Mallory,但是这个请求来自Mallory而不是Bob,他不能通过安全验证,因此该请求不会起作用;

扫描二维码关注公众号,回复: 2121612 查看本文章

这时,Mallory用CSRF攻击方式,自己做一个网站,网站中有以上请求地址,并且通过广告等诱惑Bob来访问他的网站,当Bob访问该网站时,上述URL就会从Bob的浏览器发送给银行,而这个请求会附带Bob浏览器中的Cookie信息一起发送给银行服务器,大多数情况下,请求会失败,因为他要求Bob的认证信息,但是,如果Bob当时Bob刚访问他的银行后不久,他的浏览器与银行网站之间的session尚未过期,浏览器的Cookie中含有Bob的认证信息,这时悲剧就发生了,这个恶意的请求就会得到响应,钱就会从Bob的账号转移到Mallory的账号,而Bob毫不知情,等以后Bob发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自他自己的的合法的资金转移,没有任何被攻击的痕迹,而Mallory则可以拿到钱后逍遥法外;

代码:

<html>
<head><meta charset="utf-8" /></head>
<script src="/jquery.min.js"></script>
恭喜你钱没了

	<input type="hidden"  name="money" value="1000000">
	<input type="hidden"  name="for1" value="Mallory">
<script>
	var money=$('[name=money]').val()
	var for1=$('[name=for1]').val()
	$.post("http://localhost/eight_month/test/csrf/withdraw.php",
	  {
	    money:money,
	    for:for1
	  },
	  function(data){
	    alert("恭喜你钱没了");
	  });
</script>
</html>

防御CSRF攻击:
在请求地址中添加token并验证:
    

CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

        这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token

post方式代码如下:

    转账页面

<html>
<head><meta charset="utf-8" /></head>
<script src="/jquery.min.js"></script>
恭喜你钱没了

	<input type="hidden"  name="money" value="1000000">
	<input type="hidden"  name="for1" value="Mallory">
<script>
	var money=$('[name=money]').val()
	var for1=$('[name=for1]').val()
	$.post("http://localhost/eight_month/test/csrf/withdraw.php",
	  {
	    money:money,
	    for:for1
	  },
	  function(data){
	    alert("恭喜你钱没了");
	  });
</script>
</html>
处理转账页面:
<?php
header('content-type:text/html;charset=utf-8');
session_start();//先匹配session,
if(isset($_SESSION['username'])){
	if($_POST['csrf_token']!=$_SESSION['csrf_token']){
		//die;
		echo "非法请求";
		die;
	}

	$money=$_POST['money'];
	$for=$_POST['for'];
	var_dump($money);
	var_dump($for);
	echo 'has login   '.$username=$_SESSION['username'];
	$dbms='mysql';     //数据库类型
	$host='localhost'; //数据库主机名
	$dbName='yii2basic';    //使用的数据库
	$user='root';      //数据库连接用户名
	$pass='root';          //对应的密码
	$dsn="$dbms:host=$host;dbname=$dbName";
	try {
	    $pdo = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8';")); //初始化一个PDO对象
	    echo "连接成功<br/>";
		 try {  
	        $pdo->beginTransaction(); // 开启一个事务  
	        $row = null;  
	        $sql1="update `user` set  money=money-$money where username='$username'";
	        echo $sql1;
	        //die;
	        $row = $pdo->exec($sql1); // 执行第一个 SQL  
	        if (!$row)   
	            throw new PDOException('提示信息或执行动作'); // 如出现异常提示信息或执行动作  
	        $sql2="update `user` set  money=money+$money where username='$for'";
	        $row = $pdo->exec($sql2); // 执行第二个 SQL  
	        if (!$row)   
	            throw new PDOException('提示信息或执行动作');  
	        $pdo->commit();  
	    } catch (PDOException $e) {  
	        $pdo->rollback(); // 执行失败,事务回滚  
	        exit($e->getMessage());  
	    }  

		$dbh = null;
	} catch (PDOException $e) {
	    die ("Error!: " . $e->getMessage() . "<br/>");
	}

}else{
	echo "to login";
}

还有其他防御方法如:

    1、在请求地址中添加 token 并验证

    2、在 HTTP 头中自定义属性并验证

等这里不做具体详解;

详细了解:https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/

猜你喜欢

转载自blog.csdn.net/jiuyue9561/article/details/80670741