跨站请求伪造(CSRF,Cross-Site Request Forgery)是一种网络攻击方式,它允许攻击者在用户不知情的情况下,以用户的名义执行非授权的命令。这种攻击利用了用户已经验证的身份和信任关系,来执行恶意操作。

一、工作原理

  1. 用户登录:用户登录到受信任的网站A,并在浏览器中保留了登录状态(例如,通过Cookies)。
  2. 诱导点击:攻击者诱使该用户访问恶意网站B,网站B包含了一个向网站A发起请求的操作(如表单提交)。
  3. 自动请求:由于用户已经在网站A中登录,浏览器会自动附带用户在网站A的认证信息(如Cookies)发起请求。如果网站A没有适当的防护措施,这个请求就会被视为用户自愿发起的,从而执行攻击者预设的操作。

二、防御措施

2.1 使用CSRF令牌

在表单或者AJAX请求中使用一个随机生成的令牌(CSRF Token),服务器验证请求中的令牌是否有效。由于攻击者无法获取这个令牌,因此无法构造有效的请求。
使用CSRF令牌(CSRF Token)是防御跨站请求伪造(CSRF)攻击的一种常用方法。以下是一个简单的示例,展示了如何在Web应用中实现CSRF保护机制。

2.1.1 假设场景

假设我们有一个简单的表单,用户可以通过它来更新自己的个人信息。我们将使用CSRF令牌来确保只有真正的用户可以提交这个表单。

2.1.2 后端实现(以Python Flask为例)

首先,我们需要在后端生成一个CSRF令牌,并将其发送到前端页面。

from flask import Flask, render_template, request, session
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)  # 用于安全地签名session

@app.route('/profile', methods=['GET'])
def profile():
    # 生成CSRF令牌
    csrf_token = os.urandom(16).hex()
    # 将CSRF令牌存储在session中
    session['csrf_token'] = csrf_token
    # 将CSRF令牌发送到前端
    return render_template('profile.html', csrf_token=csrf_token)

@app.route('/update-profile', methods=['POST'])
def update_profile():
    # 从表单和session中获取CSRF令牌
    form_csrf_token = request.form.get('csrf_token')
    session_csrf_token = session.get('csrf_token')
    
    # 验证CSRF令牌
    if not form_csrf_token or form_csrf_token != session_csrf_token:
        return "CSRF token mismatch", 403
    
    # 处理更新逻辑...
    return "Profile updated successfully"
2.1.3 前端实现

在前端页面(profile.html),我们需要确保表单包含CSRF令牌作为一个隐藏字段。

<form action="/update-profile" method="post">
    <!-- 其他表单字段 -->
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <button type="submit">Update Profile</button>
</form>
2.1.4 工作流程
  1. 当用户访问/profile页面时,后端生成一个CSRF令牌,并将其存储在用户的session中,同时发送到前端页面。
  2. 用户填写表单并提交时,表单中包含的CSRF令牌也会被发送到后端。
  3. 后端接收到表单提交的请求后,会从请求中提取CSRF令牌,并将其与session中存储的令牌进行比较。
  4. 如果两个令牌匹配,请求被视为合法,后端继续处理表单提交的数据;如果不匹配,请求被拒绝,以防止CSRF攻击。
2.1.5 注意
  • CSRF令牌应该是随机生成的,对每个用户和会话都是唯一的。
  • 在用户完成表单提交后,应该重新生成CSRF令牌,以防止令牌泄露。
  • 对于敏感操作(如密码更改、个人信息更新等),使用CSRF令牌是一种有效的安全措施。

2.2 检查Referer头

服务器可以验证HTTP请求头中的Referer字段,以确保请求是从受信任的域名发起的。但这种方法并不完全可靠,因为Referer头可以被禁用或伪造。
检查Referer头是一种简单的方法,用于防御跨站请求伪造(CSRF)攻击。Referer头部分包含了发起HTTP请求的页面的地址。通过验证这个地址,服务器可以判断请求是否来自于一个可信的源。下面是一个使用Python Flask框架实现Referer头检查的示例:

2.2.1 Python Flask 示例

假设我们有一个敏感操作,比如用户信息的更新,我们希望确保这个操作只能从我们的网站内部发起。

from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/update-info', methods=['POST'])
def update_info():
    # 获取请求的Referer头
    referer_header = request.headers.get('Referer')
    
    # 检查Referer头是否存在,且是否来自我们的网站
    if not referer_header or not referer_header.startswith('https://www.ourwebsite.com'):
        abort(403)  # 如果不是,返回403禁止访问
    
    # 处理更新信息的逻辑...
    return "Information updated successfully"

