Cross Site Request Forgery in Echo Go with Firestore
Cross Site Request Forgery in Echo Go with Firestore — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) occurs when an authenticated user is tricked into executing unwanted actions on a web application. In an Echo Go application that uses Firestore as a backend, CSRF risk arises when state-changing endpoints rely solely on session cookies for authentication and do not enforce anti-CSRF tokens. Because Firestore security rules validate authentication but typically do not enforce origin checks or CSRF-specific protections, a malicious site can craft requests that the browser automatically sends with valid credentials, leading to unauthorized Firestore operations.
Consider an Echo Go handler that updates a user document in Firestore based on URL parameters without verifying the request origin:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"cloud.google.com/go/firestore"
"context"
)
func UpdateUserEmail(c echo.Context) error {
ctx := c.Request().Context()
client, _ := firestore.NewClient(ctx, <project-id>)
defer client.Close()
email := c.QueryParam("email")
uid := c.Param("uid")
_, err := client.Collection("users").Doc(uid).Update(ctx, []firestore.Update{
{Path: "email", Value: email},
})
if err != nil {
return c.String(http.StatusInternalServerError, "update failed")
}
return c.NoContent(http.StatusOK)
}
If this endpoint is exposed via a cookie-based session and lacks a CSRF token, an attacker can embed an image or form on a malicious site that triggers the update when the victim is authenticated. For example:
<img src="https://api.example.com/users/[email protected]" />
When the victim’s browser sends the request, Firestore receives it with the victim’s credentials. Because Firestore rules may permit the update based on UID matching (e.g., allowing users to write to their own document), the attacker can change the victim’s email. This is a classic CSRF against Firestore via Echo Go: the server trusts the session cookie and does not validate the request origin or require a synchronizer token, and Firestore does not provide built-in CSRF protection.
Additionally, if the application serves authenticated pages that include JavaScript making cross-origin requests to the Echo Go endpoints, the combination of CORS misconfiguration and cookie-based authentication can further expose CSRF-like behavior. Firestore’s REST and SDKs do not implement anti-CSRF mechanisms, so the burden falls on the Go server to implement checks.
Firestore-Specific Remediation in Echo Go — concrete code fixes
To mitigate CSRF in Echo Go with Firestore, apply defense-in-depth: use anti-CSRF tokens for state-changing requests, enforce strict CORS, and design Firestore security rules to be least-privilege. Below are concrete code examples.
1. Anti-CSRF token with Echo middleware
Use a CSRF middleware to validate a token for mutating methods. The token should be cryptographically random, stored in a secure, httpOnly cookie, and required in a header for POST/PUT/DELETE requests.
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
CookieName: "_csrf",
CookieHTTPOnly: true,
CookieSecure: true,
TokenLength: 32,
})) // CSRF token set in cookie and expected in header X-CSRF-Token
// Public read endpoints may not require CSRF; write endpoints do
e.POST("/users/:uid/email"), updateUserEmailWithCSRF)
e.Logger.Fatal(e.Start(":8080"))
}
Then require the token in your handler:
func updateUserEmailWithCSRF(c echo.Context) error {
// Echo CSRF middleware validates automatically for methods that require it
// If validation fails, middleware returns 403 before reaching here
ctx := c.Request().Context()
client, _ := firestore.NewClient(ctx, <project-id>)
defer client.Close()
email := c.QueryParam("email")
uid := c.Param("uid")
_, err := client.Collection("users").Doc(uid).Update(ctx, []firestore.Update{
{Path: "email", Value: email},
})
if err != nil {
return c.String(http.StatusInternalServerError, "update failed")
}
return c.NoContent(http.StatusOK)
}
2. Strict CORS policy
Configure Echo’s CORS middleware to allow only trusted origins and to not allow credentials for cross-origin requests that don’t need them:
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://trusted.example.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, "X-CSRF-Token"},
ExposeHeaders: []string{"X-CSRF-Token"},
AllowCredentials: false, // set true only if you serve authenticated pages from same origin and need cookies
}))
3. Least-privilege Firestore security rules
Ensure Firestore rules require authentication and validate that the request user matches the document user. Do not allow broad writes based only on UID path parameters.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId
&& request.time < timestamp.datetime(2025, 1, 1); // example additional constraint
}
}
}
With these measures, Echo Go handlers remain protected against CSRF when interacting with Firestore, as the server validates origins and tokens, and Firestore rules enforce user-specific access.