Affected versions

Symfony Serializer versions <5.4.56, >=6, <6.4.50, >=7, <7.4.23, >=8, <8.1.11 are affected by this security issue.

The issue has been fixed in Symfony Serializer 5.4.56, 6.4.50, 7.4.23, 8.1.11.

Description

DataUriNormalizer builds \SplFileInfo, \SplFileObject and Symfony\Component\HttpFoundation\File\File objects from data URIs, for those exact types only. A property typed with a subclass of them, like UploadedFile, was left to ObjectNormalizer, which instantiated it through its constructor from the denormalized data, path, original name and test flag included. With #[MapRequestPayload] and a DTO holding an UploadedFile property, which is how uploads are mapped in Symfony 8.1, a request needs no upload at all to make the application handle any file of the server as one:

1
{"name": "x", "image": {"path": "/path/to/project/.env.local", "originalName": "a.png", "test": true}}

The same works with a form body, and with any code that deserializes untrusted data into a type holding such a property, on every maintained branch. test: true makes UploadedFile::isValid() skip is_uploaded_file() and move() fall back to rename(); code that reads the file without calling isValid() does not even need it. Depending on what the application does with the upload, this exposes any file readable by PHP, for instance when uploads are stored and served back, moves any such file when move() is called as the upload documentation shows, and reveals whether a path exists. Assert\File does not prevent it, since it validates the real server file.

Resolution

DataUriNormalizer now handles every subclass of \SplFileInfo as well: it passes through an instance of the expected type, as the files of a request are, and rejects any other data with a NotNormalizableValueException, which #[MapRequestPayload] turns into a 422. File, \SplFileInfo and \SplFileObject are still built from data URIs when the type is spelled as their ::class constant. Any other spelling of these classes, like a leading backslash, a different case or a class alias, is handled like a subclass.

A serializer built without DataUriNormalizer ahead of the object normalizer does not get this protection; the serializer configured by FrameworkBundle has it.

The patches for this issue are available here for branch 5.4 and here for branch 6.4 (and forward-ported to 7.4, 8.1 and 8.2).

Credits

We would like to thank Oliver Wenzel for reporting the issue and Nicolas Grekas for providing the fix.

We would also like to thank Bruno Dias and Miguel Neves from Checkmarx for their help getting a CVE assigned.