Data Theft in Salesforce: Manipulating Public Links

Varonis Threat Labs uncovered a vulnerability in Salesforce's public link feature that threat actors could exploit to retrieve sensitive data.
Nitay Bachrach
8 min read
Last updated September 16, 2024
Manipulating Public links in Salesforce, original threat research by Varonis

Varonis Threat Labs uncovered a vulnerability in Salesforce's public link feature that threat actors could exploit to retrieve sensitive data.   

By manipulating the API calls sent to the undocumented Salesforce Aura API — combined with SOQL subqueries — hackers could commit a blind SOQL injection attack to retrieve customer information, including PII.   

Varonis Threat Labs informed Salesforce of the vulnerability January 4, 2024. In February 2024, Salesforce patched the vulnerability for blind SOQL injection. Given the severity and the potential of this exploit to expose and leak sensitive information, Varonis researchers intentionally waited to release their findings. 

The vulnerability we identified applied to virtually any public link generated by Salesforce, making the potential impact widely detrimental. Because of the ubiquitous nature of public sharing links, most — if not all — Salesforce environments would likely have been vulnerable to some level of exposure, which could lead to data theft or leakage.   

Varonis recommends that organizations revisit the Salesforce Permission Sets granted to users to limit the creation of public links, remediate them where feasible, and monitor access activity. 

In this blog, we’ll explain how Salesforce public links work, how we discovered this vulnerability, and how attackers could exploit it to retrieve sensitive data.

What are public links in Salesforce? 

Salesforce public links allow you to share files or folders with people inside or outside your organization without creating user accounts for them. Within Salesforce, files shared via public links can also be attached (or connected) to other records such as accounts, contacts, leads, and more.  

How do links work? 

When you create a public link for a file, Salesforce generates a URL that can be shared with anyone inside or outside the organization. However, the URL is not a direct link to the file.

Instead, the URL leads to a small Salesforce Lightning application, which will verify a password (if necessary), retrieve the file, and, in some cases, show the file in the browser preview.  

Salesforce Lightning uses Aura components for front-end elements. Those components send requests to Aura endpoints to perform server-side actions such as data retrieval. In effect, when a public link is created, a new Aura endpoint — accessible to unauthenticated users — is created. Users can communicate directly with these endpoints using the undocumented Aura API as unauthenticated users.

People who click on public links from Salesforce are a special inaccessible “hidden external user.” This user has a restricted set of permissions required to access the file. An admin cannot control or modify the permissions of the “hidden external user” because it’s hidden and inaccessible. 

How do public links request information?  

When a user clicks on a Salesforce public link, the Lightning app requests information about the public link, using the following method and parameters:

	
		

serviceComponent://ui.content.components.forceContent.contentDistributionViewer.

ContentDistributionViewerController

/ACTION$getContentDistributionInfo 

There are three parts to a method.  

  • Namespace: This prefix determines the location or package of the controller whose method is being called. In our case, the namespace is “ui.content.components.forceContent.contentDistributionViewer”.  
  • Controller class: This is the name of the controller or Apex class that contains the method. Here the controller class is ContentDistributionViewerController.
  • Action: This is the name of the specific method we want to call. Here, the method is getContentDistributionInfo.  

The following parameters are included with the request:  

  • The ID of the link record: This is automatically received earlier in the JavaScript code when the Lightning app is loaded. However, the link record ID can also be directly inferred from the link itself.
  • IsInternalView: This is an empty string.
  • dpt: This value is required if the link is protected using a password. If there’s no password, an empty string is provided.

This method will return the IDs of the specific file (ContentDocument) and file version (ContentVersion) shared using the public link, along with more information, such as the file type, version number, whether a preview is available, and more. 

Inspecting the request and response to Salesforce when using a public link through Burp Intruder reveals a successful call to an Aura endpoint.

1 its aura

Inspecting the request and response to Salesforce when using a public link through Burp Intruder reveals a successful call to an Aura endpoint.

Abusing the Aura endpoint and API

Having established that public links create Aura endpoints, we sought to find ways to exploit that access.

We covered Aura exploits before in our research on abusing Salesforce communities and ghost sites.    

We tried abusing the Aura endpoint behind a public link to access more data from the Salesforce environment, including data of records associated with the link.

We started with the most basic Aura method: getting the config data. Surprisingly, the getConfigData method which usually returns some information, returned an “Unable to Process Request” error. 

Altering the method to use getConfigData typically returns useful information. In this case, the getConfigData method returned an error message.

2 error what

Altering the method to use getConfigData typically returns useful information. In this case, the getConfigData method returned an error message.

We tried other Aura methods but received the same error. We revised our methods and checked the encoding multiple times, attempting to locate the origin of the error, until a researcher noticed our query parameters and method did not match. Changing the query parameters proved to be the breakthrough needed.

What are query parameters?   

Query parameters provide information to web servers when making requests.

In typical scenarios, like a user navigating a Lightning interface through a web browser, Salesforce communicates to the server by using query parameters to indicate the methods included in the request.

