From my colleague Reid at http://blog.sforce.com/sforce/2011/05/application-initialization-patterns-from-forcecom-labs.html
The chief reason for this is to help prevent a class of attacks called "Cross-Site Request Forgery" or CSRF's. More information can be found at
So what's a CSRF? Consider an online shopping website, which for this post we'll call "llamazon.biz", where you can buy high-quality Peruvian Llamas and have them drop-shipped anywhere on earth with the touch of a button. http://www.llamazon.biz/buy-llama.html might have a form on it that looks like…
<input type="text" name="shipping_address"></input>
<input type="text" name="quantity"></input>
<select name="llama_type">
<option value="city">City Llama</option>
<option value="country">Country Llama</option>
<option value="coolranch">Zesty Cool Ranch Llama</option>
</select>
<input type="submit" value="Submit">Buy it!</input>
</form>
But it gets worse! With just a tiny amount of javascript, the user doesn't even have to click anything. The evil-hacker.com page could look like…
<input type="hidden" name="quantity" value="100" />
<input type="hidden name="llama_type" value="3" />
<input type="hidden" value="Submit" />
</form>
<script>document.getElementById("llamas-to-pgh").submit();</script>
<input type="hidden" name="_CONFIRMATIONTOKEN" value="xxyyzz" />
Where "xxyyzz" is usually something like a SHA-256 hash of the user's llamazon.biz cookie concatenated with some other stuff. The computation happens on the server-side, and the browser doesn't have to do any math. It just echoes the string back on form submission. It's usually not the llamazon.biz cookie itself, but a hash of it, for a variety of reasons. Consider what happens if the user's machine is shared by a family. If mom logs out and dumps her cookies, the llamazon.biz page itself might still be in cache. Then if snoopy little brother trawls through the cache, he can steal the cookie and buy llamas on mom's credit card, or view mom's purchase history, or any number of inappropriate things, since llamazon.biz will think little brother *is* mom.
The token approach works because the attacker at evil-hacker never actually knows the value of the user's cookie. They just rely on the browser using it on their behalf. Because they don't know the cookie, they can't compute the value of the token. Because they can't compute the token, they can't include it in their malicious <form id="llamas-to-pgh">. Because the server re-computes the token when it receives the form and compares the re-computed token to the form-submitted token, that comparison will fail and no llamas get shipped to Pittsburgh.
So that's a CSRF, and how to mitigate against it. What does that mean to you as a VisualForce/Apex developer?
Whenever you use <apex:form>, the platform automatically creates a CSRF token for you, includes it the <form> HTML element that's sent to the browser, and validates it when the form is submitted. If the validation fails, we don't execute the form, or any action attribute you've assigned to it. That's not the case when you use an action on <apex:page> – there are no CSRF tokens auto-created there.
Here’s an example of how you might run into this pattern. llamazon.biz is a loyal Salesforce.com customer, which they use for inventory control. Specifically, llamas are recorded as standard Contact objects with one slight change; there’s a custom field named “llama_type__c”. It stores a picklist with valid strings “city”, “country”, and “coolranch”.
Since llamazon only deals with the highest-quality llama, they employ a crack team of highly trained caretakers, each of whom personally looks after a dozen or so llamas. In our example, the caretakers are Salesforce users. Further, each llama Contact object has its “owner” field set to the caretaker. The llamas also tend to form very close emotional bonds to one another, so whenever they move from one location to another (ah, the fast-paced life of a jet-setting llama), most of the time all of a caretaker’s charges will all move together. This results in the caretakers doing lots of bulk mailing address update changes.
Hey, wouldn’t it be cool if there was a Force.com page that let the caretakers do a bulk update of mailing addresses of all their llamas? That would be awesome! Oh, but here we run into our unsafe code example. The VF page looks like this:
<h1>you have {!LlamaCount} llama{!LlamaPlural}</h1>
<apex:dataTable value="{!llamas}" var="l" border="1">
<apex:column value="{!l.LastName}"/>
<apex:column value="{!l.Llama_Type__c}"/>
<apex:column value="{!l.MailingStreet}"/>
<apex:column value="{!l.MailingCity}"/>
<apex:column value="{!l.MailingState}"/>
<apex:column value="{!l.MailingCountry}"/>
</apex:dataTable>
</apex:page>
public final List<Contact> llamas {get; set; }
private final Integer llama_count {get; set;}
private final Map<String, String> p;
public Llamas() {
this.llamas = [select id, LastName, MailingStreet, MailingCity, MailingState, MailingPostalCode, MailingCountry, llama_type__c from Contact where Llama_Type__c != NULL];
this.llama_count = this.llamas.size();
this.p = ApexPages.currentPage().getParameters();
}
public Integer getLlamaCount(){ return this.llamas.size(); }
public String getLlamaPlural(){
public void update_llamas_mailing() {
Map<String, String> p = this.p;
for(Contact c : this.llamas){
if(p.get('MailingStreet') != null ){
if(p.get('MailingCity') != null ){
if(p.get('MailingState') != null ){
if(p.get('MailingPostalCode') != null ){
if(p.get('MailingCountry') != null ){
}
update this.llamas;
}
static testMethod void myTest(){
}
https://llamazon.na9.force.com/apex/llamas?mailingcity=Pittsburgh&mailingpostalcode=15213
then update_llama_mailing() would set the MailingCity to Pittsburgh and the MailingPostalCode to 15213 for all of the caretaker’s llamas. The caretaker would also get a nice little table listing all of his charges and their new mailing addresses.
And there’s the flaw! There’s nothing that prevents an attacker from crafting an evil-hacker.com page that contains a bunch of image tags that look like…
<img src=”https://llamazon.na9.force.com/apex/llamas?mailingcity=Pittsburgh&…”>
And if the attacker can trick the caretaker from viewing evil-hacker.com (not even clicking anything, just loading the evil-hacker.com page), then the poor caretaker’s browser will happily send a cookie of to salesforce, and the llamas are off to spend a sultry summer in Pittsburgh.
The solution, as discussed above, is to move the action from <apex:page> to <apex:form>. The new VisualForce page looks like…
<apex:dataTable value="{!llamas}" var="l" border="1">
<apex:column value="{!l.LastName}"/>
<apex:column value="{!l.Llama_Type__c}"/>
<apex:column value="{!l.MailingStreet}"/>
<apex:column value="{!l.MailingCity}"/>
<apex:column value="{!l.MailingState}"/>
<apex:column value="{!l.MailingPostalCode}"/>
<apex:column value="{!l.MailingCountry}"/>
</apex:dataTable>
<apex:form >
<apex:pageBlock title="Bulk Update Llama Mailing Address">
<apex:messages />
<apex:pageBlockButtons >
<apex:commandButton value="Save" action="{!update_llamas_mailing}"/>
</apex:pageBlockButtons>
Street: <apex:inputText value="{!fl.MailingStreet}" /><p />
City: <apex:inputText value="{!fl.MailingCity}" /><p />
State: <apex:inputText value="{!fl.MailingState}" /><p />
Postal Code: <apex:inputText value="{!fl.MailingPostalCode}" /><p />
Country: <apex:inputText value="{!fl.MailingCountry}" /><p />
</apex:pageBlock>
</apex:form>
</apex:page>
private final Integer llama_count {get; set;}
public Map<String, String> p {get; set; }
public Contact fl {get; set; } // fake llama!
public Llamas() {
this.llamas = [select id, LastName, MailingStreet, MailingCity, MailingState, MailingPostalCode, MailingCountry, llama_type__c from Contact where Llama_Type__c != NULL];
this.llama_count = this.llamas.size();
this.p = ApexPages.currentPage().getParameters();
this.fl = new Contact();
}
public Integer getLlamaCount(){
return this.llamas.size();
}
public String getLlamaPlural(){
return this.llamas.size() == 1 ? '' : 's';
}
public void update_llamas_mailing() {
Map<String, String> p = this.p;
for(Contact c : this.llamas){
if(fl.MailingStreet != null) { c.MailingStreet = fl.MailingStreet; }
if(fl.MailingCity != null) { c.MailingCity = fl.MailingCity; }
if(fl.MailingState != null) { c.MailingState = fl.MailingState; }
if(fl.MailingPostalCode != null) { c.MailingPostalCode = fl.MailingPostalCode; }
if(fl.MailingCountry != null) { c.MailingCountry = fl.MailingCountry; }
}
update this.llamas;
}
static testMethod void myTest(){
Llamas test_llama = new Llamas();
System.assert(test_llama.getLlamaCount() >= 0);
}
}
That big-ugly-long-base64-string? That’s the CSRF token, which the platform has also auto-provided us with, as discussed above. If it’s absent (or incorrect) when the page is requested, the platform won’t call update_llamas_mailing(), and we’re safe from CSRF.
So why are we so capricious? Why do we protect you on <apex:form> but not <apex:page>? The problem is that if we required valid tokens for <apex:page>, we'd break direct navigation. Right now, I can take the link https://llamazon-app.na1.force.com/00A0000abcdef that points to some object, and mail it or IM it to coworkers, and they can just click it and load it in their browser. If the force.com platform were to require a valid token for all <apex:page>'s, I'd have no way to do that sort of regular casual link sharing we all take for granted.
Now, as I said above, you may (rightly) be thinking that there are a whole slew of <apex:page> actions you could do that it doesn't matter if an attacker can trick a user into executing. If your action just does a basic SOQL query for inclusion on the page, but doesn't actually create, update, or delete any objects in the database (or do ANYTHING ELSE), you're fine. Remember, the attacker in a CSRF is blind – they never see the results of the request, so they can't eavesdrop on it, either.
When you're trying to decide if something needs CSRF protection, the question you should always ask yourself is:
"If this results in something happening that makes an innocent user say 'But I didn't do that!', you need a CSRF token"
While creating, updating, and deleting database objects covers most of that ground, it doesn't cover everything. If your action does any HTTP callouts (like say, a 3rd party API), and that results in something happening in the 3rd party's database, you need a CSRF token. If your action code sends mail, you need a CSRF token. If your object is sensitive, and you're under a regulatory requirement to have audit log records every time the object is viewed (not just edited), you should really, really have a CSRF token to help maintain the integrity of the audit log.