Salesforce - Developer - Security - Open Redirect

Security

// Salesforce - Developer - Security - Open Redirects:

Wherever possible, Salesforce has made sure that no open redirects are included 
with the Force.com platform. Due to the extensibility and flexibility of the 
Visualforce and Apex programming languages, it is possible to create open 
redirects in your custom code. A common open redirect vulnerability would 
appear like this:

PageReference redirect = new PageReference(
  ApexPages.currentPage().getParameters().get('redirect')
);
redirect.setRedirect(true);
return redirect;

When the redirect parameter is retrieved from the Visualforce page, it is passed 
directly into the pagereference with setRedirect(true). When redirect is 
returned, the Visualforce page will redirect to this URL with no validation.  
This flexibility is necessary for developers, but it also comes with a 
responsibility for responsible coding! 

The demo section below contains a common Visualforce mass edit form. The "Save" 
button uses server side functionality (Apex) to redirect to the "onSave" URL 
parameter.  The "Cancel" button uses client side functionality to redirect to 
the "onCancel" URL parameter.  To simplify the demo, the page reloads to add the 
above url parameters when they are not found.

The purpose of this demo is to show how an open redirect can manifest in 
Salesforce. Walk through the steps below to attack the vulnerability.

1. To begin with, look at the URL bar and note the two parameters that contain 
   URLs. The open redirect vulnerability occurs when a URL redirect blindly 
   trusts persisted user input.

2. To attack this potential vulnerability, we need to replace the benign URLs 
   with malicious URLs. Since we don't want to use actual malicious URLs lets 
   use this Rick Astley music video and Rickroll our target: 
   https://www.youtube.com/watch?v=dQw4w9WgXcQ

3. Replace the onSave value with "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

4. Replace the onCancel value with "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

5. Hit enter to navigate to your URL containing your two open redirect attacks.

6. Now, it's time to verify the attack. Click Save and you should be directed 
   to the video.

7. Now hit back.

8. This time, hit the cancel button, and the result should be the same.

9. For extra credit, see which kinds of entries you can assign to the URL 
   parameters to successfully redirect. 

Standard Redirect Protections:

The salesforce platform has certain predefined URL parameters that are validated 
to ensure they do not redirect to an external domain.

Examples:

1. startURL
2. retURL
3. saveURL
4. cancelURL

For example, if we go to home/home.jsp?retURL=http://www.google.com, Salesforce 
will display the "Invalid Page Redirection".

While it is possible to use these same parameters in Apex controllers and 
javascript, these protections are not actually fully extended to your code. 
Apex and Visualforce are case-insensitive while these protections are case 
sensitive. Do not rely on these standard URL parameters for your custom code. 

The standard Salesforce redirect parameters are case sensitive.

In this demo, you will experience how using standard open redirect protections 
with Visualforce and Apex can give you a false sense of security and make your 
code vulnerable.  Look at the retURL parameter in the demo URL and you will see 
it has a URL target. If you click "Save" or "Cancel", the controller will 
redirect to this location.

