VYPR
High severityNVD Advisory· Published Jun 11, 2021· Updated Aug 4, 2024

CVE-2020-13663

CVE-2020-13663

Description

Cross Site Request Forgery vulnerability in Drupal Core Form API does not properly handle certain form input from cross-site requests, which can lead to other vulnerabilities.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A CSRF vulnerability in Drupal Core Form API allows an attacker to craft malicious links that can lead to unauthorized actions.

Vulnerability

Drupal Core versions prior to the fix (around 8.8.x, 8.9.x, and 9.0.x) contain a Cross-Site Request Forgery (CSRF) vulnerability in the Form API. The Form API does not properly handle certain form input from cross-site requests, specifically it may accept and process form submissions with an invalid or missing form token, leading to the possibility that a user's session could be exploited to perform unintended actions. The vulnerability is described in SA-CORE-2020-004 and the associated commits [3] show the fix in the FormValidator logic, ensuring that form data is not retained when the token is invalid. Versions affected include Drupal 8.8.x before 8.8.10, 8.9.x before 8.9.6, and 9.0.x before 9.0.6 [2].

Exploitation

An attacker needs to trick a logged-in Drupal user into clicking a crafted link or visiting a malicious page that submits a cross-site form request. The attacker does not need authentication—the victim must be authenticated. The attacker leverages the CSRF vulnerability by crafting a form submission to a Drupal site without a valid CSRF token. If the Form API does not properly reject the submission, the attacker can perform actions on behalf of the victim, such as changing administrative settings or performing any action the victim has permission to do. The exploitation is an indirect attack: the attacker cannot directly interact with the Drupal site; they rely on the victim's session [1][2].

Impact

Successful exploitation of the CSRF vulnerability can lead to other vulnerabilities, as stated in the CVE description [2]. The attacker can perform unauthorized actions on the Drupal site under the victim's session. Depending on the victim's privileges, this could include altering content, user accounts, configuration, or even taking over the site. The impact is a loss of integrity and potentially confidentiality, as the attacker can perform actions without consent. The root cause is the inadequate validation of the CSRF token in the Form API, which can cascade into more severe security issues [2].

Mitigation

The vulnerability is fixed in Drupal versions 8.8.10, 8.9.6, and 9.0.6, released around June 2020 [2]. The patch is visible in the commit faf3243c4ce03bbaab386af2b272b363fd0dfddb and 5f3c4d80fd77df0cfa87722b446db54040d55693 on the Drupal core repository [3][4]. Administrators are advised to update to these or later versions immediately. No workaround is documented other than applying the patch. Drupal is not listed on the known exploited vulnerabilities (KEV) catalog as of this writing.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
drupal/corePackagist
>= 8.9.0, < 8.9.18.9.1
drupal/corePackagist
>= 9.0.0, < 9.0.19.0.1
drupal/corePackagist
>= 7.0.0, < 7.727.72
drupal/corePackagist
>= 8.0.0, < 8.8.88.8.8
drupal/drupalPackagist
>= 7.0.0, < 7.727.72
drupal/drupalPackagist
>= 8.0.0, < 8.8.88.8.8
drupal/drupalPackagist
>= 8.9.0, < 8.9.18.9.1
drupal/drupalPackagist
>= 9.0.0, < 9.0.19.0.1

Affected products

4

Patches

3
bc3235dcb557

SA-CORE-2020-004 by samuel.mortenson, DorTumarkin, greggles, xjm, larowlan, webchick, pwolanin, dawehner, mcdruid, alexpott, dsnopek