Usually, Aura endpoints are not affected if query parameters and methods do not match. However, given the errors received, we sought to test if forcing the methods and query parameters to match would work.

In Salesforce Aura, query parameters are based on the method used, with three parts separated by a dot(.), and a numeric value such as 1. The name of the query parameters initially provided is:  

	
		

/ui-content-components-forceContent-contentDistributionViewer.

ContentDistributionViewer.getContentDistributionInfo=1 

The query parameter has the same three parts as the method above, but with a different formatting. The are three parts to the query parameter. 

  • Namespace: This prefix helps to define what method is being called and changes depending on the method used. For service component methods, the namespace is all the parts that lead to the controller, with a hyphen instead of a dot. So, in our case: “ui-content-components-forceContent-ContentDistributionViewer".
  • Controller class: This is the name of the controller or Apex class. When used in a query, the word “Controller” is dropped, thus ContentDistributionViewerController is written as ContentDistributionViewer.
  • Action: This is the specific action being called. In this case, we’re requesting information about content. When in use, ACTION$, is dropped and will display as getContentDistributionInfo.

We attempted to call getConfigData again, but this time with a new query parameter:  

	
		

ui-force-components-controllers-hostConfig.HostConfig.getConfigData

This produced a successful response. 

Forcing the query parameters (line 1, left side) to match the method (line 21, left side) produces a successful response (line 19, right side).

3 there-we-go

Forcing the query parameters (line 1, left side) to match the method (line 21, left side) produces a successful response (line 19, right side).

Next, we tried listing ContentDocument records. This produced an error message. 

Despite aligning the query parameters with a new method, the introduction of new parameters produces an error.

4 we cant list

Despite aligning the query parameters with a new method, the introduction of new parameters produces an error.

We concluded that there are two reasons why an action could be blocked: 

  • The method itself is blocked 
  • The method is allowed, but not with the provided parameters 

To continue the research, we needed to distinguish between the two potential causes for an action to be blocked.

We devised a test to determine which methods were valid. By specifying a query parameter (which typically matches the method used) but keeping the actions list empty, there is only one variable being tested — the method itself.  

If a method is valid, then submitting a query parameter with an empty action list should return an Aura response with no actions. We sent a request without actions, and as expected we received an Aura response:

By submitting query parameters with empty actions, there aren’t any variables to cause an error. Therefore, if a query parameter with an empty action returns a positive result, the method is allowed.

5 works with empty actions

By submitting query parameters with empty actions, there aren’t any variables to cause an error. Therefore, if a query parameter with an empty action returns a positive result, the method is allowed.

But when we tried submitting query parameters with empty actions using a forbidden method, we received an error: 

By submitting query parameters with empty actions, there aren’t any variables to cause an error. Therefore, if a query parameter with an empty action returns an error, the method is forbidden.

6 blocked with empty actions

By submitting query parameters with empty actions, there aren’t any variables to cause an error. Therefore, if a query parameter with an empty action returns an error, the method is forbidden.

With this test, we can use the query parameters to determine whether the method itself is forbidden, or if the problem is the parameters.

To quickly test all the combinations, we used Burp Intruder, a Burp Suite tool that lets users send many requests simultaneously and observe the response. 

By changing the query parameters, we can create payloads to test viable methods.

7 intruder request

By changing the query parameters, we can create payloads to test viable methods.

We created and tested a series of payloads. Creating our test payloads required us to assemble and correctly format a list of almost 500 Aura methods, that we at Varonis Threat Labs uncovered during our deep dive into Salesforce security and potential threat vectors.

Burp Intruder allows researchers to test hundreds of payloads quickly.

8 intruder payload

Burp Intruder allows researchers to test hundreds of payloads quickly.

We ended up with a very short list of allowed methods: 

Burp Intruder displays a short list of valid methods after delivering the test payload.

9 intruder results

Burp Intruder displays a short list of valid methods after delivering the test payload.

One method that stood out is getRecord, specifically: 

	
		

serviceComponent://

ui.force.components.controllers.recordGlobalValueProvider.RecordGvpController

/ACTION$getRecord

The method getRecord is very powerful. It allows a user to specify the fields they want to retrieve, including related entities. The getRecord method works using SOQL and it builds the query using the provided fields.

We can use those fields to inject subqueries to retrieve more data but cannot use the fields to see the results of the subquery, because that method does not support subqueries. Instead, any response the subquery receives is displayed as an error message, forcing us to make a blind attack. 

SOQL subquery blind attack 

Basic SOQL queries look a lot like SQL queries, but they are not the same. One key difference is how their table relationships work. In SQL, the JOIN clause is used to query multiple tables simultaneously based on a shared value(s), but SOQL does not support JOIN. Instead, SOQL uses a subquery.  

For example, files — or ContentDocument records — have related identities. One of them is the owner, but files can also be attached to other records such as accounts, contracts, and more. Files have a many-to-many relationship and a table called ContentDocumentLink handles those relationships. If we wanted the name of a user attached to a ContentDocument in SQL, the query would look something like this: 

	
		