Try changing the target of retURL to our Rick Astley attack URL (
https://www.youtube.com/watch?v=dQw4w9WgXcQ) and hit enter.

When you hit enter, you will see the standard open redirect protection error 
since you have pointed retURL to an external website.

Recall in the previous demo that the standard open redirect protections are only 
applied to the case sensitive "retURL." This causes the illusion of protection 
with Visualforce pages and Apex controllers for a very tricky reason... 

Change "retURL" to "retURl" (de-capitalizing the final L) and hit enter. The 
page will load as normal now and the standard protections are no longer applied.

Hit "Save" or "Cancel" and the page will be redirected to the soothing sounds of
 Rick Astley. Why did our attack work?

The standard protections are case sensitive, but Apex is case insensitive. To 
Apex "retURL" == "RETURL" == "returl" == "retURl" ... A clever attacker would 
understand this difference and be able to expoit the gap in the code to perform 
open redirect attacks.

For this reason, you cannot rely on the standard protections alone  in your 
custom development. Additional mitigations must be applied in Apex which we will 
teach you in the following demos.

Mitigating Open Redirect In Force.com Code:

1. Hardcode all redirects

2. Force only local redirects

3. Whitelist redirects.

The two methods we provide are:

1. Relative URL's Only: This defense works by taking all open redirects and 
   allowing them to only redirect to the local domain. The assumption here is 
   that local domains are much safer than the broader internet. To understand 
   how this fix works, consider the following.  To begin, you are on the page 
   https://na17.salesforce.com/apex/visualforce?url=http://www.evil-attacker.com
   If you were to perform some action that invokes a redirect, the controller 
   would take the target URL and transform it to this:
   newURL = '/http://www.evil-attacker.com'
   When this newURL variable is used in a redirect, the resulting webpage is:
   https://na17.salesforce.com/http://www.evil-attacker.com
   This URL returns an error, but it does not send you to the attacker 
   controlled domain. It might also help you realize that something was wrong!

2. Whitelisted URL's - This defense is a little more flexible that the relative 
   URL defense. The developer can build a list of acceptable domains for url 
   redirection, which may include acceptable and safe external domains. 
   Whenever a redirect is performed, the domain of that redirect is compared 
   against the domains in the whitelist. If there is a match, the redirect is 
   performed. Salesforce uses this exact same defense to allow various 
   redirection targets like:

   a. *.visual.force.com
   b. *.salesforce.com
   c. *.content.force.com

In this demo you will learn an approach to mitigating open redirect 
vulnerabilities by forcing all redirects to become relative URL's.  We do this 
by prefixing the url with '/' . Here is what happens:

1. Original URL: 'http://www.google.com'
2. Relative URL: '/http://www.google.com'
3. Redirected Page: https://na1.salesforce.com/http://www.google.com

To force a relative URL in Apex, you will start by doing this:

savePage = new PageReference('/' + onSave);

This isn't the full solution however, as it comes with a very serious gap. 
Return to the demo page, set retURl=/www.youtube.com/watch?v=dQw4w9WgXcQ and 
hit enter. Click Save.  The attack will succeed. Why is that? This is because 
'/' + '/www.youtube.com/watch?v=dQw4w9WgXcQ' = 
'//www.youtube.com/watch?v=dQw4w9WgXcQ' which is valid browser syntax! 

Forcing redirects to be relative is a strong solution, but only when done 
correctly. The following mistakes are critical to avoid:  

1. Blindly adding a single '/' is insecure if the string already starts with a 
   '/' because //www.google.com is valid browser syntax.

2. Checking for '//' and replacing it with '/' is also bad because 
   '///www.google.com' becomes the valid '//www.google.com'

To truly force a relative URL in Apex, we will need to use a regex that removes 
all leading forward slashes and replaces them with a single forward slash.

Find this code:

savePage = new PageReference('/' + onSave);

And change it to:

if(onSave.startsWith('/')){
     onSave.replaceFirst('/+','');
}
savePage = new PageReference('/'+onSave);

We can also do the same fix on the client side.  The best approach is to use a 
regular expression to replace all leading '/' with '' and then add a new 
leading '/' like this:

cancelURL = '/'+cancelURL.replace(/^\/+/g,'');

The key component of this regex is ^\/+ which says "all leading forward slashes"

In this demo, you will be learning how to protect against open redirect by using 
a url whitelist. This method allows more flexibility than forcing relative url's 
because it lets you redirect to specific external url's that are included in 
the Whitelist.

public class URL_Validation_Protection_Demo {

    public List<Account> accounts {get;set;}

    public URL_Validation_Protection_Demo(){
        accounts = new List<Account>();
        for(Account account : [SELECT name, type, industry FROM Account LIMIT 5]){
            accounts.add(account);
        } 
    }

    public pageReference seedURL(){
        pageReference p = page.URL_Validation_Protection_Demo;
        String retURL = ApexPages.currentPage().getParameters().get('retURl');
        String cancelURL = ApexPages.currentPage().getParameters().get('cancelURl');
        if(string.isBlank(retURL)||string.isBlank(cancelURL)){      
            p.getParameters().put('retURl', 
                'https://www.youtube.com/watch?v=dQw4w9WgXcQ');
            p.getParameters().put('cancelURl', 
                'https://www.youtube.com/watch?v=dQw4w9WgXcQ');
            p.setRedirect(true);
            return p;
        }
        return null;      
    }

    /*
    Task #1
    Below is an apex method that can be used to validate URLs. Any URL with a 
    host not inside the whiteListedDomains set is made into a relative URL. 
    This can be useful when you need to redirect to a website outside of the 
    local domain without providing an open redirect vulnerability.

    Currently the whitelist contains the current domain, and www.salesforce.com. 
    For fun, we are going to add Youtube to the whitelisted domains so we can 
    view that Rick Astley video one more time.

    Once www.youtube is added to the whitelist, proceed to Task #2.
    */

    public PageReference validateURL(string stringURL){
        URL currentURL = New URL('https://' + ApexPages.currentPage().getUrl());
        Set<String> whiteListedDomains = new Set<String>();
            whiteListedDomains.add(currentURL.getHost());
            whiteListedDomains.add('www.salesforce.com');
            // Add another whitelist entry here for www.youtube.com

        Pagereference validatedURL;
        //If the string is blank, give a default URL
        if(string.isBlank(stringURL)){
            validatedURL= new PageReference('/home/home.jsp');
        //If the domain is in the whitelist, construct a secure URL
        } else if(whiteListedDomains.contains(New URL(stringURL).getHost())){
            validatedURL= new PageReference('https://' + 
            New URL(stringURL).getHost() + New URL(stringURL).getPath()+ '?' + 
            New URL(stringURL).getQuery()); 
        //If not null, and not in the whitelist, make it a relative URL
        } else {
            if(stringURL.startsWith('/')){
                stringURL.replaceFirst('/+','');  
            }
            validatedURL = new PageReference('/'+stringURL);
        }    

        return validatedURL;
    }       

    /*
    Task #2
    Now we need to make sure that the whitelist is used to validate the retURl. 
    Since the validateURL method returns a pageReference, it would be easiest 
    to replace:

    savePage = new PageReference(onSave);

    with 

    savePage = validateURL(onSave);

    Do so below and save, then return to the demo page to continue.
    */

    public PageReference save(){
        PageReference savePage;
        if (Schema.SObjectType.Account.isUpdateable()){
            try{
                update accounts;
                String onsave = ApexPages.currentPage().getParameters().
                    get('retURl');
                if(string.isBlank(onSave)){
                    onSave = '/home/home.jsp';
                }
                savePage = new PageReference(onSave);
                savePage.setRedirect(true);
                return savePage;
            }catch (exception e){
                ApexPages.addmessage(new ApexPages.message(
                    ApexPages.severity.ERROR, 
                        'Unable to update Accounts.  Exception: ' + 
                        e.getMessage()));
                return null;
            } 
        }else{
            ApexPages.addmessage(new ApexPages.message(
                ApexPages.severity.ERROR, 
                'You do not have permission to update accounts'));
            return null;
        }
    } 
}

Task #3 Client Side URL Validation Script:

The "Cancel" button has all it's redirect functionality client side. Rather 
than re-design it to function server side like the "Save" button, we have 
included a script below that can be used to whitelist client side redirects. 
When the page loads, this script will check for cancelURL  (it is not case 
sensitive) and validate it against the white list. If it fails, it makes it a 
relative URL like in the previous demo. in the Salesforce domain.

This approach allows for more flexibility (like if an external domain is 
required for redirect), but is more complicated to write. Delete the comment 
tags wrapped around the script block below and save the page to enable the 
protection.

<script type="text/javascript">
    var currentUrl = "{!JSENCODE($CurrentPage.Url)}";
    var currentUrlParser = document.createElement('a');
    currentUrlParser.href = currentUrl;

    //Add valid entries like "www.youtube.com" to the whitelist below!
    var whiteListedDomains = ["www.salesforce.com", currentUrlParser.hostname];

    var cancelURL = "{!JSENCODE($CurrentPage.Parameters.cancelURL)}";
    var paramParser = document.createElement('a');
    paramParser.href = cancelURL;

    if(!cancelURL){
        document.getElementById("cancelbutton").setAttribute("href", '/home/home.jsp');
    } else if (whiteListedDomains.indexOf(paramParser.hostname) != -1){
        document.getElementById("cancelbutton").setAttribute("href",paramParser);
    } else {
        cancelURL = '/'+cancelURL.replace(/^\/+/g,'');
        document.getElementById("cancelbutton").setAttribute("href", cancelURL);
    }
</script>

startURL: redirect users to a location on page load

retURL: redirect users to a location when the click the Back button

saveURL: redirect users to a location when they click the Save button

cancelURL: redirect users to a location when they click the Cancel button.
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License