如何在静态网站上防御CSRF攻击?

15

我有一个静态网站,通过CDN提供服务,并通过AJAX与API通信。如何防止CSRF攻击?

由于我无法控制静态网站的服务方式,因此无法在某人加载我的静态网站时生成CSRF令牌(并将令牌插入表单或随AJAX请求发送)。我可以创建一个GET端点来检索令牌,但攻击者似乎可以简单地访问该端点并使用它提供的令牌?

在这种情况下,是否有一种有效的方法来预防CSRF攻击?


附加细节:身份验证是完全独立的。我想要CSRF保护的一些API请求是经过身份验证的端点,而其他一些请求是公共POST请求(但我想确认它们来自我的站点,而不是别人的)。


通过AJAX与API通信...没有涉及到服务器。但是,对于API来说,是需要有服务器的。这个服务器/ API不是你自己的吗? - Matt S
是的 - 我会在问题中澄清这一点。我假设CDN也有服务器?但我无法控制它们。我对API服务器拥有完全的控制权。 - Justin
4个回答

17
我可以创建一个GET端点来检索令牌,但似乎攻击者只需访问该端点并使用提供的令牌即可?
正确。 但是CSRF令牌并不是要保密的。 它们存在的唯一目的是确认按照一个用户期望的顺序执行操作(例如,表单提交仅在获取表单的GET请求后才会进行)。 即使在动态网站上,攻击者也可以提交自己的GET请求到页面,并从嵌入表单中的CSRF令牌中解析出它。
来自OWASP的信息:
CSRF是一种欺骗受害者提交恶意请求的攻击。 它继承了受害者的身份和权限,以代表受害者执行不希望执行的功能。
在页面加载时进行初始GET请求以获取新的令牌,然后使用该令牌提交请求执行操作是完全有效的。
如果您想确认发出请求的人的身份,则需要身份验证,这是与CSRF分开考虑的问题。

1
很好的观点。听起来像是CSRF令牌,即使在页面加载时嵌入HTML页面中,也不能完全防止跨站请求伪造,因为攻击者可以解析它并在POST请求中使用。提供令牌的专用GET端点肯定更容易被攻击者获取/使用,但它比根本不强制执行CSRF令牌提供更多的安全性。这样对吗?谢谢。 - Justin
1
是的,专用的GET请求至少可以确认用户没有被欺骗发送POST请求。 - Matt S
谢谢。这真是非常有帮助的。 - Justin
1
他们似乎混淆了身份验证和CSRF。并且他们假设每个用户都已经通过身份验证。但无论如何,由于您的网站是静态的,您只需在页面加载后使用AJAX来获取令牌即可妥协。如果每个AJAX请求也经过身份验证,则没有第三方可以获取令牌。由于您的一些POST是公开的,因此您必须允许公共GET请求进行CSRF。攻击者可以获取令牌,然后向您的API发起POST请求,但如果API是公开的,则您无法做其他事情。 - Matt S
我必须说我不同意这个建议。“它们只是存在于确认一个动作按照一个用户的期望顺序执行” - 不,它们是用来防御CSRF攻击的,如果它们没有起到作用,那么你做错了。 “即使在一个动态网站上,攻击者也可以提交自己的GET请求到一个页面,并解析出嵌入在表单中的CSRF令牌” - 这样令牌将与攻击者会话相关联而不是用户会话,并且它将失败,如果它在您的网站上没有失败,那么您正在做一些错误的事情。 - Alon Segal
显示剩余3条评论

0
我的解决方案如下:

客户端 [静态 HTML]

<script>
// Call script to GET Token and add to the form
fetch('https:/mysite/csrf.php')
.then(resp => resp.json())
.then(resp => {
    if (resp.token) {
        const csrf = document.createElement('input');
        csrf.name = "csrf";
        csrf.type = "hidden";
        csrf.value = resp.token;
        document.forms[0].appendChild(csrf);
    }
});
</script>

上述内容可以修改以针对现有的CSRF字段。我使用这个来添加到我的表单页面中。脚本假定页面上的第一个表单是目标,因此如果需要,这也需要进行更改。
在服务器上生成CSRF(使用PHP:假定> 7)
[CSRFTOKEN在配置文件中定义。示例]
define('CSRFTOKEN','__csrftoken');

服务器:

$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = $_SERVER['HTTP_REFERER'] ?? false;

// Check that script was called by page from same origin
// and generate token if valid. Save token in SESSION and
// return to client
$token = false;
if ($root_domain && 
    $referrer && 
    parse_url($referrer, PHP_URL_HOST) == $root_domain) {
  $token = bin2hex(random_bytes(16));
  $_SESSION[CSRFTOKEN] = $token;
}

header('Content-Type: application/json');
die(json_encode(['token' => $token]));

最终在处理表单的代码中。
session_start();

// Included for clarity - this would typically be in a config
define('CSRFTOKEN', '__csrftoken');

$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);

// Check submission was from same origin
if ($root_domain !== $referrer) {
    // Invalid attempt
    die();
}

// Extract and validate token
$token = $_POST[CSRFTOKEN] ?? false;
$sessionToken = $_SESSION[CSRFTOKEN] ?? false;
if (!empty($token) && $token === $sessionToken) {
  // Request is valid so process it
}

// Invalidate the token  
$_SESSION[CSRFTOKEN] = false;
unset($_SESSION[CSRFTOKEN]);

-1

对于此问题有非常好的解释,请查看
https://cloudunder.io/blog/csrf-token/

从我的理解来看,由于CORS限制,静态网站不会面临CSRF问题,如果我们添加了X-Requested-With标志。
这里还有一个问题我想要强调一下:

如何保护你的API,以便它可以被移动应用程序和静态站点调用?

由于API是公开暴露的,您希望确保只有允许的用户才能调用它。
我们可以在API服务层中添加一些检查来实现同样的目的。

1)针对AJAX请求(来自静态站点),检查请求的域,因此只有允许的站点可以访问它。
2)对于移动请求,请使用HMAC令牌,点击此处了解更多信息
http://googleweblight.com/i?u=http://www.9bitstudios.com/2013/07/hmac-rest-api-security/&hl=en-IN


2
-1 CORS限制不会防止CSRF攻击。CORS可以防止您读取请求的结果,但无法防止您发出请求。这是不可能的,因为CORS在响应标头中发送,因此浏览器在读取CORS数据之前必须生成响应。因此,恶意站点可以对更改状态的端点进行CSRF攻击,尽管存在CORS(例如bank.com/pay?amount=1000&to=Alice)。他们无法读取结果,但可以通过其他方式观察状态的变化-即Alice是否有1000美元在她的账户中。 - Jansky
@Jansky 使用自定义请求头是OWASP推荐的方法之一。尽管请求被发起,服务器将会检查自定义请求头。您只能使用XHR添加标头,而CORS则阻止其他方使用自定义请求头发出请求。 - Franz Wong
我不确定我理解了,你会在这个自定义请求头中放什么?除非你使用从你的域生成的Web令牌,否则没有任何防止恶意代理从其域添加特定自定义标头到客户端请求的方法。从你的域生成Web令牌,用户只有在通过CORS时才能读取它,他们使用令牌进行后续请求,你就知道他们是合法的,因为他们无法在没有通过CORS的情况下获得有效的请求令牌而不在你的域上。 - Jansky

-1

根据目前的写法,你的回答不够清晰。请编辑以添加更多细节,帮助其他人理解这如何回答所提出的问题。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接