CVE-2021-32697 (PHP, published 2021-06-21T19:15Z)
Description: value: neos/forms is an open source framework to build web forms. By crafting a special `GET` request containing a valid form state, a form can be submitted without invoking any validators. Form state is secured with an HMAC that is still verified. That means that this issue can only be exploited if Form Finishers cause side effects even if no form values have been sent. Form Finishers can be adjusted in a way that they only execute an action if the submitted form contains some expected data. Alternatively a custom Finisher can be added as first finisher. This regression was introduced with https://github.com/neos/form/commit/049d415295be8d4a0478ccba97dba1bb81649567
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/neos/form
Files changed:
Classes/TYPO3/Form/Core/Runtime/FormRuntime.php
Tests/Functional/SimpleFormTest.php
Commit message: [BUGFIX] fix reconstituting a form from its internal state

This is an edge case which is only exposed when persisting the formState
in e.g. a database, and accessing it with GET requests as well.

Change-Id: Ib3d24628f10160f2c170d01fc3daab873595a292
Releases: master


Vulnerable code:
		if (!$this->isFirstRequest()) {
		$r = $this->gotoNextFormPage($this->browser->getForm());

Fixed code:
		if (!$this->isFirstRequest() && $this->getRequest()->getHttpRequest()->getMethod() === 'POST') {
		$this->gotoNextFormPage($this->browser->getForm());

	/**
	 * 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());
	}