Introduction:
The most important aspect of any application is the ability to store and retrieve data from a datastore. Structured Query Language (SQL) is used widely in a traditional web application for effective retrieval of data. When we build dynamic queries using SQL, it is most important to understand how our SQL query is defensive against the user supplied data.
What is SQL Injection?
We can’t trust the users’ input blindly without any additional validation; such data input can cause SQL injection, meaning a malicious user modifies the resulting query by injecting characters with special meaning to SQL.
Will it affect SOQL queries?
Salesforce uses SOQL (Salesforce Object Query Language) protects the application against most attacks. In SOQL, we can only perform SELECT operation and not UPDATE or DELETE. So, when compared to SQL, SOQL has a lower risk on SOQL injection but still the attacks are nearly identical to the traditional SQL injection. For example, a user can see the fields or an object which he doesn’t have access.
SQL vs SOQL:
SOQL has some restrictions over SQL.
- No INSERT, UPDATE or DELETE statements, only SELECT
- No wild cards for fields; all fields must be explicitly typed
- No JOIN statement; but can be written as Select name, phone, account.name from contact
- No UNION operator
- Queries cannot be chained together
Let’s look upon an example how a malicious user input is modifying the resulting query. The following code is intended to retrieve accounts which are not deleted.
- <! – A simple VF Page –!>
- <apex:page controller=”SOQLInjectionController” >
- <apex:form>
- <apex:outputText value=”Enter Name” />
- <apex:inputText value=”{!name}” />
- <apex:commandButton value=”Query” action=”{!dynamicquery}“ />
- </apex:form>
- </apex:page>
- //Controller class
- public class SOQLInjectionController{
- public String name {
- get {return name;}
- set {name = value;}
- }
- public PageReference dynamicquery() {
- String qryString = ’SELECT Id FROM Account WHERE ’ +
- '(IsDeleted = false and Name like \’%’ + name + ’%\’)’;
- queryResult = Database.query(qryString);
- return null;
- }
- }
Let’s consider the user input under two scenarios.
When the user enters a plain input like name = XYZConsulting, the query will execute as expected.
- // Query string
- SELECT Id FROM Account WHERE (IsDeleted = false and Name like ’%XYZConsulting%’)
When a malicious user gives an input like this, name = test%’) OR (Name LIKE ‘ then the resulting query will be,
- SELECT Id FROM Account WHERE (IsDeleted = false AND Name LIKE ’%test%’) OR (Name LIKE ’%’)
Since the query is not validated, the result of this query shows all the accounts not just the deleted ones.
SOQL Defense Techniques:
There are some techniques we can apply to validate our SOQL query to prevent the SOQL injection. Some of them are listed below:
- Static queries with bind variables
- Type casting
- Replacing characters
- Whitelisting
Let’s see how these techniques are handled to prevent the vulnerability.
Static queries with bind variables
Using static queries in our application is the most recommended solution to prevent the SOQL Injection. Consider the following query which uses the user input (the var variable) directly in a SOQL query opens the application up to SOQL injection.
- String query = ‘select id from contact where firstname =\’’+var+’\’’;
- queryResult = Database.execute(query);
We can modify the query as below so that the user input is taken as a variable and not as an executable statement in the query.
- queryResult = [select id from contact where firstname =: var];
For this query, even though the user gives the value as test’ LIMIT 1, it looks for any first names that are “test’ LIMIT 1” in the database and the query is efficient now.
Type Casting
In our queries, if we are sure about what data type we are going to refer, then we can type cast it; so that we can eliminate the fake input. For example, if you are expecting an input type such as Boolean or integer, you can cast the user input value to the expected type.
- String query = ’Select Name, Address from Contact where isActive = ’ + Boolean(input);
Even though we get an erroneous input, the type casting will throw error.
Whitelisting
If we want to support dynamically selecting fields from the object we need to query, and also if we know what fields needs are expected to be selected, then we can check that the user input is one of those field names.
- Set<String> fields = new Set<String>();
- fields.add(‘myField1’);
- fields.add(‘myField2’);
- fields.add(‘myField3’);
- if(!(fields.contains(inputField)){
- .. Throw error ..
- }
We can throw error if the user input is coming in for unexpected fields.
Replacing Characters
When Type casting / whitelisting doesn’t prevent the adequate vulnerability against prevention, blacklisting otherwise called as Replacing Characters will be handy.
Consider our user input is like this,
- String query = ‘select id from user where isActive= ‘+var;
And the SOQL injection input as
- true AND ReceivesAdminInfoEmails=true
thus, the resulting query will lead to retrieving unintended data.
So, the fix can be removing all white spaces to make the erroneous input invalid.
- String query = ’select id from user where isActive=’+var.replaceAll(‘[^\w]’,”);
Conclusion
When we expose our data to the outside world, it is most important to check for the vulnerability against our queries. We cannot expect the users to give a valid input every time. Thus, by implementing the SOQL defense techniques, we can have a strict guideline against CRUD operations.