Understanding CSRF vulnerabilities

In this article, we will explore how CSRF vulnerabilities work and how to prevent them
Friday, September 12, 2025

📘 What is CSRF vulnerability?

CSRF is a type of attack where a malicious site tricks a user’s browser into making an unwanted request to a different site where the user is authenticated. It exploits the trust that a site has in the user’s browser.

Imagine you’re logged into your bank account in one tab. In another tab, you visit a malicious site that silently sends a request to your bank to transfer money, using your credentials. That’s CSRF.

🧪 Demonstration

Here’s a simplified vulnerable Go web app:

package main
import (
"fmt"
"net/http"
)
func transferHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
amount := r.FormValue("amount")
to := r.FormValue("to")
fmt.Fprintf(w, "Transferred %s to %s", amount, to)
} else {
w.Write([]byte(`
<form method="POST" action="/transfer">
<input name="amount">
<input name="to">
<input type="submit" value="Transfer">
</form>
`))
}
}
func main() {
http.HandleFunc("/transfer", transferHandler)
http.ListenAndServe(":8080", nil)
}

This app blindly accepts POST requests without verifying the origin or user intent.

💣 Exploiting the vulnerability

An attacker could craft a malicious HTML page like this:

<form action="http://victim.com/transfer" method="POST">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="to" value="attacker">
<input type="submit" value="Click me for a surprise!">
</form>
<script>document.forms[0].submit();</script>

If the victim is logged into victim.com, this form auto-submits and transfers money to the attacker—without the user knowing.

🛡️ How to prevent CSRF attacks

✅ Use CSRF Tokens

Generate a unique token per user session and embed it in forms. On form submission, validate the token against the one stored in the session :

// Generate token
func generateCSRFToken() string {
b := make([]byte, 32)
rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
}

Then, we pass the CSRF_TOKEN in the form. The attacker should not be able to guess the CSRF_TOKEN of the user.

<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="{{ .CSRF_TOKEN }}">
<input name="amount">
<input name="to">
<input type="submit" value="Transfer">
</form>

On the server side, verify that the submitted token matches the one stored in the user’s session. This ensures the request is legitimate.

Modern framework include this functionality by default:

✅ Use SameSite Cookies

Set cookies with SameSite=Strict or Lax to prevent cross-origin requests.

http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: "abc123",
SameSite: http.SameSiteStrictMode, // or http.SameSiteLaxMode. Don't use SameSiteNoneMode
Secure: true, // ensures cookie is sent over HTTPS
HttpOnly: true, // prevents access from JavaScript
Path: "/",
})

The SameSite property on cookie can have multiple values:

  • Strict: cookie is only sent in a first-party context (i.e., same site). Best for sensitive cookies.
  • Lax: cookie is sent with top-level navigations and GET requests initiated by third-party sites.
  • None: cookie is sent in all contexts, including cross-origin.

Use SameSite=Strict for maximum protection, but be aware it may block legitimate cross-site usage (e.g., from third-party services). SameSite=Lax offers a balance by allowing top-level navigation while still blocking most cross-origin requests.


Recommended articles