VYPR
Moderate severityNVD Advisory· Published Jun 21, 2021· Updated Aug 3, 2024

Form validation can be skipped

CVE-2021-32697

Description

A crafted GET request with a valid HMAC-signed form state can bypass validators in neos/forms, causing unintended side effects via form finishers.

AI Insight

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

A crafted GET request with a valid HMAC-signed form state can bypass validators in neos/forms, causing unintended side effects via form finishers.

Vulnerability

In neos/forms 5.1.x before 5.1.3, a regression introduced in commit 049d415 allows an attacker to submit a form without invoking any validators by sending a crafted GET request that contains a valid, HMAC-signed form state [1]. The form state is still securely signed, so the attack only succeeds when the form's Finishers perform side effects even if no form values were submitted. Affected versions are those using the commit 049d415295be8d4a0478ccba97dba1bb81649567 [2].

Exploitation

An attacker must obtain a valid form state (which is protected by an HMAC) and then craft a GET request that includes only that state, without any form field parameters [1][2]. No authentication is required if the form is publicly accessible. The attacker can then trigger the form's Finishers, which must be configured to execute actions even when no form values are present. The exploit is demonstrated in a test case showing a multi-page form where values are persisted despite bypassing validation [2].

Impact

Successful exploitation causes the form's Finishers to execute unintended side effects, such as sending emails, storing data, or other actions, without the form being properly validated [1]. The integrity and availability of the application can be compromised, depending on what the Finishers do. The confidentiality of data may also be affected if Finishers expose or transmit unvalidated input.

Mitigation

The fix is included in release 5.1.3 of neos/forms [3]. Users should upgrade to version 5.1.3 or later immediately. As a workaround, administrators can adjust Form Finishers to only execute when expected data is present, or add a custom Finisher as the first finisher that prevents side effects for empty submissions [1]. There is no indication that this CVE is listed in CISA's Known Exploited Vulnerabilities catalog.

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
neos/formPackagist
>= 1.2.0, < 4.3.34.3.3
neos/formPackagist
>= 5.0.0, < 5.0.95.0.9
neos/formPackagist
>= 5.1.0, < 5.1.35.1.3

Affected products

2

Patches

2
69de4219b1f5

Merge pull request from GHSA-m5vx-8chx-qvmm

