Security Vulnerabilities in Salesforce
- SOQL
injection,
- Cross-Site
Scripting (XSS),
- Cross-Site
Request Forgery (CSRF).
Let's
discuss each of them briefly.
SOQL injection
From
definition SQL injection can happen when we have insecure construction of
database queries with user-supplied data e.g.:
- queries
are built directly with user data inlined,
- queries
are concatenated directly with the query
text (not type-safe bind parameters).
- Behaviors of the WHERE clause to
change the set of returned records.
- Selecting fields from an object.
- Selecting the object for query.
Malicious
input may be able to change the structure of the query to bypass or change
application logic.
Good
information is that In SOQL (own Salesforce database query language) in
contrast to SQL, we cannot update or delete data. Because of this, the
most dangerous operations for data are not possible. But we need to remember
about another risks.
Ad. 1 Example scenario –searching based on user input from form:
public PageReference search() {
String query = ‘SELECT Id FROM Account WHERE Name LIKE \’%’ + Name +‘% \’’;
queryResult = Database.query(query);
return null;
}
Simple Visualforce with custom Controller where User
can search Account record by inserting Name of Account.
When user will enter a value like:
- null%’
- OR: ‘
Query will return all the Account records.
That’s not good.
Solution:
public PageReference search() { String queryName = ‘%’ + name + ‘%’; queryResult = [SELECT Id FROM Account WHERE Name LIKE :queryName]; return null; }
Ad.2 Example scenario - Selecting the Object
Names:
In this case, we can’t just replace dynamic query with a parameterized one. Good practice is to writing a sanitizing function:
public boolean isSafeObject(String objName){
public Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
public SObjectType myObj = schemaMap.get(objName);
if (myObj.getDescribe().isAccessible()({
return true;
} else {
return false;
}
}
Later on our database query, we can construct with using our sanitizing function:
public PageReference doQuery() { String myQuery = 'Select Name, Address from '+ objName + ' where Name like '%Sir%'; if(!isSafeObject(objName)){ return null; } else { if(!Schema.getGlobalDesribe().get(objName).fields.Name.isAccessible() ||!Schema.getGlobalDesribe().get(objName).fields.Address.isAccessible()){ return null; } Records = database.query(myQuery); } }
Ad.3 Example scenario - Selecting fields from an object:
Maybe you know what
object you want to query against and how you want to filter it, but you aren’t
sure which fields you will need access to in the object.
In the example below, we take care about CRUD and FLS checks for the object and the associated fields:
public boolean isSafeField(String fieldName, String objName) {
public Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
public SObjectType myObj = schemaMap.get(objName);
if (myObj.getDescribe().isAccessible() ) {
SObjectField myField = myObj.getDescribe()
.fields.getMap().get(fldName);
if(myField.getDescribe().isAccessible()){
return true;
} else {
return false;
}
return false;
}
}
public PageReference doQuery() {
String objName = 'myObj__c';
String myQuery = 'Select ' + field1 +',' + field2 +' from myObj__c where Name like '%Sir%';
if(!(isSafeField(field1, objName) && isSafeField(field2, objName))){
return null;
} else {
Records = database.query(myQuery);
}
}
Cross-Site Scripting (XSS):
From definition XSS occurs when an attacker can insert unauthorized JavaScript, VBScript, HTML, or other active content into a web page viewed by other users. A malicious script inserted into a page in this manner can hijack the user’s session, submit unauthorized transactions as the user, steal confidential information, or simply deface the page.
Generally XSS occurs when browsers interpret attacker
controller data as code. To understand this we need to know how browser distinguish
between data and code.
- First stage:
<script> document.querySelector('#greet').innerHTML ='You searched for <b>" {!$CurrentPage.parameters.q}"</b>'; </script>
The attacker can set e.g. URL parameter :q= </script><script>..attacker code here..</script>.The HTML parser determines the original script block has ended, and an attacker controlled script block would be sent as a second script to the Javascript engine.
- Second stage:
- . Third stage:
For those 3
stages three different control characters could be used:
- >
can be used to break out of the original script block
- '
can be used to break out of the javascript string declaration
- \x3c or \u003c or < can be used to inject a new tag via innerHTML.
- auto HTML encoding,
- built in encoding functions e.g. JSENCODE function
All merge-fields are always auto HTML encoded provided they:
- do not occur within a <style> or <script>
tag
- do not occur within an apex tag with the escape='false' attribute
<apex:outputText> {!$CurrentPage.parameters.userInput} <!-- safe (auto HTML Encoded) --> </apex:outputText>
<div>
{!$CurrentPage.parameters.userInput} <!-- safe (auto HTML Encoded) -->
</div>
But no auto-encoding is performed here because of the script tag:
<script> var x = '{!$CurrentPage.parameters.userInput}'; //vulnerable to XS </script>
- JSENCODE -- performs string encoding within a Javascript String context,
- HTMLENCODE -- encodes all characters with the appropriate HTML character references so as to avoid interpretation of characters as markup,
- URLENCODE -- performs URI encoding (% style encoding) within a URL component context,
- JSINHTMLENCODE -- a convenience method that is equivalent to the composition of HTMLENCODE(JSENCODE(x)).
<apex:page> <srcipt> Document.getElementById(“{!$Component.input}”).innerHTML = “{!$CurrentPage.parameters.input}”; </script> <div id=”input”><div><apex:page> </apex:page>
<script> Document.getElementById(“{!$Component.input}”).innerHTML = “”; alert(‘error’); </script>
<script>
Document.getElementById(“{!$Component.input}”).innerHTML = “”;
alert(‘error’);
</script>
<apex:page> <srcipt> Document.getElementById(“{!$Component.input}”).innerHTML = “{!JSENCODE($CurrentPage.parameters.input)}”; </script> <div id=”input”><div> </apex:page>
Example scenario – deleting records
<apex:page controller = “CustomController” action =”{!init }”></apex:page>
public class CustomController { public void init() { Id id = ApexPages.currentPage().getParameters().get(‘id’); Account a – [SELECT Id FROM Account WHERE Id = :id]; Delete a; } }
In this case if user will visit website of attacker while being authenticated with Salesforce, attacker will be able to delete record.
Solution:
Within the Lightning Platform, we have an anti-CSRF token to prevent this attack. The main problem is that init() method is called before VF page is rendered in the user’s browser.
The action attribute should be deleted from <apex:page>. For fire action we can use <apex:commandButton> for example:
Of course, what is in the article gives only a general view. But every jorney starts with it :)
Resources:
- https://developer.salesforce.com/docs/
- https://focusonforce.com/courses/platform-developer-1-study-guide/
Comments
Post a Comment