在这个示例中,当/update-info路由接收到POST请求时,它首先检查Referer头。如果这个头部不存在,或者它的值不是以我们网站的域名https://www.ourwebsite.com开始的,那么请求将被拒绝,并返回HTTP状态码403,表示禁止访问。

2.2.2 注意事项
  • 安全性:虽然检查Referer头可以提供一定程度的安全保护,但它并不是完全可靠的。用户的浏览器可能被配置为不发送Referer头,或者攻击者可能通过某些技术手段伪造这个头部。
  • 隐私:出于隐私考虑,某些浏览器或用户可能选择禁用Referer头。
  • 备选方案:因此,虽然检查Referer头可以作为CSRF防护的一部分措施,但最好与其他防御机制(如CSRF令牌)结合使用,以提供更全面的安全保护。

总的来说,检查Referer头是一种简单的防御CSRF攻击的方法,但应该与其他安全措施结合使用,以确保Web应用的安全。

2.3 使用自定义请求头

由于跨站请求通常无法携带自定义头信息,因此检查请求中是否包含自定义头可以作为一种防御手段。
使用自定义请求头是一种防御跨站请求伪造(CSRF)攻击的技术。这种方法的基本思想是在客户端的请求中添加一个自定义的HTTP头部(Header),服务器端检查这个头部以验证请求的合法性。由于跨站请求通常无法设置自定义头部,这为服务器提供了一种简单的验证机制。

2.3.1 实现步骤
2.3.1.1 客户端(JavaScript示例)

在发送AJAX请求时,添加一个自定义的HTTP头部。例如,使用X-Requested-With头部,这是一个常用的约定,用于标识AJAX请求。

fetch('/api/update-profile', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        // 添加自定义请求头
        'X-Requested-With': 'XMLHttpRequest'
    },
    body: JSON.stringify({
        username: 'newUsername'
    })
}).then(response => {
    if (response.ok) {
        console.log('Profile updated successfully');
    }
}).catch(error => {
    console.error('Error updating profile:', error);
});
2.3.1.2 服务器端(Python Flask示例)

在服务器端,检查每个请求是否包含了这个自定义的头部。如果请求不包含这个头部,或者头部的值不符合预期,服务器可以拒绝这个请求。

from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/api/update-profile', methods=['POST'])
def update_profile():
    # 检查自定义请求头
    if request.headers.get('X-Requested-With') != 'XMLHttpRequest':
        abort(403)  # 如果请求头不匹配,返回403 Forbidden
    
    # 处理更新逻辑...
    return "Profile updated successfully"
2.3.2 注意事项
  • 安全性:虽然使用自定义请求头可以提高安全性,但它不应该是唯一的防御措施。最好将其与其他安全机制(如CSRF令牌)结合使用。
  • 兼容性:确保客户端能够设置自定义头部,并且服务器能够正确地处理这些头部。某些浏览器或网络环境可能会限制或修改HTTP头部。
  • CORS策略:如果你的Web应用涉及跨域请求,需要确保CORS(跨源资源共享)策略正确配置,以允许自定义头部。

使用自定义请求头是一种相对简单的防御CSRF攻击的方法,它依赖于跨站请求的限制。然而,为了提供更强的安全保障,建议采用多种防御策略的组合。

2.4 双重Cookie验证

在用户登录时生成一个随机值,存储在Cookie中,并要求所有表单请求或AJAX请求都携带这个值作为参数。由于第三方网站无法读取这个Cookie,因此无法伪造请求。

双重Cookie验证是一种防御跨站请求伪造(CSRF)的技术,它利用了跨站请求无法读取或设置目标站点Cookie的特性。这种方法不需要在服务器端存储CSRF令牌的状态,因此对于一些无状态的应用场景特别有用。下面是一个具体的实现示例:

2.4.1 实现步骤
2.4.1.1 设置CSRF令牌Cookie

当用户访问你的网站并进行登录或者开始一个会话时,服务器生成一个CSRF令牌,并将这个令牌作为Cookie发送给用户的浏览器。

from flask import Flask, make_response, request, abort
import os

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    # 登录逻辑...
    
    # 生成CSRF令牌
    csrf_token = os.urandom(16).hex()
    
    # 创建响应对象
    response = make_response("Logged in successfully.")
    # 设置CSRF令牌Cookie
    response.set_cookie('csrf_token', csrf_token)
    
    return response
2.4.1.2 在客户端请求中包含CSRF令牌

在客户端,每次发送请求时,从Cookie中读取CSRF令牌,并将这个令牌以请求头或请求体的形式包含在请求中。

