diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index fe6d104..ef1a5da 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -105,6 +105,27 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
   protected $formCache;
 
   /**
+   * Defines element value callables which are safe to run even when the form
+   * state has an invalid CSRF token.
+   *
+   * @todo Drupal 8 has some new form elements, check if any of them could be
+   *   added to this list.
+   *
+   * @var array
+   */
+  protected $safeCoreValueCallables = [
+    '\Drupal\Core\Render\Element\Token::valueCallback',
+    '\Drupal\Core\Render\Element\Textarea::valueCallback',
+    '\Drupal\Core\Render\Element\Textfield::valueCallback',
+    '\Drupal\Core\Render\Element\Checkbox::valueCallback',
+    '\Drupal\Core\Render\Element\Checkboxes::valueCallback',
+    '\Drupal\Core\Render\Element\Radios::valueCallback',
+    '\Drupal\Core\Render\Element\PasswordConfirm::valueCallback',
+    '\Drupal\Core\Render\Element\Select::valueCallback',
+    '\Drupal\Core\Render\Element\Tableselect::valueCallback',
+  ];
+
+  /**
    * Constructs a new FormBuilder.
    *
    * @param \Drupal\Core\Form\FormValidatorInterface $form_validator
@@ -521,11 +542,6 @@ public function processForm($form_id, &$form, FormStateInterface &$form_state) {
 
     // Only process the input if we have a correct form submission.
     if ($form_state->isProcessingInput()) {
-      // Form constructors may explicitly set #token to FALSE when cross site
-      // request forgery is irrelevant to the form, such as search forms.
-      if (isset($form['#token']) && $form['#token'] === FALSE) {
-        unset($form['#token']);
-      }
       // Form values for programmed form submissions typically do not include a
       // value for the submit button. But without a triggering element, a
       // potentially existing #limit_validation_errors property on the primary
@@ -648,25 +664,23 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
     // since tokens are session-bound and forms displayed to anonymous users are
     // very likely cached, we cannot assign a token for them.
     // During installation, there is no $user yet.
-    if ($user && $user->isAuthenticated() && !$form_state->isProgrammed()) {
-      // Form constructors may explicitly set #token to FALSE when cross site
-      // request forgery is irrelevant to the form, such as search forms.
-      if (isset($form['#token']) && $form['#token'] === FALSE) {
-        unset($form['#token']);
-      }
-      // Otherwise, generate a public token based on the form id.
-      else {
-        $form['#token'] = $form_id;
-        $form['form_token'] = array(
-          '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'),
-          '#type' => 'token',
-          '#default_value' => $this->csrfToken->get($form['#token']),
-          // Form processing and validation requires this value, so ensure the
-          // submitted form value appears literally, regardless of custom #tree
-          // and #parents being set elsewhere.
-          '#parents' => array('form_token'),
-        );
-      }
+    // Form constructors may explicitly set #token to FALSE when cross site
+    // request forgery is irrelevant to the form, such as search forms.
+    if ($form_state->isProgrammed() || (isset($form['#token']) && $form['#token'] === FALSE)) {
+      unset($form['#token']);
+    }
+    elseif ($user && $user->isAuthenticated()) {
+      // Generate a public token based on the form id.
+      $form['#token'] = $form_id;
+      $form['form_token'] = array(
+        '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'),
+        '#type' => 'token',
+        '#default_value' => $this->csrfToken->get($form['#token']),
+        // Form processing and validation requires this value, so ensure the
+        // submitted form value appears literally, regardless of custom #tree
+        // and #parents being set elsewhere.
+        '#parents' => array('form_token'),
+      );
     }
 
     if (isset($form_id)) {
@@ -743,6 +757,13 @@ protected function buildFormAction() {
   /**
    * {@inheritdoc}
    */
