In my project an User can create a Customer and assigning it zero or more Tag. These entities have a relation with User of course. This is done by a form that has a tag field of entity type, filtered by current logged user:
$user = $this->securityContext->getToken()->getUser();
$builder
->add('tags', 'meta_selector', array(
'label' => 'Tag',
'class' => 'Acme\HelloBundle\Entity\Tag',
'property' => 'select_label',
'query_builder' => function(EntityRepository $er) use($user) {
$qb = $er->createQueryBuilder('t');
return $qb
->where($qb->expr()->eq('t.user_id', ':user')
->orderBy('t.name')
->setParameter('user', $user);
}
))
;
And this is working fine. Looking at a generated HTML tags are rendered as checkboxes:
<div class="controls">
<label class="checkbox">
<input type="checkbox" value="2" name="customer[tags][2]"
id="customer_tags_2"> A Tag
</label>
<label class="checkbox">
<input type="checkbox" value="3" name="customer[tags][3]"
id="customer_tags_3"> Another Tag
</label>
</div>
I’d like to investigate further about form tampering. In particular making a POST request from a trusted user adding customer%5Btags%5D%5B1%5D=1, that is a tag with id equal to 1 which exists but it has been created by another user. Attacker user is creating an customer with a tag created by another user:
POST http://localhost/Symfony2/web/app_dev.php/app/customers/new HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-it,it;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost/Symfony2/web/app_dev.php/app/customers/new
Cookie: PHPSESSID=3avu1a2a1eufthr5tdftuhrnn7; hl=it
Content-Type: application/x-www-form-urlencoded
Content-Length: 276
customer%5Bfirst%5D=fake&customer%5Blast%5D=fake&customer%5Bgender%5D=m&customer%5Bbirthday%5D=&customer%5Bemail%5D=&customer%5Bmobile%5D=&customer%5Baddress%5D=&customer%5Bcountry%5D=IT&customer%5Btags%5D%5B1%5D=1&customer%5B_token%5D=455783fa2f866677669c9034a90554b9f75d68b4
.. and seems there is some sort of control that prevents this. Result is 200 OK (should be a 302 in case of success) without any error and form is rendered again. Of course entity is not persisted.
Actual question is: how Symfony 2 protect from this kind of form “tampering” attacks? A possible explanation is the it checks that submitted tags exist inside the collection returned by the form builder. But a reference is needed…
EDIT: even disabling CSRF protection the result is the same. By the way i was passing a valid token and CSRF is intended to protect from other types of attacks.
The answer to your question can be explained quite easily. Every choice field (and the entity type is a specialization of the choice type) has a list of choices. For each choice, the field is aware about
Taginstance)Tag)When you submit the form, the choice field looks in this list which model representation matches the submitted view representation. If none can be found, the field remains unassigned.
The code for this logic can be found in the class ChoiceList and its descendants, in your case EntityChoiceList. Upon submission, the method getChoicesForValues() is executed which does the lookup and is optimized for speed.