Ecosyste.ms: Advisories

An open API service providing security vulnerability metadata for many open source software ecosystems.

Security Advisories: GSA_kwCzR0hTQS04MnZ4LW1tNnItZ2c4d84AA4_5

Bref vulnerable to Body Parsing Inconsistency in Event-Driven Functions

Impacted Resources

bref/src/Event/Http/Psr7Bridge.php:130-168

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 its content added in the $files or $parsedBody arrays.
To do that, the following method is called with as first argument the result array ($files or $parsedBody), as second argument the part name, and as third argument the part content:

/**
 * Parse a string key like "files[id_cards][jpg][]" and do $array['files']['id_cards']['jpg'][] = $value
 */
private static function parseKeyAndInsertValueInArray(array &$array, string $key, mixed $value): void
{
    if (! str_contains($key, '[')) {
        $array[$key] = $value;

        return;
    }

    $parts = explode('[', $key); // files[id_cards][jpg][] => [ 'files',  'id_cards]', 'jpg]', ']' ]
    $pointer = &$array;

    foreach ($parts as $k => $part) {
        if ($k === 0) {
            $pointer = &$pointer[$part];

            continue;
        }

        // Skip two special cases:
        // [[ in the key produces empty string
        // [test : starts with [ but does not end with ]
        if ($part === '' || ! str_ends_with($part, ']')) {
            // Malformed key, we use it "as is"
            $array[$key] = $value;

            return;
        }

        $part = substr($part, 0, -1); // The last char is a ] => remove it to have the real key

        if ($part === '') { // [] case
            $pointer = &$pointer[];
        } else {
            $pointer = &$pointer[$part];
        }
    }

    $pointer = $value;
}

The conversion process produces a different output compared to the one of plain PHP when keys ending with and open square bracket ([) are used.

Let's take for example the following part:

------WebKitFormBoundary
Content-Disposition: form-data; name="key0[key1][key2]["

value
------WebKitFormBoundary--

In plain PHP it would be converted to Array( [key0] => Array ( [key1] => Array ( [key2] => value) ) ), while in Bref it would be converted to Array( [key0] => Array ( [key1] => Array ( [key2] => ) ) [key0[key1][key2][] => value ).

Impact

Based on the application logic the difference in the body parsing might lead to vulnerabilities and/or undefined behaviors.

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, [], var_export($request->getParsedBody(),true));
    }
}

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 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="key0[key1][key2]["

value
------WebKitFormBoundaryQqDeSZSSvmn2rfjb--
  1. Notice how the body has been parsed.
  2. Create a plain.php file with the following content:
<?php

var_dump($_POST);
  1. Start a PHP server inside the project directory (e.g. php -S 127.0.0.1:8090).
  2. Replay the following request after having replaced the <HOST> placeholder with the PHP server address:
POST /plain.php HTTP/1.1
Host: <HOST>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQqDeSZSSvmn2rfjb
Content-Length: 180

------WebKitFormBoundaryQqDeSZSSvmn2rfjb
Content-Disposition: form-data; name="key0[key1][key2]["

value
------WebKitFormBoundaryQqDeSZSSvmn2rfjb--
  1. Notice the differences in the parsing compared to what observed at step 5.

Suggested Remediation

Use the PHP function parse_str to parse the body parameters to mimic the plain PHP behavior.

Permalink: https://github.com/advisories/GHSA-82vx-mm6r-gg8w
JSON: https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04MnZ4LW1tNnItZ2c4d84AA4_5
Source: GitHub Advisory Database
Origin: Unspecified
Severity: Low
Classification: General
Published: 3 months ago
Updated: 3 months ago


CVSS Score: 3.7
CVSS vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Identifiers: GHSA-82vx-mm6r-gg8w, CVE-2024-24754
References: Repository: https://github.com/brefphp/bref
Blast Radius: 9.0

Affected Packages

packagist:bref/bref
Dependent packages: 37
Dependent repositories: 267
Downloads: 4,331,657 total
Affected Version Ranges: < 2.1.13
Fixed in: 2.1.13
All affected versions: 0.1.0, 0.2.0, 0.2.1, 0.2.2, 0.2.3, 0.2.4, 0.2.5, 0.2.6, 0.2.7, 0.2.8, 0.2.9, 0.2.10, 0.2.11, 0.2.12, 0.2.13, 0.2.14, 0.2.15, 0.2.16, 0.2.17, 0.2.18, 0.2.19, 0.2.20, 0.2.21, 0.2.22, 0.2.23, 0.2.24, 0.2.25, 0.2.26, 0.2.27, 0.2.28, 0.2.29, 0.2.30, 0.2.31, 0.2.32, 0.2.33, 0.2.34, 0.2.35, 0.2.36, 0.2.37, 0.3.0, 0.3.1, 0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6, 0.3.7, 0.3.8, 0.3.9, 0.4.0, 0.4.1, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8, 0.5.9, 0.5.10, 0.5.11, 0.5.12, 0.5.13, 0.5.14, 0.5.15, 0.5.16, 0.5.17, 0.5.18, 0.5.19, 0.5.20, 0.5.21, 0.5.22, 0.5.23, 0.5.24, 0.5.25, 0.5.26, 0.5.27, 0.5.28, 0.5.29, 0.5.30, 0.5.31, 0.5.32, 0.5.33, 1.0.0, 1.0.1, 1.0.2, 1.1.0, 1.1.1, 1.1.2, 1.1.3, 1.1.4, 1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5, 1.2.6, 1.2.7, 1.2.8, 1.2.9, 1.2.10, 1.2.11, 1.2.12, 1.2.13, 1.2.14, 1.3.0, 1.3.1, 1.3.2, 1.3.3, 1.3.4, 1.3.5, 1.3.6, 1.4.0, 1.4.1, 1.4.2, 1.5.0, 1.5.1, 1.5.2, 1.5.3, 1.5.4, 1.5.5, 1.5.6, 1.5.7, 1.5.8, 1.6.0, 1.7.0, 1.7.1, 1.7.2, 1.7.3, 1.7.4, 1.7.5, 1.7.6, 1.7.7, 1.7.8, 1.7.9, 1.7.10, 1.7.11, 1.7.12, 1.7.13, 1.7.14, 1.7.15, 1.7.16, 1.7.17, 1.7.18, 1.7.19, 1.7.20, 1.7.21, 1.7.22, 1.7.23, 1.7.24, 1.7.25, 1.7.26, 1.7.27, 1.7.28, 1.7.29, 1.7.30, 1.7.31, 1.7.32, 1.7.33, 1.7.34, 1.7.35, 1.7.36, 1.7.37, 1.7.38, 1.7.39, 1.7.40, 1.7.41, 1.7.42, 1.7.43, 1.7.44, 1.7.45, 1.7.46, 1.7.47, 2.0.0, 2.0.1, 2.0.2, 2.0.3, 2.0.4, 2.0.5, 2.0.6, 2.0.7, 2.0.8, 2.0.9, 2.0.10, 2.0.11, 2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.11, 2.1.12
All unaffected versions: 2.1.13, 2.1.14, 2.1.15, 2.1.16, 2.1.17, 2.1.18, 2.1.19, 2.1.20, 2.1.21