Fix OAuth token-endpoint injection in the Adobe Sign and Zoho CRM samples - #562
Open
mohammad5525 wants to merge 1 commit into
Open
Conversation
…ples The Adobe Sign and Zoho CRM samples read a host/URL from an OAuth callback query parameter (api_access_point / accounts-server) and pass it straight into setTokenUrl(), then persist it. In a web-app or add-on deployment the callback is reachable with attacker-chosen parameters, so a single crafted GET makes the Apps Script server POST the token exchange — authorization code, client_id and client_secret — to an arbitrary host, and every later token refresh is sent there as well. In the Adobe Sign sample the second setPropertyStore call additionally moves the stored state into shared ScriptProperties, so one visitor's crafted authorization persists the attacker endpoint for all other users of the deployment. This change: - Validates the api_access_point / accounts-server parameter against the vendor's own domains (Adobe: adobe.com / adobesign.com / echosign.com; Zoho: accounts.zoho.*) and rejects anything else before it reaches setTokenUrl() or storage. - Removes the second setPropertyStore call in the Adobe Sign sample so tokens stay in per-user properties. Verified against the live attack chain from the report that motivated this fix (Google VRP iss.ee/551231684): after the change the crafted callback is rejected (returns "Denied.") without any outbound token request, while a legitimate vendor access point continues to work.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Both samples read a host/URL from an OAuth callback query parameter (
api_access_pointinsamples/AdobeSign.gs,accounts-serverinsamples/ZohoCRM.gs) and pass it straight intosetTokenUrl(), then persist it into the service storage.In a web-app or add-on deployment the callback URL is reachable with attacker-chosen parameters, so a single crafted GET (no valid state required —
Service_.handleCallbacknever checksstate) makes Google's Apps Script server POST the token exchange —code,client_id,client_secret— to the attacker's host. The value is then persisted, so every later token refresh is also delivered to the attacker. In the Adobe Sign sample, the secondsetPropertyStorecall moves the stored state into sharedScriptProperties, which extends the poisoning across every user of the deployment: one anonymous crafted request, and all subsequent visitors run with the attacker-persisted token and host.I demonstrated the full chain live on a deployment of the unmodified sample (credential POST captured on an external listener; attacker-issued token accepted and persisted; an anonymous third-party visitor shown running on the attacker state). Report that motivated this fix: Google VRP iss.ee/551231684 (closed under the OT2/OT3 tier policy, which is why I'm following up with a fix here as suggested in the reply).
The changes
samples/AdobeSign.gs/samples/ZohoCRM.gs: validate the callback parameter against the vendor's own domains (Adobe:adobe.com/adobesign.com/echosign.com; Zoho:accounts.zoho.*) before it reachessetTokenUrl()or storage; anything else returnsDenied.with no outbound request.samples/AdobeSign.gs: drop the secondsetPropertyStore(PropertiesService.getScriptProperties())call so tokens stay in per-user properties instead of the deployment-wide shared store.Verification
Re-ran the attack chain against the patched sample: the crafted callback is now rejected (
Denied.) with no token request leaving the server, while a legitimate vendor access point continues to flow through unchanged.A possible follow-up (out of scope for this minimal fix):
Service_.handleCallbackcould validate thestateparameter so callbacks can't be replayed with an arbitrary state.