浏览代码

Fix #1667 - CRUDController::deleteAction, batchActionDelete & batchAction are vulnerable to CRSF

Thomas Rabaix 11 年之前
父节点
当前提交
de01ce8af4

+ 60 - 17
Controller/CRUDController.php

@@ -22,6 +22,7 @@ use Symfony\Component\HttpFoundation\Request;
 use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
 use Sonata\AdminBundle\Admin\BaseFieldDescription;
 use Sonata\AdminBundle\Util\AdminObjectAclData;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
 
 class CRUDController extends Controller
 {
@@ -177,9 +178,10 @@ class CRUDController extends Controller
         $this->get('twig')->getExtension('form')->renderer->setTheme($formView, $this->admin->getFilterTheme());
 
         return $this->render($this->admin->getTemplate('list'), array(
-            'action'   => 'list',
-            'form'     => $formView,
-            'datagrid' => $datagrid
+            'action'     => 'list',
+            'form'       => $formView,
+            'datagrid'   => $datagrid,
+            'csrf_token' => $this->getCsrfToken('sonata.batch'),
         ));
     }
 
@@ -230,6 +232,9 @@ class CRUDController extends Controller
         }
 
         if ($this->getRestMethod() == 'DELETE') {
+            // check the csrf token
+            $this->validateCsrfToken('sonata.delete');
+
             try {
                 $this->admin->delete($object);
 
@@ -252,8 +257,9 @@ class CRUDController extends Controller
         }
 
         return $this->render($this->admin->getTemplate('delete'), array(
-            'object' => $object,
-            'action' => 'delete'
+            'object'     => $object,
+            'action'     => 'delete',
+            'csrf_token' => $this->getCsrfToken('sonata.delete')
         ));
     }
 
@@ -396,13 +402,19 @@ class CRUDController extends Controller
             $idx          = $this->get('request')->get('idx');
             $all_elements = $this->get('request')->get('all_elements');
             $data         = $this->get('request')->request->all();
+
+            unset($data['_csrf_token']);
         }
 
+
         $batchActions = $this->admin->getBatchActions();
         if (!array_key_exists($action, $batchActions)) {
             throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
         }
 
+        // check the csrf token
+        $this->validateCsrfToken('sonata.batch');
+
         $camelizedAction = BaseFieldDescription::camelize($action);
         $isRelevantAction = sprintf('batchAction%sIsRelevant', ucfirst($camelizedAction));
 
@@ -431,10 +443,11 @@ class CRUDController extends Controller
             $formView = $datagrid->getForm()->createView();
 
             return $this->render($this->admin->getTemplate('batch_confirmation'), array(
-                'action'   => 'list',
-                'datagrid' => $datagrid,
-                'form'     => $formView,
-                'data'     => $data,
+                'action'     => 'list',
+                'datagrid'   => $datagrid,
+                'form'       => $formView,
+                'data'       => $data,
+                'csrf_token' => $this->getCsrfToken('sonata.batch'),
             ));
         }
 
@@ -723,7 +736,7 @@ class CRUDController extends Controller
 
     /**
      * Gets ACL users
-     * 
+     *
      * @return \Traversable
      */
     protected function getAclUsers()
@@ -738,7 +751,7 @@ class CRUDController extends Controller
                 $aclUsers = $userManager->findUsers();
             }
         }
-        
+
         return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
     }
 
@@ -757,7 +770,7 @@ class CRUDController extends Controller
         if (!$this->admin->isAclEnabled()) {
             throw new NotFoundHttpException('ACL are not enabled for this admin');
         }
-        
+
         $id = $this->get('request')->get($this->admin->getIdParameter());
 
         $object = $this->admin->getObject($id);
@@ -780,7 +793,7 @@ class CRUDController extends Controller
             $aclUsers,
             $adminObjectAclManipulator->getMaskBuilderClass()
         );
-        
+
         $form = $adminObjectAclManipulator->createForm($adminObjectAclData);
 
         $request = $this->getRequest();
@@ -797,11 +810,11 @@ class CRUDController extends Controller
         }
 
         return $this->render($this->admin->getTemplate('acl'), array(
-            'action' => 'acl',
+            'action'      => 'acl',
             'permissions' => $adminObjectAclData->getUserPermissions(),
-            'object' => $object,
-            'users' => $aclUsers,
-            'form' => $form->createView()
+            'object'      => $object,
+            'users'       => $aclUsers,
+            'form'        => $form->createView()
         ));
     }
 
@@ -817,4 +830,34 @@ class CRUDController extends Controller
              ->getFlashBag()
              ->add($type, $message);
     }
+
+    /**
+     * Validate CSRF token for action with out form
+     *
+     * @param string $intention
+     *
+     * @throws \RuntimeException
+     */
+    public function validateCsrfToken($intention)
+    {
+        if (!$this->container->has('form.csrf_provider')) {
+            return;
+        }
+
+        if (!$this->container->get('form.csrf_provider')->isCsrfTokenValid($intention, $this->get('request')->request->get('_sonata_csrf_token', false))) {
+            throw new \RuntimeException("The csrf token is not valid, CSRF attack ?");
+        }
+    }
+
+    /**
+     * @param $intention
+     */
+    public function getCsrfToken($intention)
+    {
+        if (!$this->container->has('form.csrf_provider')) {
+            return false;
+        }
+
+        return $this->container->get('form.csrf_provider')->generateCsrfToken($intention);
+    }
 }

+ 1 - 0
Resources/views/CRUD/base_list.html.twig

@@ -24,6 +24,7 @@ file that was distributed with this source code.
     {% if admin.datagrid.results|length > 0 %}
         {% if admin.hasRoute('batch') %}
         <form action="{{ admin.generateUrl('batch', {'filter': admin.filterParameters}) }}" method="POST" >
+            <input type="hidden" name="_sonata_csrf_token" value="{{ csrf_token }}" />
         {% endif %}
             <table class="table table-bordered table-striped">
                 {% block table_header %}

+ 1 - 0
Resources/views/CRUD/batch_confirmation.html.twig

@@ -34,6 +34,7 @@ file that was distributed with this source code.
             <form action="{{ admin.generateUrl('batch', {'filter': admin.filterParameters}) }}" method="POST" >
                 <input type="hidden" name="confirmation" value="ok" />
                 <input type="hidden" name="data" value="{{ data|json_encode }}" />
+                <input type="hidden" name="_sonata_csrf_token" value="{{ csrf_token }}" />
 
                 <div style="display: none">
                     {{ form_rest(form) }}

+ 2 - 1
Resources/views/CRUD/delete.html.twig

@@ -29,7 +29,8 @@ file that was distributed with this source code.
 
         <div class="well form-actions">
             <form method="POST" action="{{ admin.generateObjectUrl('delete', object) }}">
-                <input type="hidden" value="DELETE" name="_method" />
+                <input type="hidden" name="_method" value="DELETE" />
+                <input type="hidden" name="_sonata_csrf_token" value="{{ csrf_token }}" />
 
                 <input type="submit" class="btn btn-danger" value="{{ 'btn_delete'|trans({}, 'SonataAdminBundle') }}" />