

Dynamic SOQL Injection Prevention – Every Salesforce Dev must know
SOQL injection is a security flaw where attackers manipulate inputs to alter the logic of a Salesforce Object Query Language (SOQL) query. This can expose sensitive data or corrupt records. Dynamic SOQL (queries built as strings at runtime) are especially vulnerable if inputs are not sanitized. Let’s learn about Dynamic SOQL Injection Prevention – Every Salesforce Dev must know.
Vulnerable Visualforce Page & Apex Controller
Scenario: A search page that dynamically filters Accounts by name.
Visualforce Page (Vulnerable): AccountSearchPage.vfg
<apex:page controller="AccountSearchController">
<apex:form>
<apex:pageBlock>
<apex:inputText value="{!searchTerm}" label="Search Account Name"/>
<apex:commandButton value="Search" action="{!searchAccounts}"/>
<apex:pageBlockTable value="{!results}" var="acc">
<apex:column value="{!acc.Name}"/>
<apex:column value="{!acc.Industry}"/>
</apex:pageBlockTable>
</apex:pageBlock>
</apex:form>
</apex:page>
Explanation:
- <apex:inputText>: Creates an input field where users can enter a search term. The value is bound to the searchTerm property in the Apex controller.
- <apex:commandButton>: A button that triggers the searchAccounts method in the Apex controller when clicked.
- <apex:pageBlockTable>: Displays the search results (results) in a table format. Each row represents an Account object, and the columns display the Name and Industry fields.
AccountSearchController class
public class AccountSearchController {
public String searchTerm { get; set; }
public List<Account> results { get; set; }
public void searchAccounts() {
String query = 'SELECT Id, Name, Industry FROM Account WHERE Name LIKE \'%' + searchTerm + '%\'';
results = Database.query(query);
}
}
Explanation:
- searchTerm: A public property bound to the input field in the Visualforce page.
- results: A list of Account objects that stores the query results. searchAccounts Method:
Builds a dynamic SOQL query by concatenating the searchTerm directly into the query string.
Executes the query using Database.query() and stores the results in the results variable.
The Attack:
If a user enters ‘ OR Name LIKE ‘% as searchTerm, the query becomes:
SELECT Id, Name, Industry FROM Account WHERE Name LIKE ‘%’ OR Name LIKE ‘%’
This returns all Accounts, exposing data.
Learn about SOQL best practices.
Secure Apex Controller Using Binding Variables
Apex Controller (Secure)
public class AccountSearchController {
public String searchTerm { get; set; }
public List<Account> results { get; set; }
public void searchAccounts() {
String sanitizedTerm = '%' + searchTerm + '%';
String query = 'SELECT Id, Name, Industry FROM Account WHERE Name LIKE :sanitizedTerm';
results = Database.query(query);
}
}
Explanation:
- sanitizedTerm: Prepends and appends % to the searchTerm to create a wildcard search.
- Binding Variable (:sanitizedTerm):
Instead of concatenating the input directly into the query, the :sanitizedTerm syntax binds the input as a variable.
Salesforce automatically escapes special characters (like ‘) in the bound variable.
Why It’s Secure:
If a user enters ‘ OR Name LIKE ‘%, the query becomes:
SELECT Id, Name, Industry FROM Account WHERE Name LIKE :sanitizedTerm
The :sanitizedTerm binding ensures the input is treated as a literal string, preventing injection.
Secure Apex Controller Using String.escapeSingleQuotes():
Apex Controller (Secure)
public class AccountSearchController {
public String searchTerm { get; set; }
public List<Account> results { get; set; }
public void searchAccounts() {
String sanitizedTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
String query = 'SELECT Id, Name, Industry FROM Account WHERE Name LIKE \'' + sanitizedTerm + '\'';
results = Database.query(query);
}
}
Explanation:
- String.escapeSingleQuotes(searchTerm): Escapes single quotes (‘) in the searchTerm by replacing them with \’.
- Dynamic Query: The sanitized input is concatenated into the query string.
Why It’s Secure:
If a user enters ‘ OR Name LIKE ‘%, the input becomes \’ OR Name LIKE \’%.
The query becomes:
SELECT Id, Name, Industry FROM Account WHERE Name LIKE ‘%\’ OR Name LIKE \’%’
The escaped single quotes prevent the input from altering the query logic.
Dynamic Sorting with Whitelisting:
Visualforce Page
<apex:page controller="AccountSortController">
<apex:form>
<apex:selectList value="{!sortField}" size="1">
<apex:selectOptions value="{!sortOptions}"/>
</apex:selectList>
<apex:commandButton value="Sort" action="{!sortAccounts}"/>
<!-- Display results -->
</apex:form>
</apex:page>
Explanation:
<apex:selectList>: A dropdown menu bound to the sortField property in the Apex controller.
<apex:selectOptions>: Populates the dropdown with options from the sortOptions property.
Apex Controller (Secure)
public class AccountSortController {
public String sortField { get; set; }
public List<Account> results { get; set; }
public List<SelectOption> sortOptions { get; set; }
public AccountSortController() {
sortOptions = new List<SelectOption>{
new SelectOption('Name', 'Name'),
new SelectOption('Industry', 'Industry'),
new SelectOption('CreatedDate', 'Created Date')
};
}
public void sortAccounts() {
// Whitelist allowed fields
Set<String> allowedFields = new Set<String>{'Name', 'Industry', 'CreatedDate'};
if (!allowedFields.contains(sortField)) {
throw new AuraHandledException('Invalid sort field');
}
String query = 'SELECT Id, Name FROM Account ORDER BY ' + sortField;
results = Database.query(query);
}
}
Explanation:
sortOptions: A list of SelectOption objects that define the dropdown options.
Whitelisting: The sortField is validated against a predefined list (allowedFields). If the input is not in the list, an exception is thrown.
Dynamic Query: The sortField is safely concatenated into the query because it has been validated.
Why It’s Secure:
Attackers cannot inject malicious clauses like CreatedDate ASC NULLS LAST, (SELECT Id FROM Users) because the input is restricted to the whitelist.
Final Thoughts:
Dynamic SOQL is a powerful feature in Salesforce that allows developers to build flexible and adaptable queries at runtime. However, with great power comes great responsibility. SOQL injection is a serious security risk that can lead to unauthorized data access, data corruption, and even full system compromise. By following the best practices outlined in this blog, you can harness the power of dynamic SOQL while keeping your org secure. I hope this Dynamic SOQL Injection Prevention will helpful for you.