Submeter #841998: CherryHQ cherry-studio 1.9.6 Authorization Bypass / Flow-Key Confusioninformação

TítuloCherryHQ cherry-studio 1.9.6 Authorization Bypass / Flow-Key Confusion
Descrição## Vulnerability Title Authorization Bypass via Hash-Key Confusion in CherryIN OAuth Flow Binding ## Affected Component `src/main/services/CherryINOAuthService.ts` and `src/preload/index.ts` Repository: https://github.com/CherryHQ/cherry-studio ## Summary An attacker with the ability to execute JavaScript in a Cherry Studio renderer that can access the CherryIN preload API and observe a valid OAuth callback can craft a security-distinct token exchange request that reuses another renderer's OAuth `state` key. This causes flow-key confusion, which leads to cross-renderer OAuth flow consumption and possible CherryIN API key disclosure. ## Technical Details The vulnerability occurs because the CherryIN OAuth service treats the OAuth `state` string as the sole identity for pending OAuth flows. The `state` value is used as an application-level key for retrieving the stored PKCE verifier and OAuth server configuration, but the key does not include the initiating IPC sender or window. **Where the Flow Key is Computed** In `src/main/services/CherryINOAuthService.ts`, pending flows are tracked globally in a `Map` keyed only by the `state` string: ```ts const pendingOAuthFlows = new Map<string, PendingOAuthFlow>() ``` When `startOAuthFlow` is called, the PKCE `codeVerifier` and a random 32-character `state` are mapped together, omitting any tracking of `event.sender` or window contexts: ```ts pendingOAuthFlows.set(state, { codeVerifier, oauthServer, apiHost: resolvedApiHost, timestamp: Date.now() }) ``` **Vulnerable Token Exchange Lookup** When exchanging the authorization code, the IPC handler checks `pendingOAuthFlows` using only the client-provided `state` parameter: ```ts public exchangeToken = async ( _: Electron.IpcMainInvokeEvent, code: string, state: string ): Promise<TokenExchangeResult> => { const flowData = pendingOAuthFlows.get(state) if (!flowData) { throw new CherryINOAuthServiceError('OAuth flow expired or not found') } pendingOAuthFlows.delete(state) // ... proceed to exchange token and return API keys to caller ... ``` Because the `IpcMainInvokeEvent` sender context (`_`) is ignored, any context within the Electron application that can trigger the exposed `cherryin:exchangeToken` preload IPC channel can intercept and consume a valid `state` lookup transaction initiated by a different window. ## Impact This vulnerability allows attackers to: - Complete or consume a CherryIN OAuth flow initiated by another renderer or window context. - Exfiltrate CherryIN API keys returned from the cross-sender `exchangeToken` handler response. - Cause a localized Denial of Service (DoS) by deleting and consuming the transient pending state before the legitimate initiator completes their flow. ## Proof of Concept The following Node.js test environment models the main-process mapping logic to demonstrate how a mismatching IPC sender can pull flow data and exfiltrate keys based on `state` tracking alone. ```js #!/usr/bin/env node const pendingOAuthFlows = new Map() async function startOAuthFlow(event, state) { pendingOAuthFlows.set(state, { codeVerifier: 'victim_pkce_verifier', oauthServer: '[https://open.cherryin.ai](https://open.cherryin.ai)', apiHost: '[https://open.cherryin.ai](https://open.cherryin.ai)', timestamp: Date.now() }) return { state, sender: event.sender.id } } async function exchangeToken(event, code, state) { const flowData = pendingOAuthFlows.get(state) if (!flowData) { throw new Error('OAuth flow expired or not found') } pendingOAuthFlows.delete(state) return { exchangedBySender: event.sender.id, code, usedCodeVerifier: flowData.codeVerifier, apiKeys: 'VICTIM_CHERRYIN_API_KEY' } } async function main() { const victimEvent = { sender: { id: 17, label: 'victim window' } } const attackerEvent = { sender: { id: 42, label: 'attacker renderer' } } const sharedState = 'NsXVzSt3Aiz5CchrVGUGY7OI0s5lPLKe' await startOAuthFlow(victimEvent, sharedState) const attackerResult = await exchangeToken(attackerEvent, 'AUTH_CODE_FOR_VICTIM', sharedState) console.log(JSON.stringify({ victimStartedFlowId: victimEvent.sender.id, attackerExchangedFlowId: attackerEvent.sender.id, attackerExfiltrationResult: attackerResult, vulnerable: attackerResult.exchangedBySender === 42 }, null, 2)) } main() ``` *Observed execution output: `"vulnerable": true`, confirming that a separate renderer context can pull down the active state record and intercept the token payloads.* ## Remediation Explicitly bind each pending CherryIN OAuth flow sequence to the originating IPC sender/window context. 1. **Verify IPC Sender Context**: Store `event.sender.id` or the unique `BrowserWindow` identifier within the `PendingOAuthFlow` structure during `startOAuthFlow`. 2. **Enforce Authorization Bounds**: Modify `exchangeToken` to validate that the caller's sender ID matches the recorded initiator before resolving the lookup: ```ts if (flowData.initiatingSenderId !== event.sender.id) { throw new CherryINOAuthServiceError('Unauthorized flow context invocation') } ``` 3. **Capability Tokens**: Alternatively, pass an unforgeable token unique to the initiating renderer frame that must be presented alongside the authorization code during the extraction workflow. ## References - Core OAuth Service state mapping logic: `src/main/services/CherryINOAuthService.ts` - Preload IPC interface exposure bindings: `src/preload/index.ts` - Global IPC channel registration definitions: `src/main/ipc.ts`
Fonte⚠️ https://github.com/CherryHQ/cherry-studio/issues/15411
Utilizador
 dem0000 (UID 98390)
Submissão29/05/2026 03h17 (há 1 mês)
Moderação28/06/2026 11h26 (1 month later)
EstadoAceite
Entrada VulDB374542 [CherryHQ cherry-studio até 1.9.7 CherryIN Preload API MemoryService.ts sha256 state Elevação de Privilégios]
Pontos20

Want to know what is going to be exploited?

We predict KEV entries!