
A Not So Comprehensive Guide to Securing Your Salesforce Organization
Please note: this blog post is the first in a two-part series aimed at IT professionals and Salesforce developers. We’ll cover foundational knowledge crucial for understanding the practical application in Part 2.
Salesforce security is often overlooked by organization owners and security professionals alike. However, we’ve seen a number of very interesting blogs and articles on the topic of Salesforce security released by researchers in the app sec space. The intersection of the content found in those blogs, though, is usually larger than the complement. The methodology almost exclusively revolves around the use of the Aura endpoint to evaluate your user’s permissions and retrieve all data your user has permission to view.
While the application of this methodology can lead to uncovering interesting and impactful findings, it focuses entirely on one facet of the overall attack surface of a Salesforce Org. The goal of this article is to provide readers with a concise introduction to the ideas and topics that will be utilized heavily in the next installment of this blog. Each topic and idea demonstrated today will play an important part in the follow-up article where we will walk through a real exploitation scenario step by step.
TL;DR
- Unintentional Data Exposure Through Unsecured SOQL in Apex Code
- The Perils of Cleartext Credentials in Salesforce
- Understanding Salesforce Managed Packages: Namespaces and Authorization
- Accessing Salesforce Data via the REST API
- Exposing Functionality with Apex Actions via the REST API
Unintentional Data Exposure Through Unsecured SOQL in Apex Code
Let’s explore a common security oversight: data exposure through improperly secured SOQL queries in the context of Apex code. The focus will be on @AuraEnabled methods defined in Apex classes defined with the “with sharing” access modifier. While developers often use bind variables, this alone does not guarantee security. Failing to enforce object- and field-level security (FLS) can expose sensitive data.
The Problem: Bypassing Salesforce’s Sharing Model
Salesforce’s sharing model controls a user’s visibility and access to data stored in your Salesforce Org’s database. These controls are enforced whenever data is accessed via default functionality created by Salesforce. An example would be when a Visualforce Page accesses data with the use of a standard controller. However, the use of Apex code allows developers to both greatly enhance the power of default functionality and create new functionality that meets the business needs of their organization. With great power comes great responsibility, though. Apex code, by default, runs in the SYSTEM context which grants it access to all of the data stored in a Salesforce Org’s database. Without the proper checks in place, Salesforce Object Query Language (SOQL) queries issued in Apex code bypass the sharing model.
The example Apex class shown below dynamically constructs a SOQL query using input from the calling user. The user’s input is safely added to the query using the escapeSingleQuotes method that’s built into the String class. This method returns a string that has the escape character (\) added before any single-quote characters found in its argument. While the use of the escapeSingleQuotes method prevents SOQL Injection attacks, it does nothing to ensure that the sharing model is enforced.
public with sharing class DataRetriever { @AuraEnabled public static List<sObject> getData(List<String> fields, String objectName) { String fieldList = String.join(new List<String>(fields), ', '); String query = 'SELECT \'' + String.escapeSingleQuotes(fieldList) + '\' FROM \'' + String.escapeSingleQuotes(objectName) + '\' LIMIT 1'; try { List<sObject> results = Database.query(query); return results; } catch (QueryException e) { throw new AuraHandledException(e.getMessage()); } } }
While the use of the “with sharing” access modifier ensures that the sharing model is considered when data is accessed or mutated by native Apex methods, not all restrictions are applied when data is accessed using a SOQL query.
According to the Salesforce documentation that covers this situation, if an Apex class declared with the “with sharing” access modifier issues a SOQL query to access data on behalf of a user, the Object-Level Security and Field-Level Security components of the Sharing Model will not apply.
In the example Apex class shared above, the Salesforce Organization’s Org-Wide Sharing Defaults would still affect the visibility of any objects the calling user attempts to access, but it would allow them to gain unauthorized access to any record data stored in objects they do have visibility of.
However, there is a remedy. The Salesforce resource labeled Enforcing Object and Field Permissions describes how the use of the WITH SECURITY ENFORCED clause coupled with the Security.stripInaccessible method allows developers to apply fine-grain control over the data returned to their users. Together, these methods can effectively prevent the introduction of authorization issues into your Salesforce Org’s codebase.
The Perils of Cleartext Credentials in Salesforce
A critical security vulnerability arises when developers store sensitive information, particularly integration credentials (usernames, passwords, API keys), in cleartext within Salesforce. This section examines four common, yet dangerous, locations where credentials might be mistakenly stored: Custom Metadata Types, Custom Settings, Custom Objects, and Apex code comments.
1. Custom Metadata Types
Custom Metadata Types define application metadata. They are not designed to store sensitive data. While Custom Metadata Types have visibility settings, these only control access from other packages. Within the Salesforce Org, an unprotected SOQL query issued in Apex code can retrieve all records associated with a Custom Metadata Type, regardless of visibility. Custom Metadata Type objects are easy to spot as their names end with __mdt.
If a developer decides to place AWS credentials, for example, in a Custom Metadata Type named Integration_Creds__mdt, a user with access to the DataRetriever Apex class could call DataRetriever.getData() with the Custom Metadata Type’s name and field names to retrieve your integration’s cleartext credentials.
2. Custom Settings
Custom Settings create custom datasets associated with an organization, profile, or user. Two types of Custom Settings object exist: List (table-like) and Hierarchy (context-specific values). This datatype is meant to aid in the configuration of your applications. They are not meant to store secrets. However, we here at NetSPI find that some of our clients heavily rely on Custom Settings to store integration credentials and other sensitive data.
3. Custom Objects
Custom Objects are fundamental for storing business data and customizing your applications. However, they are never a safe option for storing sensitive data. Before targeting Custom Metadata Types or Custom Settings objects, an attacker will always look for low-hanging fruit in default and custom Salesforce objects.
Both Custom Settings and Custom Objects are easy to spot as their names will always end with __c. Any user in your Salesforce Org with access to the DataRetriever Apex class can call DataRetriever.getData() to retrieve any sensitive data stored in any Custom Setting or Custom Object they have visibility of.
4. Apex Code Comments
Developers sometimes hardcode credentials into Apex class or test class comments during the development process and forget to remove them before the code leaves their dev environment. It’s important to keep two things in mind when deciding to add hardcoded credentials to your Apex code.
The first is that comments are part of the codebase and visible to anyone with “View Setup and Configuration” permission. While this permission doesn’t grant a user the ability to modify or call any Apex classes, it does let them view the source code of any custom Apex, Visualforce, etc… code that is present on your Salesforce Org.
The second is that the source code for an unmanaged Apex class is stored in its corresponding ApexClass record’s Body field. That means that a user could use DataRetriever.getData() to query the Body field of the ApexClass object, retrieving the class’s source code, including comments. This would expose the hardcoded, cleartext credentials to an attacker.
Storage Location | Intended Use | Vulnerability | DataRetriever |
---|---|---|---|
Custom Metadata Types | Application metadata | Accessible in system context Apex; visibility settings are not a security measure. | Yes |
Custom Settings | Application configuration | Accessible in system context Apex; hierarchy settings are not a security measure. | Yes |
Custom Objects | Business data | Accessible in system context Apex; sharing model bypassed. | Yes |
Apex Code Comments | None (should not be used for data) | Directly visible; and retrievable via SOQL querying the ApexClass object’s Body field. | Yes |
The point of this section is to really drive home the idea that Apex developers should never store credentials in cleartext anywhere on your Salesforce Org. Use secure alternatives like Named Credentials or a dedicated secrets management solution instead. The DataRetriever examples highlight the importance of secure coding practices.
Understanding Salesforce Managed Packages: Namespaces and Authorization
Managed packages are a powerful way to distribute applications on the Salesforce platform. They provide a container for Apex, custom objects, Lightning components, and other metadata. This section explores two key aspects: namespaces and authorization.
1. Namespaces: Preventing Conflicts and Identifying Package Components
When you create a managed package, it’s assigned a unique namespace. This namespace acts like a prefix that’s added to all the components within the package and is crucial for preventing naming conflicts. Imagine two different developers creating a custom object named Project__c. Without namespaces, these objects would clash when installed in the same organization.
How Namespaces Work:
- When you create a managed package, you register a namespace prefix with Salesforce. This prefix is typically short (1-15 characters) and unique to your organization or package.
- All components within the managed package automatically have this prefix added to their API names.
- For example, if your namespace is MyPkg, a custom object named Project__c within your package would be assigned the name MyPkg__Project__c. A custom field named Status__c on that object would become MyPkg__Project__c.MyPkg__Status__c.
- This applies to Apex classes, Visualforce pages, Lightning components, custom fields, custom metadata types, and so on.
Referencing Components with Namespaces:
- Within the Package: Within the Apex code of your managed package, you can often refer to components without explicitly using the namespace (Salesforce handles this internally).
- Outside the Package (in the Subscriber Org): When code outside your managed package (e.g., in the subscriber organization’s Apex code, formulas, or other configurations) needs to reference a component within your package, it must use the namespace prefix.
- SOQL Example:
SELECT MyPkg__Name__c FROM MyPkg__Project__c WHERE ...
- Apex Example:
MyPkg.MyClass myObject = new MyPkg.MyClass(); MyPkg__Project__c project = [SELECT MyPkg__Name__c FROM MyPkg__Project__c LIMIT 1];
- Visualforce/Lightning Component Example:
<apex:inputField value="{!MyPkg__Project__c.MyPkg__Status__c}" />
- SOQL Example:
Benefits of Namespaces:
- Conflict Prevention: Guarantees that your package’s components won’t clash with components in the subscriber org or in other installed packages.
- Clear Identification: Makes it immediately obvious which components belong to your package.
- Organization: Helps to keep code and metadata organized, especially in complex projects.
2. Authorization: Connected Apps and OAuth
Managed packages often need to access data or perform actions on behalf of the user or the organization. Salesforce uses Connected Apps and the OAuth 2.0 protocol to handle this authorization securely.
A Connected App is a framework that enables external applications to integrate with Salesforce using APIs. When you create a managed package that needs to interact with the Salesforce API (e.g., to create records, read data, or call external services), you’ll typically include a Connected App as part of the package.
OAuth 2.0 is an industry-standard protocol for authorization. It allows users to grant access to their data to a third-party application (your managed package) without sharing their Salesforce username and password directly with the application.
Here’s a quick summary of how it works in Managed Packages:
- Installation: When a user installs your managed package, the included Connected App is also installed.
- Configuration: The administrator of the Salesforce Org may need to configure the Connected App, granting it specific permissions (scopes) that define what the app can access. For example, accessing and managing your user’s data may require the “api” scope.
- Authorization Flow: When your managed package’s code needs to access the Salesforce API, it initiates an OAuth flow. This typically involves:
- Redirecting the user to a Salesforce login page (if they are not already logged in).
- Prompting the user to grant access to the Connected App (if they haven’t already done so).
- Receiving an authorization code from Salesforce.
- Exchanging the authorization code for an access token.
- Using the access token to make API calls.
- Refresh Tokens: Often, the Connected App will also receive a refresh token. This allows the app to obtain new access tokens without requiring the user to re-authorize repeatedly.
- Benefits of OAuth and Connected Apps:
- Security: Users don’t share their credentials with your package.
- Granular Control: Administrators can control precisely what data and actions your package can access.
- Revocable Access: Users or administrators can revoke access to your Connected App at any time.
- Standard Protocol: OAuth 2.0 is a widely used and well-understood protocol.
Managed packages use namespaces to avoid naming conflicts and clearly identify their components. They also leverage Connected Apps and the OAuth 2.0 protocol to securely access Salesforce data and functionality on behalf of users and organizations. All without requiring users to share their credentials directly with the package. This combination of namespaces and secure authorization makes managed packages a robust and reliable way to build and distribute applications on the Salesforce platform.
Accessing Salesforce Data via the REST API
The Salesforce REST API offers a powerful way to interact programmatically with Salesforce data. External applications (and internal code like Lightning components) can use it to create, read, update, and delete records, access metadata, and so on. This section covers the REST API, the crucial “API Enabled” permission, and a key consideration for managed packages.
1. The Salesforce REST API: A Brief Overview
The REST API uses standard HTTP methods (GET, POST, PATCH, DELETE) to interact with Salesforce resources, each identified by a unique URI.
- Resource-Based: Interactions are centered around resources (e.g.,
/services/data/vXX.X/sobjects/Account
). - HTTP Methods: Uses standard verbs (GET for retrieval, POST for creation, PATCH for updates, DELETE for deletion).
- Versioned: The API is versioned (e.g., v63.0) for updates and backward compatibility.
2. The API Enabled Permission: A Gatekeeper
To utilize the Salesforce REST API (or SOAP API), a user must have the API Enabled permission. This permission is granted via the user’s profile or an assigned permission set.
- Importance: This permission controls which users (and applications acting on their behalf) can access Salesforce data through the REST API, serving as a critical security control.
- Location: Found in the “System Permissions” section for both Profiles and Permission Sets.
3. Managed Packages, Connected Apps, and API Access: A Key Distinction
A Connected App within a managed package can possess the “API Enabled” permission even if the user’s own profile or permission sets do not grant them this permission directly for their browser session.
- The Scenario:
- A user installs a managed package containing a Connected App.
- The Connected App is configured (by the package developer/administrator) with “API Enabled,” often via a permission set within the package.
- The user’s own profile/permission sets (assigned outside the package) lack “API Enabled.”
- The Consequence:
- The managed package, via its Connected App, can make REST API calls. It acts on behalf of the user, using the Connected App’s permissions.
- Direct API access attempts by the user (e.g., via Workbench) outside the managed package context will fail due to the user’s lack of “API Enabled.”
This highlights that a managed package’s permissions (via its Connected App) can differ from the installing user’s direct permissions. The package operates within the boundaries set by its Connected App, while the user’s direct access is governed by their profile and permission sets.
The “API Enabled” permission is vital for REST API access. Managed packages, through their Connected Apps, can have API access even if the user’s profile doesn’t grant it directly. This allows packages to perform actions on the user’s behalf, within defined limits, without requiring the user to have broad API access.
Access Method | API Enabled | Result | Explanation |
---|---|---|---|
Direct User Access (e.g., Workbench) | User’s Profile/Permission Sets | Access Denied (if permission is missing) | Salesforce checks the user’s own permissions for direct API calls. |
Managed Package via Connected App | Connected App’s Permissions (often via a package Permission Set) | Access Granted (if Connected App has permission) | The Connected App’s permissions are used, not the user’s direct permissions. |
Exposing Functionality with Apex Actions via the REST API
Building on our understanding of the REST API and managed packages, we now introduce Apex Actions. Apex Actions provide a secure and controlled way to expose specific pieces of Apex logic as callable endpoints within the Salesforce REST API. This allows external applications, or even other parts of Salesforce (like Lightning Web Components), to interact with your server-side code, leveraging the power of Apex without needing direct access to the underlying implementation.
1. Connecting Apex to the Outside World
Recall that the REST API allows interaction with Salesforce data and metadata. Apex Actions extend this capability by allowing the execution of custom Apex code. Think of them as named functions that you can call remotely.
2. Location within the REST API
Apex Actions are neatly organized within the REST API under a specific path:
- /services/data/vXX.X/actions/custom/apex/ActionName (for actions not in a managed package)
- /services/data/vXX.X/actions/custom/apex/Namespace/ActionName (for actions within managed packages)
The ActionName is directly derived from the name of the Apex class containing the action’s logic. The Namespace is relevant if the containing class is a part of a managed package as covered in a previous section.
3. The @InvocableMethod Annotation
Within an Apex class, a special annotation, @InvocableMethod, designates a method as being an Apex Action. This annotation acts as a flag, telling Salesforce that this particular method should be exposed as a callable endpoint.
public class AccountProcessor { @InvocableMethod(label='Process Accounts') public static List<Id> updateAccountDescriptions(List<AccountWrapper> accounts) { // Logic to update accounts return updatedAccountIds; } public class AccountWrapper{ @InvocableVariable(required=true) public Id accountId; @InvocableVariable(required=true) public String newDescription; } }
In this simplified example, the updateAccountDescriptions method, decorated with @InvocableMethod above, becomes an Apex Action named AccountProcessor. The appropriate REST API endpoint where a user can execute the Apex method AccountProcessor.updateAccountDescription is /services/data/vXX.X/actions/custom/apex/AccountProcessor.
As with any exposure of an Apex class to your Salesforce Org’s users, a great deal of caution must be taken anytime data is accessed or mutated on behalf of the calling user. Apex Actions are no exception.
Action Items / Conclusions to Help Protect Resources
Some of the topics discussed in this blog today may seem a bit disjointed, and they are, but for good reason. Living off the land and being resourceful requires that you see disjointed items as an opportunity to create something no one expects. What I hoped to accomplish in this article was to provide you with the background necessary to follow along with the next installment.
The follow-up article will walk readers through a real-life scenario that affected one of our clients. Knowledge of each of the issues discussed was necessary to build our exploit chain. A few modern Salesforce exploitation ideas and techniques will be discussed and demonstrated. If you’re a Salesforce developer or administrator, the next installment will certainly make you look at your Custom Metadata Types with more scrutiny. Thanks for reading.

Explore More Blog Posts

Validating Azure Cloud Security with Breach and Attack Simulation as a Service
NetSPI’s Breach and Attack Simulation as a Service offers focused simulation tests for Azure users to validate your cloud security capabilities.

Getting Shells at Terminal Velocity with Wopper
This article introduces Wopper - a new NetSPI tool that creates self-deleting PHP files and automates code execution on WordPress using administrator credentials.

CVE-2025-21299 and CVE-2025-29809: Unguarding Microsoft Credential Guard
Learn more about the January 2025 Patch Tuesday that addresses a critical vulnerability where Kerberos canonicalization flaws allow attackers to bypass Virtualization Based Security and extract protected TGTs from Windows systems.