VYPR
High severity8.8GHSA Advisory· Published May 13, 2026· Updated May 14, 2026

CVE-2026-42550

CVE-2026-42550

Description

Flight is an extensible micro-framework for PHP. Prior to 3.18.1, SimplePdo::insert(), SimplePdo::update(), and SimplePdo::delete() build SQL statements by concatenating the $table argument and the keys of the $data array directly into the query, with no identifier quoting and no validation. When an application forwards user-controlled data shapes to these helpers — a common and documented pattern, e.g. $db->insert('users', $request->data->getData()) — an attacker can inject arbitrary SQL by crafting malicious array keys. This vulnerability is fixed in 3.18.1.

Affected products

1

Patches

1
b8dd23aaa828

Merge pull request #692 from flightphp/security-fixes

https://github.com/flightphp/coren0nag0nApr 22, 2026via ghsa
5 files changed · +81 16
  • flight/commands/ControllerCommand.php+7 0 modified
    @@ -50,6 +50,13 @@ public function execute(string $controller): void
                 return;
             }
     
    +        $controller = basename($controller);
    +
    +        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', str_replace('Controller', '', $controller))) {
    +            $io->error('Controller name must contain only letters, numbers, and underscores.', true);
    +            return;
    +        }
    +
             if (!preg_match('/Controller$/', $controller)) {
                 $controller .= 'Controller';
             }
    
  • flight/database/SimplePdo.php+26 0 modified
    @@ -55,6 +55,17 @@ public function __construct(
             }
         }
     
    +    /**
    +     * Validates that an SQL identifier (table or column name) is safe for interpolation.
    +     * Throws PDOException on invalid identifier to prevent SQL injection.
    +     */
    +    protected function requireSafeIdentifier(string $identifier): void
    +    {
    +        if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $identifier)) {
    +            throw new PDOException("Unsafe SQL identifier: '$identifier'");
    +        }
    +    }
    +
         /**
          * Pulls one row from the query
          *
    @@ -319,6 +330,8 @@ public function transaction(callable $callback)
          */
         public function insert(string $table, array $data): string
         {
    +        $this->requireSafeIdentifier($table);
    +
             // Detect if this is a bulk insert (array of arrays)
             $isBulk = isset($data[0]) && is_array($data[0]);
     
    @@ -333,6 +346,10 @@ public function insert(string $table, array $data): string
                 $columns = array_keys($firstRow);
                 $columnCount = count($columns);
     
    +            foreach ($columns as $col) {
    +                $this->requireSafeIdentifier((string) $col);
    +            }
    +
                 // Validate all rows have same columns
                 foreach ($data as $index => $row) {
                     if (count($row) !== $columnCount) {
    @@ -363,6 +380,11 @@ public function insert(string $table, array $data): string
             } else {
                 // Single insert
                 $columns = array_keys($data);
    +
    +            foreach ($columns as $col) {
    +                $this->requireSafeIdentifier((string) $col);
    +            }
    +
                 $placeholders = array_fill(0, count($data), '?');
     
                 $sql = sprintf(
    @@ -396,8 +418,11 @@ public function insert(string $table, array $data): string
          */
         public function update(string $table, array $data, string $where, array $whereParams = []): int
         {
    +        $this->requireSafeIdentifier($table);
    +
             $sets = [];
             foreach (array_keys($data) as $column) {
    +            $this->requireSafeIdentifier((string) $column);
                 $sets[] = "$column = ?";
             }
     
    @@ -426,6 +451,7 @@ public function update(string $table, array $data, string $where, array $wherePa
          */
         public function delete(string $table, string $where, array $whereParams = []): int
         {
    +        $this->requireSafeIdentifier($table);
             $sql = "DELETE FROM $table WHERE $where";
             $stmt = $this->runQuery($sql, $whereParams);
             return $stmt->rowCount();
    
  • flight/Engine.php+28 12 modified
    @@ -204,10 +204,12 @@ public function init(): void
             $this->set('flight.case_sensitive', false);
             $this->set('flight.handle_errors', true);
             $this->set('flight.log_errors', false);
    +        $this->set('flight.debug', false);
             $this->set('flight.views.path', './views');
             $this->set('flight.views.extension', '.php');
             $this->set('flight.content_length', true);
             $this->set('flight.v2.output_buffering', false);
    +        $this->set('flight.allow_method_override', true);
     
             // Startup configuration
             $this->before('start', function () use ($self) {
    @@ -225,6 +227,8 @@ public function init(): void
                 // which causes a lot of problems. This will be removed
                 // in v4
                 $self->response()->v2_output_buffering = $this->get('flight.v2.output_buffering');
    +            // Propagate method override setting to Request
    +            $self->request()::$allowMethodOverride = (bool) $self->get('flight.allow_method_override');
             });
     
             $this->initialized = true;
    @@ -678,16 +682,24 @@ public function _start(): void
         public function _error(Throwable $e): void
         {
             $this->triggerEvent('flight.error', $e);
    -        $msg = sprintf(
    -            <<<'HTML'
    -            <h1>500 Internal Server Error</h1>
    -                <h3>%s (%s)</h3>
    -                <pre>%s</pre>
    -            HTML, // phpcs:ignore
    -            $e->getMessage(),
    -            $e->getCode(),
    -            $e->getTraceAsString()
    -        );
    +
    +        if ($this->get('flight.debug') === true) {
    +            $msg = sprintf(
    +                <<<'HTML'
    +                <h1>500 Internal Server Error</h1>
    +                    <h3>%s (%s)</h3>
    +                    <pre>%s</pre>
    +                HTML, // phpcs:ignore
    +                htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'),
    +                $e->getCode(),
    +                htmlspecialchars($e->getTraceAsString(), ENT_QUOTES, 'UTF-8')
    +            );
    +        } else {
    +            if ($this->get('flight.log_errors') === true) {
    +                error_log($e->getMessage() . "\n" . $e->getTraceAsString());
    +            }
    +            $msg = '<h1>500 Internal Server Error</h1>';
    +        }
     
             try {
                 $this->response()
    @@ -890,7 +902,7 @@ public function _redirect(string $url, int $code = 303): void
             }
     
             // Append base url to redirect url
    -        if ($base !== '/'   && strpos($url, '://') === false) {
    +        if ($base !== '/' && strpos($url, '://') === false) {
                 $url = $base . preg_replace('#/+#', '/', '/' . $url);
             }
     
    @@ -1001,7 +1013,11 @@ public function _jsonp(
             int $option = 0
         ): void {
             $json = $encode ? Json::encode($data, $option) : $data;
    -        $callback = $this->request()->query[$param];
    +        $callback = (string) $this->request()->query[$param];
    +
    +        if ($callback !== '' && !preg_match('/^[A-Za-z_$][\w$.]{0,127}$/', $callback)) {
    +            throw new Exception('Invalid JSONP callback name.');
    +        }
     
             $this->response()
                 ->status($code)
    
  • flight/net/Request.php+12 4 modified
    @@ -137,6 +137,12 @@ class Request
          */
         public string $servername;
     
    +    /**
    +     * Whether to allow HTTP method override via X-HTTP-Method-Override header or _method POST field.
    +     * Controlled by the flight.allow_method_override engine setting.
    +     */
    +    public static bool $allowMethodOverride = true;
    +
         /**
          * Stream path for where to pull the request body from
          */
    @@ -282,10 +288,12 @@ public static function getMethod(): string
         {
             $method = self::getVar('REQUEST_METHOD', 'GET');
     
    -        if (self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE') !== '') {
    -            $method = self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE');
    -        } elseif (isset($_REQUEST['_method']) === true) {
    -            $method = $_REQUEST['_method'];
    +        if (self::$allowMethodOverride === true) {
    +            if (self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE') !== '') {
    +                $method = self::getVar('HTTP_X_HTTP_METHOD_OVERRIDE');
    +            } elseif (isset($_REQUEST['_method']) === true) {
    +                $method = $_REQUEST['_method'];
    +            }
             }
     
             return strtoupper($method);
    
  • tests/EngineTest.php+8 0 modified
    @@ -87,6 +87,14 @@ public function testHandleErrorWithException(): void
         public function testHandleException(): void
         {
             $engine = new Engine();
    +        $this->expectOutputRegex('~\<h1\>500 Internal Server Error\</h1\>~');
    +        $engine->handleException(new Exception('thrown exception message', 20));
    +    }
    +
    +    public function testHandleExceptionDebugMode(): void
    +    {
    +        $engine = new Engine();
    +        $engine->set('flight.debug', true);
             $this->expectOutputRegex('~\<h1\>500 Internal Server Error\</h1\>[\s\S]*\<h3\>thrown exception message \(20\)\</h3\>~');
             $engine->handleException(new Exception('thrown exception message', 20));
         }
    

Vulnerability mechanics

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

References

5

News mentions

0

No linked articles in our index yet.