+  public function setInvalidTokenError(FormStateInterface $form_state) {
+    $this->formValidator->setInvalidTokenError($form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function validateForm($form_id, &$form, FormStateInterface &$form_state) {
     $this->formValidator->validateForm($form_id, $form, $form_state);
   }
@@ -817,6 +838,20 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       $input = $form_state->getUserInput();
       if ($form_state->isProgrammed() || (!empty($input) && (isset($input['form_id']) && ($input['form_id'] == $form_id)))) {
         $form_state->setProcessInput();
+        if (isset($element['#token'])) {
+          $input = $form_state->getUserInput();
+          if (empty($input['form_token']) || !$this->csrfToken->validate($input['form_token'], $element['#token'])) {
+            // Set an early form error to block certain input processing since
+            // that opens the door for CSRF vulnerabilities.
+            $this->setInvalidTokenError($form_state);
+
+            // This value is checked in self::handleInputElement().
+            $form_state->setInvalidToken(TRUE);
+
+            // Make sure file uploads do not get processed.
+            $_FILES = array();
+          }
+        }
       }
       else {
         $form_state->setProcessInput(FALSE);
@@ -1077,7 +1112,14 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // If we have input for the current element, assign it to the #value
         // property, optionally filtered through $value_callback.
         if ($input_exists) {
-          $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
+          // Skip all value callbacks except safe ones like text if the CSRF
+          // token was invalid.
+          if (!$form_state->hasInvalidToken() || in_array($value_callable, $this->safeCoreValueCallables)) {
+            $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
+          }
+          else {
+            $input = NULL;
+          }
 
           if (!isset($element['#value']) && isset($input)) {
             $element['#value'] = $input;
diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php
index 9cb0b0d..bd47f1e 100644
--- a/core/lib/Drupal/Core/Form/FormState.php
+++ b/core/lib/Drupal/Core/Form/FormState.php
@@ -105,6 +105,19 @@ class FormState implements FormStateInterface {
   protected $rebuild = FALSE;
 
   /**
+   * If set to TRUE the form will skip calling form element value callbacks,
+   * except for a select list of callbacks provided by Drupal core that are
+   * known to be safe.
+   *
+   * This property is uncacheable.
+   *
+   * @see self::setInvalidToken()
+   *
+   * @var bool
+   */
+  protected $invalidToken = FALSE;
+
+  /**
    * Used when a form needs to return some kind of a
    * \Symfony\Component\HttpFoundation\Response object, e.g., a
    * \Symfony\Component\HttpFoundation\BinaryFileResponse when triggering a
@@ -1284,6 +1297,21 @@ public function cleanValues() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function setInvalidToken($invalid_token) {
+    $this->invalidToken = (bool) $invalid_token;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasInvalidToken() {
+    return $this->invalidToken;
+  }
+
+  /**
    * Wraps ModuleHandler::loadInclude().
    */
   protected function moduleLoadInclude($module, $type, $name = NULL) {
diff --git a/core/lib/Drupal/Core/Form/FormStateInterface.php b/core/lib/Drupal/Core/Form/FormStateInterface.php
index fd46f70..11b089b 100644
--- a/core/lib/Drupal/Core/Form/FormStateInterface.php
+++ b/core/lib/Drupal/Core/Form/FormStateInterface.php
@@ -562,6 +562,24 @@ public function setRebuild($rebuild = TRUE);
   public function isRebuilding();
 
   /**
+   * Flags the form state as having or not an invalid token.
+   *
+   * @param bool $invalid_token
+   *   Whether the form has an invalid token.
+   *
+   * @return $this
+   */
+  public function setInvalidToken($invalid_token);
+
+  /**
+   * Determines if the form has an invalid token.
+   *
+   * @return bool
+   *   TRUE if the form has an invalid token, FALSE otherwise.
+   */
+  public function hasInvalidToken();
+
+  /**
    * Converts support notations for a form callback to a valid callable.
    *
    * Specifically, supports methods on the form/callback object as strings when
diff --git a/core/lib/Drupal/Core/Form/FormValidator.php b/core/lib/Drupal/Core/Form/FormValidator.php
index 96f2320..10678e3 100644
--- a/core/lib/Drupal/Core/Form/FormValidator.php
+++ b/core/lib/Drupal/Core/Form/FormValidator.php
@@ -105,13 +105,12 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state)
     }
 
     // If the session token was set by self::prepareForm(), ensure that it
-    // matches the current user's session.
+    // matches the current user's session. This is duplicate to code in
+    // FormBuilder::doBuildForm() but left to protect any custom form handling
+    // code.
     if (isset($form['#token'])) {
-      if (!$this->csrfToken->validate($form_state->getValue('form_token'), $form['#token'])) {
-        $url = $this->requestStack->getCurrentRequest()->getRequestUri();
-
-        // Setting this error will cause the form to fail validation.
-        $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+      if (!$this->csrfToken->validate($form_state->getValue('form_token'), $form['#token']) || $form_state->hasInvalidToken()) {
+        $this->setInvalidTokenError($form_state);
 
         // Stop here and don't run any further validation handlers, because they
         // could invoke non-safe operations which opens the door for CSRF
@@ -128,6 +127,16 @@ public function validateForm($form_id, &$form, FormStateInterface &$form_state)
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function setInvalidTokenError(FormStateInterface $form_state) {
+    $url = $this->requestStack->getCurrentRequest()->getRequestUri();
+
+    // Setting this error will cause the form to fail validation.
+    $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+  }
+
+  /**
    * Handles validation errors for forms with limited validation.
    *
    * If validation errors are limited then remove any non validated form values,
diff --git a/core/lib/Drupal/Core/Form/FormValidatorInterface.php b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
index 6f1cc52..d2c8945 100644
--- a/core/lib/Drupal/Core/Form/FormValidatorInterface.php
+++ b/core/lib/Drupal/Core/Form/FormValidatorInterface.php
@@ -54,4 +54,14 @@ public function executeValidateHandlers(&$form, FormStateInterface &$form_state)
    */
   public function validateForm($form_id, &$form, FormStateInterface &$form_state);
 
+  /**
+   * Sets a form_token error on the given form state.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return $this
+   */
+  public function setInvalidTokenError(FormStateInterface $form_state);
+
 }
diff --git a/core/modules/file/src/Tests/FileManagedFileElementTest.php b/core/modules/file/src/Tests/FileManagedFileElementTest.php
index c91c494..9953217 100644
--- a/core/modules/file/src/Tests/FileManagedFileElementTest.php
+++ b/core/modules/file/src/Tests/FileManagedFileElementTest.php
@@ -37,6 +37,18 @@ function testManagedFile() {
           $this->drupalPostForm($path, array(), t('Save'));
           $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), 'Submitted without a file.');
 
+          // Submit with a file, but with an invalid form token. Ensure the file
+          // was not saved.
+          $last_fid_prior = $this->getLastFileId();
+          $edit = [
+            $file_field_name => drupal_realpath($test_file->getFileUri()),
+            'form_token' => 'invalid token',
+          ];
+          $this->drupalPostForm($path, $edit, t('Save'));
+          $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
+          $last_fid = $this->getLastFileId();
+          $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
+
           // Submit a new file, without using the Upload button.
           $last_fid_prior = $this->getLastFileId();
           $edit = array($file_field_name => drupal_realpath($test_file->getFileUri()));
