VYPR
High severityNVD Advisory· Published Jun 9, 2026· Updated Jun 9, 2026

CVE-2026-11607

CVE-2026-11607

Description

TYPO3 CMS Form Framework allows SQL injection via crafted form definition files, enabling privilege escalation by creating admin users.

AI Insight

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

TYPO3 CMS Form Framework allows SQL injection via crafted form definition files, enabling privilege escalation by creating admin users.

Vulnerability

Backend users with access to the TYPO3 CMS Form Framework could use files not ending in .form.yaml as form definitions. The framework processed these files without properly validating the file extension. This vulnerability affects TYPO3 CMS versions before 10.4.57, 11.0.0-11.5.51, 12.0.0-12.4.46, 13.0.0-13.4.31, and 14.0.0-14.3.3 [3].

Exploitation

An attacker with backend user access to the Form Framework can craft malicious form definition files that do not end in .form.yaml. By uploading and processing these files, the attacker can trigger the execution of arbitrary SQL statements. This requires the attacker to have sufficient privileges to access and manage form definitions within the TYPO3 backend [3].

Impact

Successful exploitation allows an attacker to execute arbitrary SQL statements. This can lead to privilege escalation by creating new administrative backend user accounts, granting the attacker full control over the TYPO3 CMS instance [3].

Mitigation

Update to TYPO3 versions 10.4.57 ELTS, 11.5.51 ELTS, 12.4.46 ELTS, 13.4.31 LTS, or 14.3.3 LTS. These versions include fixes for the vulnerability [3]. The related commits are available at [1] and [2].

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

Affected products

2
  • TYPO3/Typo3references
  • TYPO3/TYPO3 CMSllm-fuzzy
    Range: <10.4.57, 11.0.0-11.5.51, 12.0.0-12.4.46, 13.0.0-13.4.31, 14.0.0-14.3.3

Patches

2
50974c658f64

[SECURITY] Properly evaluate .form.yaml file extension

