Security Vulnerabilities in Salesforce


 

In Salesforce we are using a lots of custom user interfaces. That’s why we should be aware about various types of security vulnerabilities. The most common you can find here:

  •         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).

When designing SOQL queries there are basically three places where behavior of the query can be based on input from User:

  1.    Behaviors of the WHERE clause to change the set of returned records.
  2.   Selecting fields from an object.
  3.    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:

Instead of using dynamic SOQL, the search() method should use a static SOQL query with a bind variable:

public PageReference search() {
    String queryName =  ‘%’ + name + ‘%’;
    queryResult = [SELECT Id FROM Account WHERE Name LIKE :queryName];
    return null;
}

In this case query will return no records. Well done.

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);
        }
}

Here we are validating that the object name provided by the user is not only a valid object in the org that you are querying against, but also verifies that the current user has access to the object. Well done.

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);
        }
}


The last good information is that the REST and SOAP APIs allow end users to submit arbitrary SOQL strings. However, this does not lead to SOQL injection because the APIs include built in checks for sharing and CRUD/FLS permissions. 

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>    
{!$CurrentPage.parameters.q} is passed to HTML parser as it is processing the contents of <script> tag. In this context, the parser is looking for the closing tag: </script> to determine the extent of the script data that should be passed to the Javascript engine.

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: 
Script block is sent to the JavaScript parser, here attacker can try to break out of the Javascript stringe.g. by setting the URL parameter to be input='; ....attacker code here..;//'

  • .        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.

Rather than trying to learn all possible dangerous characters, the developer should learn to identify the sequence of browser parsing passes and apply the corresponding sequence of escaping functions. This will ensure that user data renders properly as text and cannot escape into an execution context. We will focus on Salesforce case.


For Apex and Visualforce applications the platform provides two main mechanisms to avoid cross site scripting:

  • 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

For example, the value of the userInput parameter will be HTML encoded in the following:
<apex:outputText>
     {!$CurrentPage.parameters.userInput} <!-- safe (auto HTML Encoded) -->
</apex:outputText>
     
or here:
<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>
          

The same no auto-encoding  will happen for style tag.

The auto encoding only provides HTML Encoding of <, > and quotes within html attributes. You must perform your own Javascript and URL encoding as well as handle CSS cross site scripting issues.
Built in VisualForce encoding functions:
  • 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)).
Example scenario – Input parameter from User:

<apex:page>
        <srcipt> 
           Document.getElementById(“{!$Component.input}”).innerHTML =
           “{!$CurrentPage.parameters.input}”;
        </script>
        <div id=”input”><div><apex:page>
</apex:page>

The risk here exists because User can provide invalid input parameter such as: %22%22%3Balert(‘error’)%3B%2F%2F which will result in execution of following code:

<script>       
      Document.getElementById(“{!$Component.input}”).innerHTML = “”;
      alert(‘error’);
</script>



<script>
       Document.getElementById(“{!$Component.input}”).innerHTML = “”;
       alert(‘error’);
</script>

Solution: 

The JSENCODE function, which encodes text and merge field values by inserting escape characters before unsafe JavaScript characters, like the double quotation mark (“”).
        

<apex:page>
        <srcipt>
           Document.getElementById(“{!$Component.input}”).innerHTML =
           “{!JSENCODE($CurrentPage.parameters.input)}”;
        </script>
        <div id=”input”><div>
</apex:page>
Cross Site Request Forgery (CSRF)

Web browsers allow GET and POST requests to be made between different web sites.  From definition CSRF occurs when a user visits a malicious web page that makes their browser send requests to your application that the user did not intend. Because the browser always sends the relevant cookies when making requests, requests like this appear to originate from an authenticated user. The malicious site isn’t able to see the results of these requests, but if create, update or delete functionality can be triggered, the malicious site may be able to perform unauthorized actions

This can be done with the src attribute of the IMG, IFRAME or other tags and more complicated requests, including POSTs, can be made using JavaScript.

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 :)

See you soon,
Monika on behalf of Salesforce Freaks ;) 

Resources:
  • https://developer.salesforce.com/docs/
  • https://focusonforce.com/courses/platform-developer-1-study-guide/


Comments

Popular posts from this blog

How to start work with Anypoint Studio?

Fetch dependent picklist values depending on record type