Salesforce Misuse of Platform Cache Leads to Widespread Data Exposure

,

By Aaron Costello, Principal SaaS Security Engineer, AppOmni

A widespread Salesforce data exposure has been detected arising from misuse of the platform cache. The Salesforce Platform Cache is a unique feature that allows for in-memory storage at the user session and org level. Its allure to developers is that it boasts double, if not more, the speed of equivalent SOQL queries, which is especially useful for data that does not need to be retrieved fresh every time.

Yet, as commonly seen, attractive development features can quickly become a security nightmare, especially considering that the internal workings of this feature are far more nuanced than most. It presents a simple yet disruptive security model that does not play well with existing authorization controls on the platform such as profiles and permission sets. It even challenges what we know about namespaces and the segregation of data between them. If exploited, this can result in the disclosure of information to both unauthorized individuals and malicious third party packages.

Through analysis of Platform Cache implementations, it was noted that 47% of those using the Platform Cache were doing so to handle sensitive information. Among that subset, 88% were vulnerable to at least one of the two risks that are discussed in this article. In addition to highlighting and demonstrating these risks, I will educate those who are familiar and unfamiliar with the Platform Cache on how it can be used securely to minimize security threats such as data exposure and supply-chain risk.

But first, a quick primer on what the Platform Cache is, and what it can do.

Understanding the Platform Cache

Depending on the use case, Salesforce offers numerous methods for storing varying kinds of data, such as custom settings and metadata, alongside others. In comparison, the Platform Cache is a segmented option for smaller volumes of data which are not required to adhere to an expected structure, like one may see when dealing with object fields. Especially disparate from how object data may be retrieved, the cache is accessible via Apex only. The only default API that can be used to query the cache is through Execute Anonymous, and this requires extensive privileges in order to query the Apex Cache API methods directly. That is, unless the methods of an Apex class utilizing the Cache API is exposed through annotations that would make it available to a greater set of users such as `RestResource`, `AuraEnabled`, and others.

With respect to the allocation of cache space, this is controlled by the organization. At the base layer, there are the Org and Session caches. Data within the Org cache is shared and visible by all Apex within the same entire namespace. On the other hand, data stored in Session cache is unique to each user and their active session. Then, within both the Org and Session caches, individual partitions can be created and have their own space allocated. These partitions are only beneficial with respect to data and space management, they do not provide any additional layers of security. Additionally, individual managed packages can introduce their own Org and Session partitions into the organization. Whilst they are part of the package and live in the package namespace, it is up to the subscriber org to allocate the necessary space for the package cache partitions to function.

There are a reasonable number of functions that exist in both the `Org` and `Session` classes of the `Cache` namespace, along with their subclasses to manage partitions. Surprisingly, it is the `put()` operation, used to store key and value pairs in a cache, that single-handedly introduces the majority of the risk. In the following sections, I’ll be walking through the common threat vectors introduced through misuse of this function.

Layers of the Platform Cache Per Salesforce Documentation: Salesforce Misuse of Platform Cache Leads to Widespread Data Exposure
Layers of the Platform Cache Per Salesforce Documentation

Bad Defaults: Put-ting Your Data at Risk

Per the official documentation, when a `put()` call made without providing a `visibility` parameter value will result in the value being visible from any namespace. This not only means that an organization’s cache values are default readable by managed packages, but also that `put` operations made from within a Managed Package to its own partition will be read-able by the subscriber org if not specified otherwise.

As a result, malicious third parties may include Apex that upon installation, can use the undocumented `clone()` operation or loop through the result of `getKeys()` and repeatedly use `get()` to copy a subscriber’s Org cache partition keys and values that have not been secured.

Visual Representation of Third Party Risk: Salesforce Misuse of Platform Cache Leads to Widespread Data Exposure
Visual Representation of Third Party Risk

Fortunately, even though the documentation states there is an immutability flag on this operation, cross-namespace PUT operations are disallowed by default regardless of the value.

Insecure Reliance on Org Cache For Validation (Race Condition)

Even when a user has no direct access to Cache APIs themselves, Cache API implementations in accessible Apex can be dangerous when Org cache partitions are used to store sensitive information and are relying on the value of cache keys to perform validation.

Due to a lack of cache-level semaphore / transaction locking, concurrent Apex transactions that perform write operations on the same Org cache key can result in either value taking priority. In cases where multiple keys are being set per transaction, each and every `put` call that is made concurrently is racing to be the chosen value instead of an entirety of a single transaction’s `put` calls taking priority.

This can result in information being disclosed to the wrong calling user, which is especially risky considering 31% of Organization cache implementations are using it to store access tokens or private keys. A simplified example of this is below:

If the `putToken` function is executed concurrently by two different users, then a privilege escalation scenario can occur in which one of the users may receive the other user’s token. IE: The value of `calling_user` may be set to that of User 1’s, but the value of `token` may be set to that of User 2’s. A visual representation of the above code can be seen below.

Visual Representation of the Race Condition: Salesforce Misuse of Platform Cache Leads to Widespread Data Exposure
Visual Representation of the Race Condition

This visual illustrates a possible result of this misconfiguration in practice. Two individuals accessing a method using the `put()` function at the same time has resulted in a race condition, causing the ID of User 2 to be stored along with the token of User 1. Consequently, User 2 receives the token of User 1, granting unauthorized access to User 1’s data and potentially escalating privileges.

A Better Alternative: The Session Cache

My first recommendation in tackling the aforementioned scenarios is to utilize the Session Cache for the storing of sensitive data. It could be theorized that the Org cache is favored due to having support for a longer TTL, which in turn would reduce the frequent need to fetch fresh values and thus increase performance. But from a security perspective, the shared Org cache should be used solely to store non-sensitive information.

Second, ensure that the `Cache.VISIBILITY.NAMESPACE` value is passed to the `put()` operation to remove any third-party risk. This applies to both organization Apex code but even more so to package developers who likely do not have a Session cache within their managed package’s partition and are relying on the Org cache.

Remember, there is little benefit in attempting to turn the Platform Cache into something it isn’t. Sensitive data that must be shared across multiple users should be placed within a custom object governed by a dedicated permission set or even better, as a protected custom setting within a managed package.

Conclusion

It’s evident that there are still features on the Salesforce platform today with a heavy reliance on code-level security, which I believe is quickly becoming a hallmark of commonly misconfigured and misused features. Salesforce has been tackling the problem of insecure defaults relentlessly over the past few years. 

But with the growing feature set within the platform, one should never make assumptions when dealing with a product that is built on the shared-responsibility model. Organization-led SaaS security initiatives and regular security testing are paramount prior to adopting new functionality into production code.

Organizations should employ SAST tools within their pipelines to make certain that Apex functions that could place sensitive data at risk are flagged and prevented from being introduced into production. 

In parallel, SSPMs such as AppOmni can alleviate the burden of permission management for these Apex classes so that organizations have full visibility into who can access them.

Related Resources