https://github.com/neos/formBastian WaidelichJun 21, 2021via ghsa
2 files changed · +26 1
  • Classes/Core/Runtime/FormRuntime.php+1 1 modified
    @@ -167,7 +167,7 @@ public function initializeObject()
             $this->initializeFormStateFromRequest();
             $this->initializeCurrentPageFromRequest();
     
    -        if (!$this->isFirstRequest() && $this->getRequest()->getHttpRequest()->getMethod() === 'POST') {
    +        if (!$this->isFirstRequest()) {
                 $this->processSubmittedFormValues();
             }
         }
    
  • Tests/Functional/SimpleFormTest.php+25 0 modified
    @@ -80,6 +80,30 @@ public function goingForthAndBackStoresFormValuesOfSecondPageAndTriggersValidati
             $this->assertSame('', $form['--three-page-form-with-validation']['text3-1']->getValue());
         }
     
    +    /**
    +     * @test
    +     * Thanks to Anian Weber for reporting that issue!
    +     */
    +    public function validationIsNotSkippedForGetRequests()
    +    {
    +        $this->browser->request('http://localhost/test/form/simpleform/ThreePageFormWithValidation');
    +
    +        // Navigate to 2nd form page
    +        $this->gotoNextFormPage($this->browser->getForm());
    +
    +        $form = $this->browser->getForm();
    +        // Change form method to "GET"
    +        ObjectAccess::setProperty($form, 'method', 'GET', true);
    +
    +        // Set invalid value (field "text2-1" has an IntegerValidator assigned)
    +        $form['--three-page-form-with-validation']['text2-1']->setValue('My Text on the second page');
    +
    +        // Submit form
    +        $this->gotoNextFormPage($form);
    +
    +        // Expect validation errors
    +        $this->assertSame(' error', $this->browser->getCrawler()->filterXPath('//*[contains(@class,"error")]//input[@id="three-page-form-with-validation-text2-1"]')->attr('class'));
    +    }
     
         /**
          * This is an edge-case which occurs if somebody makes the formState persistent, which can happen when subclassing the FormRuntime.
    @@ -93,6 +117,7 @@ public function goingForthAndBackStoresFormValuesOfSecondPageAndTriggersValidati
          */
         public function goingForthAndBackStoresFormValuesOfSecondPageEvenWhenSecondPageIsManuallyCalledAsGetRequest()
         {
    +        $this->markTestSkipped('This test is skipped because we no longer allow Form validators to be skipped, see https://github.com/neos/form/security/advisories/GHSA-m5vx-8chx-qvmm');
             // 1. TEST SETUP: FORM STATE PREPARATION
             // - go to the 2nd page of the form, and fill in text2-1.
             $this->browser->request('http://localhost/test/form/simpleform/ThreePageFormWithValidation');
    
049d415295be

[BUGFIX] fix reconstituting a form from its internal state

https://github.com/neos/formSebastian KurfürstOct 23, 2013via ghsa
2 files changed · +49 2
  • Classes/TYPO3/Form/Core/Runtime/FormRuntime.php+1 1 modified
    @@ -146,7 +146,7 @@ public function initializeObject() {
     		$this->initializeFormStateFromRequest();
     		$this->initializeCurrentPageFromRequest();
     
    -		if (!$this->isFirstRequest()) {
    +		if (!$this->isFirstRequest() && $this->getRequest()->getHttpRequest()->getMethod() === 'POST') {
     			$this->processSubmittedFormValues();
     		}
     	}
    
  • Tests/Functional/SimpleFormTest.php+48 1 modified
    @@ -64,7 +64,7 @@ public function goingForthAndBackStoresFormValuesOfSecondPageAndTriggersValidati
     		$form['--three-page-form-with-validation']['text2-1']->setValue('My Text on the second page');
     		$this->gotoPreviousFormPage($form);
     		$this->gotoNextFormPage($this->browser->getForm());
    -		$r = $this->gotoNextFormPage($this->browser->getForm());
    +		$this->gotoNextFormPage($this->browser->getForm());
     
     		$this->assertSame(' error', $this->browser->getCrawler()->filterXPath('//*[contains(@class,"error")]//input[@id="three-page-form-with-validation-text2-1"]')->attr('class'));
     		$form = $this->browser->getForm();
    @@ -75,4 +75,51 @@ public function goingForthAndBackStoresFormValuesOfSecondPageAndTriggersValidati
     		$this->assertSame('', $form['--three-page-form-with-validation']['text3-1']->getValue());
     	}
     
    +
    +	/**
    +	 * This is an edge-case which occurs if somebody makes the formState persistent, which can happen when subclassing the FormRuntime.
    +	 *
    +	 * The goal is to build a GET request *only* containing the form state, and nothing else. Furthermore, we need to make sure
    +	 * that we do NOT send any of the parameters with the form; as we only want the form state to be applied.
    +	 *
    +	 * So, if the form state contains some values, we want to be sure these values are re-displayed.
    +	 *
    +	 * @test
    +	 */
    +	public function goingForthAndBackStoresFormValuesOfSecondPageEvenWhenSecondPageIsManuallyCalledAsGetRequest() {
    +		// 1. TEST SETUP: FORM STATE PREPARATION
    +		// - go to the 2nd page of the form, and fill in text2-1.
    +		$this->browser->request('http://localhost/test/form/simpleform/ThreePageFormWithValidation');
    +
    +		$this->gotoNextFormPage($this->browser->getForm());
    +
    +		$form = $this->browser->getForm();
    +		$form['--three-page-form-with-validation']['text2-1']->setValue('My Text on the second page');
    +
    +		// - then, go back and forth, in order to get an *up-to-date* form state having the right values inside.
    +		$this->gotoPreviousFormPage($form);
    +		$this->gotoNextFormPage($this->browser->getForm());
    +
    +		// 2. TEST SETUP: BUILD GET REQUEST ONLY CONTAINING FORM STATE
    +		$form = $this->browser->getForm();
    +		\TYPO3\Flow\Reflection\ObjectAccess::setProperty($form, 'method', 'GET', TRUE);
    +
    +		// we want to stay on the current page, that's why we send __currentPage = 1. (== 2nd page of the form)
    +		$doc = new \DOMDocument();
    +		$doc->loadXML('<input type="hidden" name="--three-page-form-with-validation[__currentPage]" value="1" />');
    +		$node = $doc->getElementsByTagName('input')->item(0);
    +		$form->set(new InputFormField($node));
    +
    +		// We do *not* send any form content with us, as we want to test these are properly reconstituted from the form state.
    +		$form->offsetUnset('--three-page-form-with-validation[text2-1]');
    +
    +		// 3. TEST RUN
    +		// submit the GET request ONLY containing formState.
    +		$this->browser->submit($form);
    +
    +		// now, make sure the text2-1 (which has been persisted in the form state) gets reconstituted and shown properly.
    +		$form = $this->browser->getForm();
    +		$this->assertSame('My Text on the second page', $form['--three-page-form-with-validation']['text2-1']->getValue());
    +	}
    +
     }
    

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.