DEV Community

Tianya School
Tianya School

Posted on

Front-End Security CSRF Attacks and Defenses

CSRF (Cross-Site Request Forgery) attacks are a security threat that allows attackers to perform unintended actions using a victim's identity. To defend against such attacks, front-end developers typically need to combine back-end validation with specific front-end strategies.

Token Validation

This is the most common defense mechanism, involving the generation of a CSRF token exchanged between the client and server.

Server-Side Token Generation

After a user logs in successfully, the server generates a CSRF token and stores it in the user’s session.

# Python example
from flask import Flask, session
app = Flask(__name__)

@app.route('/login')
def login():
    session['csrf_token'] = generate_csrf_token()
    return render_template('login.html', csrf_token=session['csrf_token'])
Enter fullscreen mode Exit fullscreen mode

Client-Side Token Usage

Embed the token in each protected form or include it as part of an HTTP header.

<!-- HTML example -->
<form action="/protected" method="POST">
  <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
  <!-- ...other form fields... -->
</form>
Enter fullscreen mode Exit fullscreen mode
// JavaScript example (using Fetch API)
fetch('/protected', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-CSRF-Token': window.csrfToken
  },
  body: formData
});
Enter fullscreen mode Exit fullscreen mode

Checking the Referer Header

Although not entirely reliable due to the possibility of browsers disabling or tampering with the Referer header, it can serve as a supplementary defense measure.

# Python example (Flask middleware)
from flask import request

@app.before_request
def check_referer():
    if request.method in ['POST', 'PUT', 'DELETE']:
        if not request.referrer or request.referrer != app.config['APP_BASE_URL']:
            return abort(403)
Enter fullscreen mode Exit fullscreen mode

Two-Factor Authentication

Require users to confirm sensitive operations with a secondary step, such as a pop-up or verification code.

SameSite Cookie Attribute

Set the SameSite attribute of cookies to Lax or Strict to restrict cross-site submissions. Lax allows cookies in top-level navigation, while Strict is more restrictive, allowing cookies only for same-site requests.

# Python example (using Flask-Session extension)
from flask_session import Session
from flask import Flask

app = Flask(__name__)
Session(app)
app.session_interface.cookie_samesite = 'Lax'
Enter fullscreen mode Exit fullscreen mode

POST-Only Interfaces

For sensitive operations, accept only POST requests and avoid GET requests, as GET requests are prone to misuse by browser bookmarks, link preloading, or third-party scripts.

In front-end code, defending against CSRF typically involves including a CSRF token in form submissions or Ajax requests. Validating the token’s authenticity on the back end is critical, as front-end defenses can be bypassed.

Example: Node.js + Express with CSRF Protection

// server.js
const express = require('express');
const bodyParser = require('body-parser');
const csrf = require('csurf');
const app = express();

// Use cookie-parser middleware to parse cookies
app.use(require('cookie-parser')());

// Use middleware to generate and inject CSRF tokens
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);

// Protect route
app.post('/protected', csrfProtection, (req, res) => {
  // Validate token and perform action
  if (req.csrfToken() === req.body.csrfToken) {
    // Safe operation
  } else {
    // Invalid CSRF token, return error
    res.status(403).send('CSRF token mismatch');
  }
});

// Respond with HTML page containing CSRF token
app.get('/', (req, res) => {
  res.send(`
    <form action="/protected" method="POST">
      <input type="hidden" name="csrfToken" value="${req.csrfToken()}">
      <button type="submit">Submit</button>
    </form>
  `);
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Using CSP (Content Security Policy)

Content Security Policy (CSP) is a web security strategy that reduces the risk of cross-site scripting (XSS) attacks by specifying which external resources can be loaded or executed. While CSP primarily targets XSS, it can indirectly help defend against CSRF by limiting the ability of third-party sites to initiate requests to your application when properly configured.

// Set CSP header in an Express application
app.use((req, res, next) => {
  res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'");
  next();
});
Enter fullscreen mode Exit fullscreen mode

Verifying the Origin Header

Although less reliable than CSRF tokens, checking the Origin header can serve as a supplementary defense in certain scenarios. If a request originates from an untrusted source, the server can reject it.

# Flask example
from flask import request

@app.before_request
def verify_origin():
    if request.method == 'POST':
        allowed_origins = ['https://your-trusted-origin.com']
        origin = request.headers.get('Origin')
        if origin not in allowed_origins:
            return 'Unauthorized', 401
Enter fullscreen mode Exit fullscreen mode

HSTS (HTTP Strict Transport Security)

Enforcing HTTPS-only communication with the server prevents man-in-the-middle attacks, reducing CSRF risks by making it harder for attackers to intercept or modify HTTPS communications.

# Flask example
@app.after_request
def apply_hsts(response):
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    return response
Enter fullscreen mode Exit fullscreen mode

Using a Web Application Firewall (WAF)

A Web Application Firewall provides an additional layer of protection by detecting and blocking malicious requests, including CSRF attacks. WAFs can be hardware devices, software services, or cloud-based solutions, typically using signature matching, behavioral analysis, or machine learning algorithms to identify and block attacks.

Rate Limiting Sensitive Operations

Limiting the frequency of sensitive operations can help prevent large-scale CSRF attacks. For example, restricting users to a few password changes or account deletions per minute.

// Simple rate limiting with JavaScript
let lastRequestTime = null;
const LIMIT = 60 * 1000; // One minute

function submitSensitiveAction() {
  const currentTime = Date.now();
  if (lastRequestTime && currentTime - lastRequestTime < LIMIT) {
    alert('Too many requests, please try again later.');
    return;
  }

  // Submit request...
  lastRequestTime = currentTime;
}
Enter fullscreen mode Exit fullscreen mode

Avoiding GET Requests for Sensitive Operations

While POST requests are generally considered safer than GET requests, GET requests may be unavoidable in some cases. In such scenarios, ensure URLs are not cached or stored in browser history to reduce CSRF risks. For example, use dynamically generated random parameters instead of exposing sensitive information directly.

# Django example
from django.http import HttpResponseBadRequest

def sensitive_get_view(request):
    if not request.GET.get('random_token') == request.session.get('random_token'):
        return HttpResponseBadRequest('Invalid token')

    # Perform sensitive operation...

    # Clear random token to prevent replay attacks
    request.session.pop('random_token', None)
Enter fullscreen mode Exit fullscreen mode

Using OAuth 2.0 or JWT

Modern web applications often use OAuth 2.0 or JSON Web Tokens (JWT) for authentication. These protocols provide built-in mechanisms to verify request origins and authorization states, reducing CSRF risks. However, proper implementation is critical, as these protocols have their own security challenges.

Keeping Frameworks and Libraries Updated

Ensure that front-end frameworks, back-end libraries, and server software are kept up to date to benefit from the latest security patches and fixes.

Secure Coding and Testing

Following secure coding best practices, such as input validation, output encoding, error handling, and logging, can help identify and fix potential vulnerabilities. Additionally, regular security testing, such as penetration testing and code reviews, ensures the application is secure before deployment.

Top comments (0)