CVE-2024-24752 (PHP, published 2024-02-01T16:17Z)
Description: value: Bref enable serverless PHP on AWS Lambda. When Bref is used with the Event-Driven Function runtime and the handler is a `RequestHandlerInterface`, then the Lambda event is converted to a PSR7 object. During the conversion process, if the request is a MultiPart, each part is parsed and for each which contains a file, it is extracted and saved in `/tmp` with a random filename starting with `bref_upload_`. The flow mimics what plain PHP does but it does not delete the temporary files when the request has been processed. An attacker could fill the Lambda instance disk by performing multiple MultiPart requests containing files. This vulnerability is patched in 2.1.13.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/brefphp/bref
Files changed:
src/Event/Http/Psr7Bridge.php
tests/Event/Http/CommonHttpTest.php
tests/Event/Http/HttpRequestEventTest.php
tests/Event/Http/Psr7BridgeTest.php
Commit message: Support malformed multipart body

For example body containing broken array keys like `key0[key1][key2][`


Vulnerable code:
        $bodyString = $event->getBody();
        $files = [];
        $parsedBody = null;
        if ($contentType !== null && $event->getMethod() === 'POST') {
            if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
                parse_str($bodyString, $parsedBody);
            } else {
                $document = new Part("Content-type: $contentType\r\n\r\n" . $bodyString);
                if ($document->isMultiPart()) {
                    $parsedBody = [];
                    foreach ($document->getParts() as $part) {
                        if ($part->isFile()) {
                            if ($tmpPath === false) {
                                throw new RuntimeException('Unable to create a temporary directory');
                            }
                            file_put_contents($tmpPath, $part->getBody());
                            $file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());

                            self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
                        } else {
                            self::parseKeyAndInsertValueInArray($parsedBody, $part->getName(), $part->getBody());
                        }
                    }
        $this->assertParsedBody([]);
    abstract protected function assertParsedBody(array $expected): void;
    protected function assertParsedBody(array $expected): void
    protected function assertParsedBody(array $expected): void

Fixed code:
    /**
     * @return array{0: array<string, UploadedFile>, 1: array<string, mixed>|null}
     */
        if ($contentType === null || $event->getMethod() !== 'POST') {
            return [[], null];
        }

        if (str_starts_with($contentType, 'application/x-www-form-urlencoded')) {
            $parsedBody = [];
            parse_str($event->getBody(), $parsedBody);
            return [[], $parsedBody];
        }

        // Parse the body as multipart/form-data
        $document = new Part("Content-type: $contentType\r\n\r\n" . $event->getBody());
        if (!$document->isMultiPart()) {
            return [[], null];
        }
        $files = [];
        $queryString = '';
        foreach ($document->getParts() as $part) {
            if ($part->isFile()) {
                if ($tmpPath === false) {
                    throw new RuntimeException('Unable to create a temporary directory');
                file_put_contents($tmpPath, $part->getBody());
                $file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part->getFileName(), $part->getMimeType());
                self::parseKeyAndInsertValueInArray($files, $part->getName(), $file);
            } else {
                // Temporarily store as a query string so that we can use PHP's native parse_str function to parse keys
                $queryString .= urlencode($part->getName()) . '=' . urlencode($part->getBody()) . '&';
        if ($queryString !== '') {
            $parsedBody = [];
            parse_str($queryString, $parsedBody);
        } else {
            $parsedBody = null;
        }
        $this->assertParsedBody(null);
    abstract protected function assertParsedBody(array|null $expected): void;
    protected function assertParsedBody(array|null $expected): void
    protected function assertParsedBody(array|null $expected): void