SELECT ContentDocument.ID, User.Name

FROM ContentDocument

JOIN ContentDocumentLink ON ContentDocumentLink.ContentDocumentID = ContentDocument.ID

JOIN User ON User.ID =  ContentDocumentLink.LinkedEntityID 

 

But this is not SQL; it’s SOQL. So instead, the subquery would be built like this: 

	
		

SELECT ID, 

(SELECT LinkedEntity.Name FROM ContentDocumentLinks WHERE LinkedEntity.Type = 'User') 

FROM ContentDocument

In this example, ContentDocumentLinks is the name of the relationship between ContentDocumentLink and ContentDocument. In fact, there are two types of subqueries — one in SELECT and one in WHERE. The main difference is the WHERE subqueries query tables whereas SELECT subqueries query relationships. This difference is important when abusing SOQL-based vulnerabilities.  

After misconfigurations, SELECT and WHERE subquery SOQL injections make up the most common attack vectors used to abuse Salesforce-based apps.

In our case, we can insert a SELECT subquery. SELECT subqueries are a powerful tool, but our use case is quite simple. Let’s see how a subquery might let us retrieve data that's typically restricted.  

As mentioned before, calling a subquery directly leads to an internal error:

Our subquery request will only return an error message if there is a result. In this blind attack, an error message is actually a positive result.

13 getrecord subquery error

Our subquery request will only return an error message if there is a result. In this blind attack, an error message is actually a positive result.

But we only get an error if the subquery returns results. So, we can use an inner WHERE inside the SELECT subquery. For example, we can use LIKE:

	
		

SELECT Id, 

(SELECT LinkedEntity.Name FROM ContentDocumentLinks WHERE LinkedEntity.Name LIKE  'A%') 

FROM ContentDocument 

For example, if there is a linked entity with a name starting with "A," our subquery will yield a result and produce an error message. If there is no linked entity with a name starting with "A," our subquery will yield no results and, consequently, produce no error message.

Using the WHERE and LIKE subqueries enabled our researchers to test a single character at a time.

14 get record subquery name like a

Using the WHERE and LIKE subqueries enabled our researchers to test a single character at a time.

By repeating the subquery process, character by character, and specifying different fields, we deduced entire names, email addresses, and phone numbers. If the ContentDocument is attached to an account, lead, or contact, we can gain information about customers as well. To save time and manual effort, we created and ran a small script:

Automating the SOQL injection makes typically laborious attacks effective and viable. 

15 script

Automating the SOQL injection makes typically laborious attacks effective and viable. 

This resulted in us learning the phone number and the file owner’s name. In other cases, we managed to deduce additional sensitive information and PII, including phone numbers and email addresses from accounts, leads, users, and other records.

Reduce the blast radius. 

The most efficient way of reducing your blast radius is to remove Salesforce public links whenever possible.  

Varonis allows you to identify and remove the ability to create public links from users who don't need those permissions, as well as remove existing links that expose sensitive information — all without navigating complex Salesforce Profiles or Permission Sets.

Learn more about how Varonis can help secure your Salesforce environment. 

 

What should I do now?

Below are three ways you can continue your journey to reduce data risk at your company:

1

Schedule a demo with us to see Varonis in action. We'll personalize the session to your org's data security needs and answer any questions.

2

See a sample of our Data Risk Assessment and learn the risks that could be lingering in your environment. Varonis' DRA is completely free and offers a clear path to automated remediation.

3

Follow us on LinkedIn, YouTube, and X (Twitter) for bite-sized insights on all things data security, including DSPM, threat detection, AI security, and more.

Try Varonis free.

Get a detailed data risk report based on your company’s data.
Deploys in minutes.

Keep reading

Varonis tackles hundreds of use cases, making it the ultimate platform to stop data breaches and ensure compliance.

security-vulnerabilities-in-apex-code-could-leak-salesforce-data
Security Vulnerabilities in Apex Code Could Leak Salesforce Data
Varonis' threat researchers identified high- and critical-severity vulnerabilities in Apex, a programming language for customizing Salesforce instances.
abusing-misconfigured-salesforce-communities-for-recon-and-data-theft
Abusing Misconfigured Salesforce Communities for Recon and Data Theft
Our research team has discovered numerous publicly accessible Salesforce Communities that are misconfigured and expose sensitive information.
ghost-sites:-stealing-data-from-deactivated-salesforce-communities
Ghost Sites: Stealing Data From Deactivated Salesforce Communities
Varonis Threat Labs discovered improperly deactivated Salesforce 'ghost' Sites that are easily found, accessible, and exploitable by attackers.
speed-data:-the-(non)malicious-insider-with-rachel-beard
Speed Data: The (Non)Malicious Insider With Rachel Beard
Salesforce's Rachel Beard discusses why insider threats may not always have ill intentions and why security in the CRM is crucial.