function sendRequest() {
    // 从Cookie中获取CSRF令牌
    const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrf_token=')).split('=')[1];
    
    fetch('/api/update-profile', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            // 将CSRF令牌包含在请求头中
            'X-CSRF-Token': csrfToken
        },
        body: JSON.stringify({
            username: 'newUsername'
        })
    }).then(response => {
        if (response.ok) {
            console.log('Profile updated successfully');
        }
    }).catch(error => {
        console.error('Error updating profile:', error);
    });
}
2.4.1.3 服务器验证CSRF令牌

服务器在接收到请求时,比较请求中的CSRF令牌和Cookie中的CSRF令牌是否一致。

@app.route('/api/update-profile', methods=['POST'])
def update_profile():
    # 从请求头和Cookie中获取CSRF令牌
    request_csrf_token = request.headers.get('X-CSRF-Token')
    cookie_csrf_token = request.cookies.get('csrf_token')
    
    # 验证CSRF令牌
    if not request_csrf_token or request_csrf_token != cookie_csrf_token:
        abort(403)  # 如果令牌不匹配,返回403 Forbidden
    
    # 处理更新逻辑...
    return "Profile updated successfully"
2.4.2 注意事项
  • 确保CSRF令牌足够随机,以防止攻击者猜测令牌值。
  • 设置Cookie时,考虑使用HttpOnlySecure标志,以增强安全性。
    • HttpOnly标志用于限制Cookies只能通过HTTP(S)请求被访问,而不能通过客户端脚本(如JavaScript)直接访问。
    • Secure标志用于指示Cookies只能通过安全的HTTPS连接发送给服务器。
  • 双重Cookie验证方法依赖于浏览器的同源策略,但并不是所有的CSRF攻击场景都能被这种方法防御。因此,最好将其与其他安全措施(如验证Referer头或使用CSRF令牌)结合使用。

双重Cookie验证提供了一种相对简单的CSRF防护机制,特别适用于需要无状态验证的应用场景。然而,它并不是万能的,开发者应根据具体的应用需求和安全要求,选择合适的防护策略。

2.5 使用SameSite Cookie属性

设置Cookies的SameSite属性可以限制Cookies只能在同一站点的请求中发送,这有助于减少CSRF攻击的风险。

SameSite Cookie属性是一种防止跨站请求伪造(CSRF)攻击的机制,它允许服务器指定某个Cookie不应该随着来自第三方站点的请求一起发送。SameSite属性可以有三个值:StrictLaxNone

  • Strict:Cookie只会在浏览器直接访问原网站时发送。即使用户通过第三方网站的链接点击访问原网站,Cookie也不会被发送。
  • Lax:相对宽松,Cookie在跨站子请求(如图片或iframe)中不会被发送,但在用户从第三方网站导航到原网站时(例如通过链接点击),Cookie会被发送。
  • None:Cookie将在所有请求中被发送,但必须同时设置Secure属性,即只能在HTTPS协议下使用。
2.5.1 如何设置SameSite属性
2.5.1.1 Python Flask示例

在Flask中设置SameSite属性:

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/set-cookie')
def set_cookie():
    response = make_response("Cookie is set")
    # 设置SameSite属性为Lax
    response.set_cookie('key', 'value', samesite='Lax')
    return response
2.5.1.2 Node.js Express示例

在Express中设置SameSite属性:

const express = require('express');
const app = express();

app.get('/set-cookie', (req, res) => {
    res.cookie('key', 'value', { sameSite: 'lax' });
    res.send('Cookie is set');
});
2.5.1.3 PHP示例

在PHP中设置SameSite属性:

setcookie('key', 'value', [
    'expires' => time() + 3600, // 设置过期时间
    'path' => '/', // 设置路径
    'domain' => 'example.com', // 设置域
    'secure' => true, // 启用Secure标志
    'httponly' => true, // 启用HttpOnly标志
    'samesite' => 'Lax', // 设置SameSite属性为Lax
]);
2.5.1 注意事项
  • SameSite=None必须与Secure一起使用,这意味着Cookie只能在HTTPS连接中发送。
  • 不是所有浏览器都支持SameSite属性,尤其是一些旧版本的浏览器。在这些浏览器中,设置SameSite属性可能没有任何效果。
  • 在实现跨域认证或跨站资源共享(CORS)时,需要仔细考虑SameSite属性的设置,以避免不必要的访问限制。

通过合理使用SameSite属性,可以有效减少CSRF攻击的风险,增强Web应用的安全性。

三、结论

CSRF是一种严重的安全威胁,它利用了Web应用中的信任机制。通过实施上述防御措施,开发者可以显著降低应用遭受CSRF攻击的风险。在设计Web应用和API时,始终考虑到CSRF防护是非常重要的。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