https://github.com/TYPO3/typo3Oliver HaderJun 9, 2026via github-commit-search
10 files changed · +95 4
  • typo3/sysext/form/Classes/Storage/AbstractFileStorageAdapter.php+2 2 modified
    @@ -130,9 +130,9 @@ protected function extractMetaDataFromCouldBeFormDefinition(string $maybeRawForm
         /**
          * @throws PersistenceManagerException
          */
    -    protected function generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array $formDefinition, string $identifier): void
    +    protected function generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension(array $formDefinition, string $identifier): void
         {
    -        if ($this->looksLikeAFormDefinitionArray($formDefinition) && !$this->hasValidFileExtension($identifier)) {
    +        if (!$this->looksLikeAFormDefinitionArray($formDefinition) || !$this->hasValidFileExtension($identifier)) {
                 throw new PersistenceManagerException(sprintf('Form definition "%s" does not end with ".form.yaml".', $identifier), 1531160649);
             }
         }
    
  • typo3/sysext/form/Classes/Storage/ExtensionStorageAdapter.php+2 2 modified
    @@ -84,7 +84,7 @@ public function read(FormIdentifier $identifier, ?ServerRequestInterface $reques
             $this->ensureValidPersistenceIdentifier($identifier->identifier);
             $file = $identifier->identifier;
             $formDefinition = $this->yamlSource->load([$file]);
    -        $this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($formDefinition, $identifier->identifier);
    +        $this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($formDefinition, $identifier->identifier);
             return FormData::fromArray($formDefinition);
         }
     
    @@ -262,7 +262,7 @@ protected function loadMetaData(string $fileOrIdentifier): FormMetadata
                     throw new NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684462);
                 }
                 $yaml = $this->extractMetaDataFromCouldBeFormDefinition($rawYamlContent);
    -            $this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
    +            $this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($yaml, $persistenceIdentifier);
                 return FormMetadata::createFromYaml(
                     $yaml,
                     $persistenceIdentifier,
    
  • typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests/composer.json+10 0 added
    @@ -0,0 +1,10 @@
    +{
    +    "name": "typo3tests/form-storage-tests",
    +    "type": "typo3-cms-extension",
    +    "require": {},
    +    "extra": {
    +        "typo3/cms": {
    +            "extension-key": "form_storage_tests"
    +        }
    +    }
    +}
    
  • typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests/Configuration/Form/Base/config.yaml+3 0 added
    @@ -0,0 +1,3 @@
    +persistenceManager:
    +  allowedExtensionPaths:
    +    10: EXT:form_storage_tests/Resources/Private/Forms/
    
  • typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests/Resources/Private/Forms/ContactForm.form.yaml+3 0 added
    @@ -0,0 +1,3 @@
    +identifier: contact-form
    +type: Form
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests/Resources/Private/Forms/ContactFormWithoutType.yaml+2 0 added
    @@ -0,0 +1,2 @@
    +identifier: contact-form-without-type
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests/Resources/Private/Forms/ContactFormWithType.yaml+3 0 added
    @@ -0,0 +1,3 @@
    +identifier: contact-form-with-type
    +type: Form
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/Extensions/test_form_persistence/Resources/Private/Forms/TestForm.form.yaml+1 0 modified
    @@ -1,4 +1,5 @@
     identifier: test-extension-form
    +type: Form
     prototypeName: standard
     label: 'Test Extension Form'
     renderables:
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/Simple.form.yaml+1 0 modified
    @@ -1,4 +1,5 @@
     identifier: ext-form-identifier
    +type: Form
     prototypeName: standard
     label: 'Label'
     renderables:
    
  • typo3/sysext/form/Tests/Functional/Storage/ExtensionStorageAdapterFunctionalTest.php+68 0 added
    @@ -0,0 +1,68 @@
    +<?php
    +
    +declare(strict_types=1);
    +
    +/*
    + * This file is part of the TYPO3 CMS project.
    + *
    + * It is free software; you can redistribute it and/or modify it under
    + * the terms of the GNU General Public License, either version 2
    + * of the License, or any later version.
    + *
    + * For the full copyright and license information, please read the
    + * LICENSE.txt file that was distributed with this source code.
    + *
    + * The TYPO3 project - inspiring people to share!
    + */
    +
    +namespace TYPO3\CMS\Form\Tests\Functional\Storage;
    +
    +use PHPUnit\Framework\Attributes\DataProvider;
    +use PHPUnit\Framework\Attributes\Test;
    +use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
    +use TYPO3\CMS\Core\Http\ServerRequest;
    +use TYPO3\CMS\Form\Domain\ValueObject\FormIdentifier;
    +use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
    +use TYPO3\CMS\Form\Storage\ExtensionStorageAdapter;
    +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
    +
    +final class ExtensionStorageAdapterFunctionalTest extends FunctionalTestCase
    +{
    +    protected array $coreExtensionsToLoad = ['form'];
    +
    +    protected array $testExtensionsToLoad = [
    +        'typo3/sysext/form/Tests/Functional/Fixtures/Extensions/form_storage_tests',
    +    ];
    +
    +    protected function setUp(): void
    +    {
    +        parent::setUp();
    +        $this->importCSVDataSet(__DIR__ . '/Fixtures/be_users.csv');
    +        $this->setUpBackendUser(1);
    +    }
    +
    +    public static function readThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider(): iterable
    +    {
    +        yield ['EXT:form_storage_tests/Resources/Private/Forms/ContactForm.form.yaml', null];
    +        yield ['EXT:form_storage_tests/Resources/Private/Forms/ContactFormWithType.yaml', 1531160649];
    +        yield ['EXT:form_storage_tests/Resources/Private/Forms/ContactFormWithoutType.yaml', 1531160649];
    +    }
    +
    +    #[Test]
    +    #[DataProvider('readThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider')]
    +    public function readThrowsExceptionWhenFormDefinitionHasInvalidFileExtension(string $identifier, ?int $exceptionCode): void
    +    {
    +        $request = (new ServerRequest())
    +            ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
    +        $GLOBALS['TYPO3_REQUEST'] = $request;
    +
    +        if ($exceptionCode !== null) {
    +            $this->expectException(PersistenceManagerException::class);
    +            $this->expectExceptionCode($exceptionCode);
    +        }
    +
    +        $subject = $this->get(ExtensionStorageAdapter::class);
    +        $formData = $subject->read(new FormIdentifier($identifier), $request);
    +        self::assertStringStartsWith('contact-form', $formData->identifier);
    +    }
    +}
    
040d50d082a0

[SECURITY] Properly evaluate .form.yaml file extension

https://github.com/TYPO3/typo3Oliver HaderJun 9, 2026via nvd-ref
6 files changed · +39 6
  • typo3/sysext/form/Classes/Mvc/Persistence/FormPersistenceManager.php+4 4 modified
    @@ -89,7 +89,7 @@ public function load(string $persistenceIdentifier, array $formSettings, array $
                 }
                 try {
                     $formDefinition = $this->yamlSource->load([$file]);
    -                $this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($formDefinition, $persistenceIdentifier);
    +                $this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($formDefinition, $persistenceIdentifier);
                 } catch (\Exception $e) {
                     $formDefinition = [
                         'type' => 'Form',
    @@ -648,7 +648,7 @@ protected function loadMetaData(string|File $persistenceIdentifier, array $formS
                     throw new NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684462);
                 }
                 $yaml = $this->extractMetaDataFromCouldBeFormDefinition($rawYamlContent);
    -            $this->generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
    +            $this->generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension($yaml, $persistenceIdentifier);
                 if ($file !== null) {
                     $yaml['fileUid'] = $file->getUid();
                 }
    @@ -694,9 +694,9 @@ protected function extractMetaDataFromCouldBeFormDefinition(string $maybeRawForm
         /**
          * @throws PersistenceManagerException
          */
    -    protected function generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier): void
    +    protected function generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier): void
         {
    -        if ($this->looksLikeAFormDefinition($formDefinition) && !$this->hasValidFileExtension($persistenceIdentifier)) {
    +        if (!$this->looksLikeAFormDefinition($formDefinition) || !$this->hasValidFileExtension($persistenceIdentifier)) {
                 throw new PersistenceManagerException(sprintf('Form definition "%s" does not end with ".form.yaml".', $persistenceIdentifier), 1531160649);
             }
         }
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactForm.form.yaml+3 0 added
    @@ -0,0 +1,3 @@
    +identifier: contact-form
    +type: Form
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithoutType.yaml+2 0 added
    @@ -0,0 +1,2 @@
    +identifier: contact-form-without-type
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithType.yaml+3 0 added
    @@ -0,0 +1,3 @@
    +identifier: contact-form-with-type
    +type: Form
    +label: 'Contact Form'
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/Simple.form.yaml+1 0 modified
    @@ -1,4 +1,5 @@
     identifier: ext-form-identifier
    +type: Form
     prototypeName: standard
     label: 'Label'
     renderables:
    
  • typo3/sysext/form/Tests/Functional/Mvc/Persistence/FormPersistenceManagerTest.php+26 2 modified
    @@ -37,12 +37,16 @@
     
     final class FormPersistenceManagerTest extends FunctionalTestCase
     {
    -    protected bool $initializeDatabase = false;
    -
         protected array $coreExtensionsToLoad = [
             'form',
         ];
     
    +    protected array $pathsToProvideInTestInstance = [
    +        'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactForm.form.yaml' => 'fileadmin/form_definitions/ContactForm.form.yaml',
    +        'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithoutType.yaml' => 'fileadmin/form_definitions/ContactFormWithoutType.yaml',
    +        'typo3/sysext/form/Tests/Functional/Mvc/Persistence/Fixtures/ContactFormWithType.yaml' => 'fileadmin/form_definitions/ContactFormWithType.yaml',
    +    ];
    +
         #[Test]
         public function loadThrowsExceptionIfPersistenceIdentifierHasNoYamlExtension(): void
         {
    @@ -92,6 +96,7 @@ public function loadReturnsFormArray(): void
             ];
             $expected = [
                 'identifier' => 'ext-form-identifier',
    +            'type' => 'Form',
                 'prototypeName' => 'standard',
                 'label' => 'Label',
                 'renderables' => [
    @@ -140,6 +145,7 @@ public function loadReturnsOverriddenConfigurationIfTypoScriptOverridesExists():
             ];
             $expected = [
                 'identifier' => 'ext-form-identifier',
    +            'type' => 'Form',
                 'prototypeName' => 'standard',
                 'label' => 'Label override',
                 'renderables' => [
    @@ -190,6 +196,7 @@ public function overrideFormDefinitionDoesNotEvaluateTypoScriptLookalikeInstruct
             ];
             $formDefinitionYaml = [
                 'identifier' => 'ext-form-identifier',
    +            'type' => 'Form',
                 'prototypeName' => 'standard',
                 'label' => [
                     'value' => 'Label override',
    @@ -212,6 +219,7 @@ public function overrideFormDefinitionDoesNotEvaluateTypoScriptLookalikeInstruct
             ];
             $expected = [
                 'identifier' => 'ext-form-identifier',
    +            'type' => 'Form',
                 'prototypeName' => 'standard',
                 'label' => [
                     'value' => 'Label override',
    @@ -1032,4 +1040,20 @@ public function isAllowedPersistencePathReturnsProperValues(string $persistenceP
             $subjectMock->method('getStorageByUid')->willReturn($storageMock);
             self::assertEquals($expected, $subjectMock->isAllowedPersistencePath($persistencePath, $formSettings));
         }
    +
    +    public static function loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider(): iterable
    +    {
    +        yield ['1:/form_definitions/ContactForm.form.yaml', null];
    +        yield ['1:/form_definitions/ContactFormWithType.yaml', true];
    +        yield ['1:/form_definitions/ContactFormWithoutType.yaml', true];
    +    }
    +
    +    #[Test]
    +    #[DataProvider('loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtensionDataProvider')]
    +    public function loadThrowsExceptionWhenFormDefinitionHasInvalidFileExtension(string $identifier, ?bool $expectedInvalid): void
    +    {
    +        $subject = $this->get(FormPersistenceManager::class);
    +        $formDefinition = $subject->load($identifier, [], []);
    +        self::assertSame($expectedInvalid, $formDefinition['invalid'] ?? null);
    +    }
     }
    

Vulnerability mechanics

Root cause

"The Form Framework did not properly validate file extensions for form definitions, allowing files with incorrect extensions to be processed."

Attack vector

Backend users with access to the Form Framework could upload or reference files that did not end in `.form.yaml`. These files were processed without proper validation of their extension. By crafting malicious form definition files with incorrect extensions, attackers could potentially execute arbitrary SQL statements. This could lead to privilege escalation by creating new administrative backend user accounts.

Affected code

The vulnerability lies within the `AbstractFileStorageAdapter.php` and `FormPersistenceManager.php` files, specifically in the `generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension` method, which has been renamed and refactored to `generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension` [patch_id=5348983, patch_id=5348982]. The `ExtensionStorageAdapter.php` and `FormPersistenceManager.php` classes call this method during the reading of form definitions.

What the fix does

The patch modifies the `generateErrorsIfFormDefinitionIsInvalidOrHasInvalidFileExtension` method in `AbstractFileStorageAdapter.php` and `FormPersistenceManager.php` [patch_id=5348983, patch_id=5348982]. The logic was updated to correctly check if the form definition is invalid OR if it has an invalid file extension. Previously, it only checked if the definition was valid AND had an invalid extension, allowing files with incorrect extensions to be processed if they were otherwise valid form definitions.

Preconditions

  • authThe attacker must be a backend user with access to the Form Framework.

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

References

3

News mentions

1