https://github.com/drupal/corexjmJun 17, 2020via ghsa
8 files changed · +57 24
  • lib/Drupal/Core/Form/FormBuilder.php+10 1 modified
    @@ -19,6 +19,7 @@
     use Drupal\Core\Theme\ThemeManagerInterface;
     use Symfony\Component\EventDispatcher\EventDispatcherInterface;
     use Symfony\Component\HttpFoundation\FileBag;
    +use Symfony\Component\HttpFoundation\ParameterBag;
     use Symfony\Component\HttpFoundation\RequestStack;
     use Symfony\Component\HttpFoundation\Response;
     
    @@ -956,8 +957,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
                 // This value is checked in self::handleInputElement().
                 $form_state->setInvalidToken(TRUE);
     
    +            // Ignore all submitted values.
    +            $form_state->setUserInput([]);
    +
    +            $request = $this->requestStack->getCurrentRequest();
    +            // Do not trust any POST data.
    +            $request->request = new ParameterBag();
                 // Make sure file uploads do not get processed.
    -            $this->requestStack->getCurrentRequest()->files = new FileBag();
    +            $request->files = new FileBag();
    +            // Ensure PHP globals reflect these changes.
    +            $request->overrideGlobals();
               }
             }
           }
    
  • lib/Drupal/Core/Form/FormValidator.php+1 3 modified
    @@ -124,10 +124,8 @@ 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>.', [':link' => $url]));
    +    $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.'));
       }
     
       /**
    
  • modules/file/tests/src/Functional/FileManagedFileElementTest.php+1 1 modified
    @@ -50,7 +50,7 @@ public function testManagedFile() {
                 $file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
               ];
               $this->drupalPostForm(NULL, $edit, t('Save'));
    -          $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +          $this->assertText('The form has become outdated.');
               $last_fid = $this->getLastFileId();
               $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
     
    
  • modules/system/tests/src/Functional/Form/FormTest.php+23 15 modified
    @@ -251,21 +251,27 @@ public function testInputWithInvalidToken() {
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
    +    $random_string = $this->randomString();
         $edit = [
    -      'textfield' => $this->randomString(),
    +      'textfield' => $random_string,
           'checkboxes[bar]' => TRUE,
           'select' => 'bar',
           'radios' => 'foo',
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    // Verify that input elements retained the posted values.
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    +
    +    $assert = $this->assertSession();
    +    $element = $assert->fieldExists('textfield');
    +    $this->assertEmpty($element->getValue());
    +    $assert->responseNotContains($random_string);
    +    $this->assertText('The form has become outdated.');
    +    // Ensure that we don't use the posted values.
    +    $this->assertFieldByName('textfield', '');
         $this->assertNoFieldChecked('edit-checkboxes-foo');
    -    $this->assertFieldChecked('edit-checkboxes-bar');
    -    $this->assertOptionSelected('edit-select', 'bar');
    -    $this->assertFieldChecked('edit-radios-foo');
    +    $this->assertNoFieldChecked('edit-checkboxes-bar');
    +    $this->assertOptionSelected('edit-select', '');
    +    $this->assertNoFieldChecked('edit-radios-foo');
     
         // Check another form that has a textarea input.
         $this->drupalGet(Url::fromRoute('form_test.required'));
    @@ -278,22 +284,24 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    -    $this->assertFieldByName('textarea', $edit['textarea']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('textfield', '');
    +    $this->assertFieldByName('textarea', '');
     
         // Check another form that has a number input.
         $this->drupalGet(Url::fromRoute('form_test.number'));
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
         $edit = [
    -      'integer_step' => mt_rand(1, 100),
    +      // We choose a random value which is higher than the default value,
    +      // so we don't accidentally generate the default value.
    +      'integer_step' => mt_rand(6, 100),
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('integer_step', $edit['integer_step']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('integer_step', 5);
     
         // Check a form with a Url field
         $this->drupalGet(Url::fromRoute('form_test.url'));
    @@ -305,8 +313,8 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('url', $edit['url']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('url', '');
       }
     
       /**
    
  • modules/system/tests/src/Functional/Form/ValidationTest.php+1 1 modified
    @@ -74,7 +74,7 @@ public function testValidate() {
         $this->drupalPostForm(NULL, ['name' => 'validate'], 'Save');
         $this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
         $this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +    $this->assertText('The form has become outdated.');
       }
     
       /**
    
  • tests/Drupal/Tests/Core/Form/FormBuilderTest.php+19 1 modified
    @@ -802,12 +802,30 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated
         $expected_form = $form_id();
         $form_arg = $this->getMockForm($form_id, $expected_form);
     
    +    // Set up some request data so we can be sure it is removed when a token is
    +    // invalid.
    +    $this->request->request->set('foo', 'bar');
    +    $_POST['foo'] = 'bar';
    +
         $form_state = new FormState();
         $input['form_id'] = $form_id;
         $input['form_token'] = $form_token;
    +    $input['test'] = 'example-value';
         $form_state->setUserInput($input);
    -    $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
    +    $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
         $this->assertSame($expected, $form_state->hasInvalidToken());
    +    if ($expected) {
    +      $this->assertEmpty($form['test']['#value']);
    +      $this->assertEmpty($form_state->getValue('test'));
    +      $this->assertEmpty($_POST);
    +      $this->assertEmpty(iterator_to_array($this->request->request->getIterator()));
    +    }
    +    else {
    +      $this->assertEquals('example-value', $form['test']['#value']);
    +      $this->assertEquals('example-value', $form_state->getValue('test'));
    +      $this->assertEquals('bar', $_POST['foo']);
    +      $this->assertEquals('bar', $this->request->request->get('foo'));
    +    }
       }
     
       public function providerTestInvalidToken() {
    
  • tests/Drupal/Tests/Core/Form/FormTestBase.php+1 1 modified
    @@ -173,7 +173,7 @@ protected function setUp() {
           ->getMock();
         $this->account = $this->createMock('Drupal\Core\Session\AccountInterface');
         $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface');
    -    $this->request = new Request();
    +    $this->request = Request::createFromGlobals();
         $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
         $this->requestStack = new RequestStack();
         $this->requestStack->push($this->request);
    
  • tests/Drupal/Tests/Core/Form/FormValidatorTest.php+1 1 modified
    @@ -131,7 +131,7 @@ public function testValidateInvalidFormToken() {
           ->getMock();
         $form_state->expects($this->once())
           ->method('setErrorByName')
    -      ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
    +      ->with('form_token', 'The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.');
         $form_state->setValue('form_token', 'some_random_token');
         $form_validator->validateForm('test_form_id', $form, $form_state);
         $this->assertTrue($form_state->isValidationComplete());
    
faf3243c4ce0

SA-CORE-2020-004 by samuel.mortenson, DorTumarkin, greggles, xjm, larowlan, webchick, pwolanin, dawehner, mcdruid, alexpott, dsnopek

https://github.com/drupal/corexjmJun 17, 2020via ghsa
8 files changed · +57 24
  • lib/Drupal/Core/Form/FormBuilder.php+10 1 modified
    @@ -19,6 +19,7 @@
     use Drupal\Core\Theme\ThemeManagerInterface;
     use Symfony\Component\EventDispatcher\EventDispatcherInterface;
     use Symfony\Component\HttpFoundation\FileBag;
    +use Symfony\Component\HttpFoundation\ParameterBag;
     use Symfony\Component\HttpFoundation\RequestStack;
     use Symfony\Component\HttpFoundation\Response;
     
    @@ -957,8 +958,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
                 // This value is checked in self::handleInputElement().
                 $form_state->setInvalidToken(TRUE);
     
    +            // Ignore all submitted values.
    +            $form_state->setUserInput([]);
    +
    +            $request = $this->requestStack->getCurrentRequest();
    +            // Do not trust any POST data.
    +            $request->request = new ParameterBag();
                 // Make sure file uploads do not get processed.
    -            $this->requestStack->getCurrentRequest()->files = new FileBag();
    +            $request->files = new FileBag();
    +            // Ensure PHP globals reflect these changes.
    +            $request->overrideGlobals();
               }
             }
           }
    
  • lib/Drupal/Core/Form/FormValidator.php+1 3 modified
    @@ -124,10 +124,8 @@ 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>.', [':link' => $url]));
    +    $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.'));
       }
     
       /**
    
  • modules/file/tests/src/Functional/FileManagedFileElementTest.php+1 1 modified
    @@ -50,7 +50,7 @@ public function testManagedFile() {
                 $file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
               ];
               $this->drupalPostForm(NULL, $edit, t('Save'));
    -          $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +          $this->assertText('The form has become outdated.');
               $last_fid = $this->getLastFileId();
               $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
     
    
  • modules/system/tests/src/Functional/Form/FormTest.php+23 15 modified
    @@ -251,21 +251,27 @@ public function testInputWithInvalidToken() {
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
    +    $random_string = $this->randomString();
         $edit = [
    -      'textfield' => $this->randomString(),
    +      'textfield' => $random_string,
           'checkboxes[bar]' => TRUE,
           'select' => 'bar',
           'radios' => 'foo',
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    // Verify that input elements retained the posted values.
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    +
    +    $assert = $this->assertSession();
    +    $element = $assert->fieldExists('textfield');
    +    $this->assertEmpty($element->getValue());
    +    $assert->responseNotContains($random_string);
    +    $this->assertText('The form has become outdated.');
    +    // Ensure that we don't use the posted values.
    +    $this->assertFieldByName('textfield', '');
         $this->assertNoFieldChecked('edit-checkboxes-foo');
    -    $this->assertFieldChecked('edit-checkboxes-bar');
    -    $this->assertOptionSelected('edit-select', 'bar');
    -    $this->assertFieldChecked('edit-radios-foo');
    +    $this->assertNoFieldChecked('edit-checkboxes-bar');
    +    $this->assertOptionSelected('edit-select', '');
    +    $this->assertNoFieldChecked('edit-radios-foo');
     
         // Check another form that has a textarea input.
         $this->drupalGet(Url::fromRoute('form_test.required'));
    @@ -278,22 +284,24 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    -    $this->assertFieldByName('textarea', $edit['textarea']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('textfield', '');
    +    $this->assertFieldByName('textarea', '');
     
         // Check another form that has a number input.
         $this->drupalGet(Url::fromRoute('form_test.number'));
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
         $edit = [
    -      'integer_step' => mt_rand(1, 100),
    +      // We choose a random value which is higher than the default value,
    +      // so we don't accidentally generate the default value.
    +      'integer_step' => mt_rand(6, 100),
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('integer_step', $edit['integer_step']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('integer_step', 5);
     
         // Check a form with a Url field
         $this->drupalGet(Url::fromRoute('form_test.url'));
    @@ -305,8 +313,8 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('url', $edit['url']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('url', '');
       }
     
       /**
    
  • modules/system/tests/src/Functional/Form/ValidationTest.php+1 1 modified
    @@ -74,7 +74,7 @@ public function testValidate() {
         $this->drupalPostForm(NULL, ['name' => 'validate'], 'Save');
         $this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
         $this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +    $this->assertText('The form has become outdated.');
       }
     
       /**
    
  • tests/Drupal/Tests/Core/Form/FormBuilderTest.php+19 1 modified
    @@ -832,12 +832,30 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated
         $expected_form = $form_id();
         $form_arg = $this->getMockForm($form_id, $expected_form);
     
    +    // Set up some request data so we can be sure it is removed when a token is
    +    // invalid.
    +    $this->request->request->set('foo', 'bar');
    +    $_POST['foo'] = 'bar';
    +
         $form_state = new FormState();
         $input['form_id'] = $form_id;
         $input['form_token'] = $form_token;
    +    $input['test'] = 'example-value';
         $form_state->setUserInput($input);
    -    $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
    +    $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
         $this->assertSame($expected, $form_state->hasInvalidToken());
    +    if ($expected) {
    +      $this->assertEmpty($form['test']['#value']);
    +      $this->assertEmpty($form_state->getValue('test'));
    +      $this->assertEmpty($_POST);
    +      $this->assertEmpty(iterator_to_array($this->request->request->getIterator()));
    +    }
    +    else {
    +      $this->assertEquals('example-value', $form['test']['#value']);
    +      $this->assertEquals('example-value', $form_state->getValue('test'));
    +      $this->assertEquals('bar', $_POST['foo']);
    +      $this->assertEquals('bar', $this->request->request->get('foo'));
    +    }
       }
     
       public function providerTestInvalidToken() {
    
  • tests/Drupal/Tests/Core/Form/FormTestBase.php+1 1 modified
    @@ -173,7 +173,7 @@ protected function setUp() {
           ->getMock();
         $this->account = $this->createMock('Drupal\Core\Session\AccountInterface');
         $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface');
    -    $this->request = new Request();
    +    $this->request = Request::createFromGlobals();
         $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
         $this->requestStack = new RequestStack();
         $this->requestStack->push($this->request);
    
  • tests/Drupal/Tests/Core/Form/FormValidatorTest.php+1 1 modified
    @@ -131,7 +131,7 @@ public function testValidateInvalidFormToken() {
           ->getMock();
         $form_state->expects($this->once())
           ->method('setErrorByName')
    -      ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
    +      ->with('form_token', 'The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.');
         $form_state->setValue('form_token', 'some_random_token');
         $form_validator->validateForm('test_form_id', $form, $form_state);
         $this->assertTrue($form_state->isValidationComplete());
    
5f3c4d80fd77

SA-CORE-2020-004 by samuel.mortenson, DorTumarkin, greggles, xjm, larowlan, webchick, pwolanin, dawehner, mcdruid, alexpott, dsnopek

https://github.com/drupal/corexjmJun 17, 2020via ghsa
8 files changed · +57 24
  • lib/Drupal/Core/Form/FormBuilder.php+10 1 modified
    @@ -19,6 +19,7 @@
     use Drupal\Core\Theme\ThemeManagerInterface;
     use Symfony\Component\EventDispatcher\EventDispatcherInterface;
     use Symfony\Component\HttpFoundation\FileBag;
    +use Symfony\Component\HttpFoundation\ParameterBag;
     use Symfony\Component\HttpFoundation\RequestStack;
     use Symfony\Component\HttpFoundation\Response;
     
    @@ -957,8 +958,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
                 // This value is checked in self::handleInputElement().
                 $form_state->setInvalidToken(TRUE);
     
    +            // Ignore all submitted values.
    +            $form_state->setUserInput([]);
    +
    +            $request = $this->requestStack->getCurrentRequest();
    +            // Do not trust any POST data.
    +            $request->request = new ParameterBag();
                 // Make sure file uploads do not get processed.
    -            $this->requestStack->getCurrentRequest()->files = new FileBag();
    +            $request->files = new FileBag();
    +            // Ensure PHP globals reflect these changes.
    +            $request->overrideGlobals();
               }
             }
           }
    
  • lib/Drupal/Core/Form/FormValidator.php+1 3 modified
    @@ -124,10 +124,8 @@ 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>.', [':link' => $url]));
    +    $form_state->setErrorByName('form_token', $this->t('The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.'));
       }
     
       /**
    
  • modules/file/tests/src/Functional/FileManagedFileElementTest.php+1 1 modified
    @@ -50,7 +50,7 @@ public function testManagedFile() {
                 $file_field_name => \Drupal::service('file_system')->realpath($test_file->getFileUri()),
               ];
               $this->drupalPostForm(NULL, $edit, t('Save'));
    -          $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +          $this->assertText('The form has become outdated.');
               $last_fid = $this->getLastFileId();
               $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.');
     
    
  • modules/system/tests/src/Functional/Form/FormTest.php+23 15 modified
    @@ -251,21 +251,27 @@ public function testInputWithInvalidToken() {
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
    +    $random_string = $this->randomString();
         $edit = [
    -      'textfield' => $this->randomString(),
    +      'textfield' => $random_string,
           'checkboxes[bar]' => TRUE,
           'select' => 'bar',
           'radios' => 'foo',
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    // Verify that input elements retained the posted values.
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    +
    +    $assert = $this->assertSession();
    +    $element = $assert->fieldExists('textfield');
    +    $this->assertEmpty($element->getValue());
    +    $assert->responseNotContains($random_string);
    +    $this->assertText('The form has become outdated.');
    +    // Ensure that we don't use the posted values.
    +    $this->assertFieldByName('textfield', '');
         $this->assertNoFieldChecked('edit-checkboxes-foo');
    -    $this->assertFieldChecked('edit-checkboxes-bar');
    -    $this->assertOptionSelected('edit-select', 'bar');
    -    $this->assertFieldChecked('edit-radios-foo');
    +    $this->assertNoFieldChecked('edit-checkboxes-bar');
    +    $this->assertOptionSelected('edit-select', '');
    +    $this->assertNoFieldChecked('edit-radios-foo');
     
         // Check another form that has a textarea input.
         $this->drupalGet(Url::fromRoute('form_test.required'));
    @@ -278,22 +284,24 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('textfield', $edit['textfield']);
    -    $this->assertFieldByName('textarea', $edit['textarea']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('textfield', '');
    +    $this->assertFieldByName('textarea', '');
     
         // Check another form that has a number input.
         $this->drupalGet(Url::fromRoute('form_test.number'));
         $this->assertSession()
           ->elementExists('css', 'input[name="form_token"]')
           ->setValue('invalid token');
         $edit = [
    -      'integer_step' => mt_rand(1, 100),
    +      // We choose a random value which is higher than the default value,
    +      // so we don't accidentally generate the default value.
    +      'integer_step' => mt_rand(6, 100),
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('integer_step', $edit['integer_step']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('integer_step', 5);
     
         // Check a form with a Url field
         $this->drupalGet(Url::fromRoute('form_test.url'));
    @@ -305,8 +313,8 @@ public function testInputWithInvalidToken() {
         ];
         $this->drupalPostForm(NULL, $edit, 'Submit');
         $this->assertFieldByXpath('//div[contains(@class, "error")]', NULL, 'Error message is displayed with invalid token even when required fields are filled.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    -    $this->assertFieldByName('url', $edit['url']);
    +    $this->assertText('The form has become outdated.');
    +    $this->assertFieldByName('url', '');
       }
     
       /**
    
  • modules/system/tests/src/Functional/Form/ValidationTest.php+1 1 modified
    @@ -74,7 +74,7 @@ public function testValidate() {
         $this->drupalPostForm(NULL, ['name' => 'validate'], 'Save');
         $this->assertNoFieldByName('name', '#value changed by #validate', 'Form element #value was not altered.');
         $this->assertNoText('Name value: value changed by setValueForElement() in #validate', 'Form element value in $form_state was not altered.');
    -    $this->assertText('The form has become outdated. Copy any unsaved work in the form below');
    +    $this->assertText('The form has become outdated.');
       }
     
       /**
    
  • tests/Drupal/Tests/Core/Form/FormBuilderTest.php+19 1 modified
    @@ -832,12 +832,30 @@ public function testInvalidToken($expected, $valid_token, $user_is_authenticated
         $expected_form = $form_id();
         $form_arg = $this->getMockForm($form_id, $expected_form);
     
    +    // Set up some request data so we can be sure it is removed when a token is
    +    // invalid.
    +    $this->request->request->set('foo', 'bar');
    +    $_POST['foo'] = 'bar';
    +
         $form_state = new FormState();
         $input['form_id'] = $form_id;
         $input['form_token'] = $form_token;
    +    $input['test'] = 'example-value';
         $form_state->setUserInput($input);
    -    $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
    +    $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
         $this->assertSame($expected, $form_state->hasInvalidToken());
    +    if ($expected) {
    +      $this->assertEmpty($form['test']['#value']);
    +      $this->assertEmpty($form_state->getValue('test'));
    +      $this->assertEmpty($_POST);
    +      $this->assertEmpty(iterator_to_array($this->request->request->getIterator()));
    +    }
    +    else {
    +      $this->assertEquals('example-value', $form['test']['#value']);
    +      $this->assertEquals('example-value', $form_state->getValue('test'));
    +      $this->assertEquals('bar', $_POST['foo']);
    +      $this->assertEquals('bar', $this->request->request->get('foo'));
    +    }
       }
     
       public function providerTestInvalidToken() {
    
  • tests/Drupal/Tests/Core/Form/FormTestBase.php+1 1 modified
    @@ -173,7 +173,7 @@ protected function setUp() {
           ->getMock();
         $this->account = $this->createMock('Drupal\Core\Session\AccountInterface');
         $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface');
    -    $this->request = new Request();
    +    $this->request = Request::createFromGlobals();
         $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
         $this->requestStack = new RequestStack();
         $this->requestStack->push($this->request);
    
  • tests/Drupal/Tests/Core/Form/FormValidatorTest.php+1 1 modified
    @@ -131,7 +131,7 @@ public function testValidateInvalidFormToken() {
           ->getMock();
         $form_state->expects($this->once())
           ->method('setErrorByName')
    -      ->with('form_token', 'The form has become outdated. Copy any unsaved work in the form below and then <a href="/test/example?foo=bar">reload this page</a>.');
    +      ->with('form_token', 'The form has become outdated. Press the back button, copy any unsaved work in the form, and then reload the page.');
         $form_state->setValue('form_token', 'some_random_token');
         $form_validator->validateForm('test_form_id', $form, $form_state);
         $this->assertTrue($form_state->isValidationComplete());
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.