We’ve seen multiple creative ways to store API tokens, credentials, and sessions in Salesforce, but across the board, the way many apps handle DocuSign setup is most noteworthy. In fact, what we learned about the DocuSign blast radius during our engagements is why we started to deeply care about why in the world would you store private RSA keys in a metadata record and go on to think it’s ok to permission any Joe User to read it. I’ll repeat this again for those that don’t know: USERS CAN READ METADATA IF METADATA SECURITY IS OFF. Furthermore, any user can of course read what they are permissioned to read. DISABLING API ACCESS IS NOT A SECURITY CONTROL. The UI API is more than capable of replicating the API Endpoints, it just takes a few more steps reading the wall of text URL Encoded Aura JSON Fields.
DocuSign is a widely-used electronic signature platform that allows users to sign, send, and manage documents digitally. It streamlines the signing process compared to traditional paper. Integrating DocuSign with Salesforce just makes sense, signing documents is a core component of the customer vendor relationship. This connection enables seamless contract and document management directly within Salesforce, facilitating quicker deal closures and improved customer interactions. A common design pattern for this integration is to provision an integration key, an RSA Private Key, and an admin user to establish the session under for this service. You can see where this is going. The private key is crucial to creating a JWT assertion. With the administrator’s private key, an attacker could access all data in the DocuSign account, again, since they are an admin, and even establish sessions as other users with the same integration/private keys.
From the docusign documentation we can learn how to use the disclosed secrets to get access token with JWT Grant:
- ISS Parameter = Integration Key
- SUB Parameter = Admin User Id
- Signature = RSA Key Pair
There’s a bunch of libraries you can use to programmatically generate this, but since we just want this manually, I recommend https://jwt.io or https://github.com/deneyed/docusigh
Just creating the assertion is what we are after.
So what do you do once you have a session?
The first thing you want to do is continue following the documentation. I recommend /oauth/userinfo which will give you some critical data on the current user and base URL you’ll be using for the rest of your API journey.
Perhaps you want to list users: /restapi/v2.1/accounts/acountid/users
What’s nice is the api even tells you how to paginate through the records, which is a breath of fresh air for a simple hacker like me. At this point, I’m at the edge of my seat. If I’m an admin, can I impersonate another user to sign documents? Like the CEO? Yes we can! Just repeat the auth flow with his userId as SUB and you’re in! Of course, you might need to verify if the app has been granted permission to everyone first.
Lastly, I’ll go over reading envelopes, because this is where the juicy contracts, PII, and confidential stuff is sitting. /restapi/v2.1/accounts/acountid/envelopes?from_date=2024-01-01
Excellent, thanks to the nice features of their API we can list envelopes by date or search string and from there, the record gives us more links to send GET requests for things like the signer/sender IP, emails, and of course the document itself!
For remediation we have a few recommendations:
- Rotate the private key immediately
- Upgrade the Salesforce integration
- Revoke Read permissions to the record
- Analyze activity logs for anomalous activity
You may want to check out our tool AuraQL which can help analyze your org for these kinds of findings, it is important to remember that even your custom apps can expose you to risk if your permission boundary isn’t tightly controlled.