Introduction
In the previous blog, we saw some of the vulnerabilities which we observe during Salesforce review. In this blog, we understand some of the security features provided by Salesforce platform and how can we leverage it to write secure code along with the method to detect insecure implementation.
Salesforce requires developers to check object-level, field-level, and record-level permissions in their code. Failure in addressing these issues could result in causing CRUD/FLS vulnerabilities, which ultimately can expose sensitive data to unauthorized users.
CRUD/FLS Violation Vulnerability
What is CRUD/FLS?
CRUD (Create, Read, Update, Delete) terms whether a user is authorized to perform operations on an object like an account, contact, or opportunity. FLS (Field-Level Security) determines whether a user can view or modify desired fields within an object, such as the Salary field in the Employee object. Object (CRUD) and Field Level Security (FLS) are configured on profiles and permission sets and can be used to restrict access to standard and custom objects and individual fields. Force.com developers should design their applications to enforce the organization's CRUD and FLS settings on both standard and custom objects, and to gracefully degrade if a user's access has been restricted.
The CRUD/FLS violation is typically triggered when the Apex code fails to verify the user authorization before performing DML operations, querying objects, or fielding directly.
Insecure Apex Code Example
public class EmployeeController {
@AuraEnabled
public List<Employee__c> getEmployees() {
// No check for object-level or field-level security
return [SELECT Id, Name, Salary__c FROM Employee__c];
}
@AuraEnabled
public void createEmployee(String name, Decimal salary) {
// No check for Create access
Employee__c emp = new Employee__c(Name = name, Salary__c = salary);
insert emp;
}
}
In this example:
- A user without “View Salary” field access can still retrieve salary data.
- A user without “Create Employee” permission can still insert a new record.
How to Detect in Code:
During a manual code review, look for:
- SOQL queries that directly select fields
- DML statements (insert, update, delete, and upsert) executed without permission checks
- Missing calls to Salesforce’s schema-based security methods:
- Schema.sObjectType.ObjectName.isAccessible()
- Schema.sObjectType.ObjectName.isCreateable()
- Schema.sObjectType.ObjectName.isUpdateable()
- Schema.sObjectType.ObjectName.isDeletable()
- Schema.sObjectType.ObjectName.fields.FieldName.isAccessible()
How to Fix in Code:
To rectify the problem, the code should effectively perform CRUD/FLS enforcement on both the object and the fields of the object. This will help the developer utilize various mechanisms based on the requirements and context of the application.
User Mode Execution – Apex code runs in System mode by default, which means that code will be executed with excessive permissions regardless of the current user’s permissions. By allowing the User mode object and field-level permissions of the current user, the DML operation is performed.
// Insert record with user-level permission checks
Database.SaveResult result = Database.insert (new Opportunity (Name = 'Big Deal', CloseDate = Date.today(), StageName = 'Prospecting'),
AccessLevel.USER_MODE
);
Traditional CRUD/FLS Enforcement Checks – isAccessible, isCreateable, isUpdateable, isDeleteable methods
With Security Enforced – User’s object/field permissions are enforced by appending the “WITH SECURITY_ENFORCED” keyword in the SOQL query. This keyword is only allowed to check permissions for read operations.
// Enforces both CRUD and FLS at query time
List<Contact> cons = [
SELECT Id, FirstName, LastName, Email
FROM Contact
WITH SECURITY_ENFORCED
];
Note: When the “WITH SECURITY_ENFORCED” keyword is used, the API version should be 48.0 or later. Additionally, this keyword does not support traversal of a polymorphic field’s relationship and TYPEOF expressions with an ELSE clause in queries.
Using stripInaccessible() – The stripInaccessible() method helps to enforce CRUD/FLS by removing the fields from query and subquery that users do not have access to. This method checks user’s access based on their field-level-security for a specified operation – create, read, update, and upsert.
List<Account> accounts = [SELECT Id, Name, Phone, Email FROM Account];
// Strip fields that are not readable
SObjectAccessDecision decision = Security.stripInaccessible (AccessType.READABLE, accounts);
List<Account> sanitizedRecords = (List<Account>) decision.getRecords();
Impact
CRUD and FLS always need to be enforced for create, read, update, and delete operations on standard objects. In the vast majority of cases, CRUD and FLS should also be enforced on custom objects and fields. Any application performing creates/updates/deletes in Apex code, passing data types other than SObjects to VisualForce pages, using Apex web services or the @AuraEnabled” notation should be checked that it is calling the appropriate access control functions.
In the past, due to incorrect implementation of CRUS/FLS in application, we have observed that an employee can gain unauthorized access to personnel data or payroll information of everyone in the organization OR in customer support applications, weak CRUD checks allow agents to modify or delete records beyond their assigned accounts.
There is business and compliance risks involved when the proper enforcement of CRUS/FLS permissions are not in place. Such weaknesses can ultimately result in data breaches, regulatory penalties, financial loss, and reputational damage.
Sharing Violation Vulnerability
What is Sharing in Salesforce?
Salesforce enforces record-level security through sharing rules, determining which records a user can access. Apex classes run in the following modes:
- With Sharing – Enforces record-level sharing rules of the logged-in user.
- Without Sharing – Ignores record-level sharing rules and runs in system context, potentially exposing all records. A sharing violation occurs whenever Apex code runs without respecting the current user's record-level sharing rules.
- Inherited Sharing – Declaring class will enforce (inherit) the sharing rules of the calling class.
Insecure Apex Code Example
public without sharing class AccountController {
@AuraEnabled
public List<Account> getAccounts() {
// Returns all accounts, even those the user should not see
if (Schema.sObjectType. Revenue__c.isAccessible() &&
Schema.sObjectType. Revenue__c.fields.Name.isAccessible()) {
return [SELECT Id, Name, Revenue__c FROM Account];
}
}
}
In this example, even if sharing rules restrict the user to accounts in their region, the class will return all accounts in the system. Even if CRUD-FLS permissions are checked before performing the query, it will still return all accounts. This is because CRUD-FLS permissions ensure field-level security, while sharing rules ensure record-level access.
How to Detect in Code
During a code review, look for:
- Classes declared as without sharing.
- Classes with no explicit sharing declaration (default is without sharing if not specified in a class used as an entry point).
- DML operations and Queries returning sensitive records without additional filtering.
Secure Apex Code Example
public with sharing class AccountController {
@AuraEnabled
public List<Account> getAccounts() {
if (Schema.sObjectType. Revenue__c.isAccessible() &&
Schema.sObjectType. Revenue__c.fields.Name.isAccessible()) {
return [SELECT Id, Name, Revenue__c FROM Account];
}
}
}
If a class must operate in system mode (e.g., batch jobs, admin tasks), developers should:
- Explicitly justify the use of without sharing.
- Apply programmatic filters to enforce access (e.g., filtering records based on owner or custom sharing logic).
Impact
The Force.com platform makes extensive use of data sharing rules. Each object can have unique permissions for which users and profiles can read, create, edit, and delete. These restrictions are enforced when using all standard controllers. When using a custom Apex class, the built-in profile permissions and field-level security restrictions are not respected during execution. The default behaviour is that an apex class has the ability to read and update all data within the organization. Because these rules are not enforced, developers who use Apex must take care that they do not inadvertently expose sensitive data that would normally be hidden from users by profile-based permissions, field-level security, or organization-wide defaults. This is particularly true for Visualforce pages.
In the past, due to insecure implementation of sharing, we have observed vulnerabilities like, sharing violation exposes medical records, insurance details, or financial information to unauthorized staff, resulting in HIPAA, GDPR, or PCI DSS violations OR in sales environments, sales executive might get access to opportunities in another territory OR a support agent gets an access to cases belonging to another client.
In regulated settings, this could allow unauthorized users to get access to personal financial and healthcare information, resulting in privacy breaches, regulatory non-compliance, and fines. When we have sharing violations, they are more than just a compliance issue. They create a lack of trust among the partners. They also disrupt the flow of business activities. Even more, they expose competitive data. Finally, all this leads to customers losing their trust and some reputational harm.
Conclusion
Salesforce platform provides CRUS/FLS and sharing implementation as a key feature to implement security and permissions however the most common vulnerabilities are inappropriate implementation of CRUD/FLS and Sharing Violation in the Salesforce application. To mitigate these risks, developers should take care of the below: -- Before executing any query or DML operation, always check the CRUD and FLS permissions.
- Classes should be declared “with sharing” by default unless you have a good reason to bypass sharing rules.
- Do manual code reviews along with automated scans to ensure that the security controls are implemented.