VYPR
High severity7.6NVD Advisory· Published Apr 24, 2026· Updated Apr 27, 2026

CVE-2026-31952

CVE-2026-31952

Description

Xibo is an open source digital signage platform with a web content management system and Windows display player software. Versions 1.7 through 4.4.0 have an SQL injection vulnerability in the API routes inside the CMS responsible for Filtering DataSets. This allows an authenticated user to to obtain and modify arbitrary data from the Xibo database by injecting specially crafted values in to the API filter parameter. Exploitation of the vulnerability is possible on behalf of an authorized user who has either of the Access to DataSet Feature privilege or the Access to the Layout Feature privilege. Users should upgrade to version 4.4.1 which fixes this issue. Customers who host their CMS with Xibo Signage have been patched if they are using 4.4, 4.3, 3.3, 2.3 or 1.8. Upgrading to a fixed version is necessary to remediate. Patches are available for earlier versions of Xibo CMS that are out of support, namely 3.3, 2.3, and 1.8.

Affected products

1
  • cpe:2.3:a:xibosignage:xibo:*:*:*:*:*:*:*:*
    Range: >=1.7.0,<4.4.1

Patches

3
ed213cb4f42d

DataSet: improve sanitization

https://github.com/xibosignage/xibo-cmsDan GarnerMar 5, 2026via nvd-ref
3 files changed · +102 12
  • lib/Controller/DataSetData.php+1 1 modified
    @@ -147,7 +147,7 @@ public function grid(Request $request, Response $response, $id)
             $filter = trim($filter, 'AND');
     
             // Work out the limits
    -        $filter = $this->gridRenderFilter(['filter' => $request->getParam('filter', $filter)], $sanitizedParams);
    +        $filter = $this->gridRenderFilter(['filter' => $filter], $sanitizedParams);
     
             try {
                 $data = $dataSet->getData(
    
  • lib/Entity/DataSet.php+4 11 modified
    @@ -31,6 +31,7 @@
     use Xibo\Factory\DataSetFactory;
     use Xibo\Factory\PermissionFactory;
     use Xibo\Helper\SanitizerService;
    +use Xibo\Helper\Sql;
     use Xibo\Service\ConfigServiceInterface;
     use Xibo\Service\DisplayNotifyServiceInterface;
     use Xibo\Service\LogServiceInterface;
    @@ -265,9 +266,6 @@ class DataSet implements \JsonSerializable
     
         private $countLast = 0;
     
    -    /** @var array Blacklist for SQL */
    -    private $blackList = array(';', 'INSERT', 'UPDATE', 'SELECT', 'DELETE', 'TRUNCATE', 'TABLE', 'FROM', 'WHERE');
    -
         /** @var  \Xibo\Helper\SanitizerService */
         private $sanitizerService;
     
    @@ -442,7 +440,7 @@ public function getUniqueColumnValues($columns)
                     if ($column->heading == $heading) {
                         // Formula column?
                         if ($column->dataSetColumnTypeId == 2) {
    -                        $select .= str_replace($this->blackList, '', htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
    +                        $select .= Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
                         }
                         else {
                             $select .= '`' . $column->heading . '`,';
    @@ -527,12 +525,7 @@ public function getData($filterBy = [], $options = [], $extraParams = [])
                     }
     
                     $count = 0;
    -                $formula = str_ireplace(
    -                    $this->blackList,
    -                    '',
    -                    htmlspecialchars_decode($column->formula, ENT_QUOTES),
    -                    $count
    -                );
    +                $formula = Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES), $count);
     
                     if ($count > 0) {
                         $this->getLog()->error(
    @@ -559,7 +552,7 @@ public function getData($filterBy = [], $options = [], $extraParams = [])
             if ($filter != '') {
                 // Support display filtering.
                 $filter = str_replace('[DisplayId]', $displayId, $filter);
    -            $filter = str_ireplace($this->blackList, '', $filter);
    +            $filter = Sql::cleanup($filter);
     
                 $body .= ' AND ' . $filter;
             }
    
  • lib/Helper/Sql.php+97 0 added
    @@ -0,0 +1,97 @@
    +<?php
    +
    +namespace Xibo\Helper;
    +
    +/**
    + * SQL definitions
    + */
    +class Sql
    +{
    +    const DISALLOWED_KEYWORDS = [
    +        ';', '@@', // Reduced symbols, handling comments via regex now
    +        'INSERT', 'UPDATE', 'SELECT', 'FROM', 'WHERE', 'DELETE', 'TRUNCATE',
    +        'TABLE', 'ALTER', 'GRANT', 'REVOKE', 'CREATE', 'DROP', 'UNION',
    +        'HAVING', 'GROUP', 'INTO', 'OUTFILE', 'DUMPFILE', 'PROCEDURE',
    +        'SLEEP', 'BENCHMARK', 'INFORMATION_SCHEMA', 'LOAD_FILE', 'LOCK',
    +        'EXECUTE', 'PREPARE', 'DEALLOCATE', 'SHOW', 'DESCRIBE', 'EXPLAIN',
    +        'CALL', 'HANDLER', 'RENAME', 'SHUTDOWN', 'SET', 'USE', 'FLUSH',
    +        'KILL', 'OPTIMIZE', 'REPAIR', 'ANALYZE', 'CHECK', 'CHECKSUM',
    +        'GET_LOCK', 'RELEASE_LOCK', 'IS_FREE_LOCK', 'IS_USED_LOCK',
    +        'MASTER_POS_WAIT', 'PASSWORD', 'USER', 'SYSTEM_USER', 'SESSION_USER',
    +        'CURRENT_USER', 'DATABASE', 'SCHEMA', 'VERSION', 'CONNECTION_ID',
    +        'LAST_INSERT_ID', 'ROW_COUNT', 'FOUND_ROWS', 'LOAD_XML', 'NAME_CONST',
    +        'DO', 'EXTRACTVALUE', 'UPDATEXML', 'XMLTYPE', 'DBMS_PIPE', 'PG_SLEEP',
    +        // Added String Builders & Encoders
    +        // we have specific use cases for 'CONCAT', so we keept that
    +        'CONCAT_WS', 'CHAR', 'UNHEX', 'HEX', 'ASCII', 'BIN', 'ORD', 'BASE64'
    +    ];
    +
    +    /**
    +     * Cleanup SQL (Maximum Paranoia for Legacy Code)
    +     * @param string $sql the SQL to clean
    +     * @param int $total the total number of replacements
    +     * @return string
    +     */
    +    public static function cleanup(string $sql, int &$total = 0): string
    +    {
    +        // 1. EXTRACT AND PROTECT STRING LITERALS (Preserve user data)
    +        $strings = [];
    +        $placeholderPrefix = '__SQL_STR_';
    +        $stringPattern = '/(\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*")/';
    +
    +        $sqlCleaned = preg_replace_callback($stringPattern, function ($matches) use (&$strings, $placeholderPrefix) {
    +            $id = count($strings);
    +            $strings[] = $matches[0];
    +            return $placeholderPrefix . $id . '__';
    +        }, $sql);
    +
    +        // 2. STRIP COMMENTS & ENCODINGS (Before keyword checks)
    +        $preCleanupCount = 0;
    +        $preCleanupPatterns = [
    +            '/(?:\/\*.*?\*\/|--[ \t].*?(?:\n|$)|#[^\n]*?(?:\n|$))/s', // Standard & Executable Comments
    +            '/\b0x[0-9a-fA-F]+\b/',                                   // Hex literals (e.g., 0x7e)
    +            '/\bb\'[01]+\'/i'                                         // Binary literals
    +        ];
    +        $sqlCleaned = preg_replace($preCleanupPatterns, '', $sqlCleaned, -1, $preCleanupCount);
    +        $total += $preCleanupCount;
    +
    +        // 3. PREPARE KEYWORD PATTERNS
    +        $wordKeywords = [];
    +        $symbolKeywords = [];
    +
    +        foreach (self::DISALLOWED_KEYWORDS as $keyword) {
    +            if (ctype_alnum(str_replace('_', '', $keyword))) {
    +                $wordKeywords[] = preg_quote($keyword, '/');
    +            } else {
    +                $symbolKeywords[] = $keyword;
    +            }
    +        }
    +
    +        $wordPattern = empty($wordKeywords) ? null : '/\b(' . implode('|', $wordKeywords) . ')\b/i';
    +
    +        // 4. RECURSIVE CLEANUP
    +        $count = 0;
    +        do {
    +            $symbolCount = 0;
    +            $wordCount = 0;
    +
    +            $sqlCleaned = str_ireplace($symbolKeywords, '', $sqlCleaned, $symbolCount);
    +
    +            if ($wordPattern) {
    +                $sqlCleaned = preg_replace($wordPattern, '', $sqlCleaned, -1, $wordCount);
    +            }
    +
    +            $count = $symbolCount + $wordCount;
    +            $total += $count;
    +        } while ($count > 0);
    +
    +        // 5. RESTORE STRING LITERALS
    +        if (!empty($strings)) {
    +            foreach ($strings as $id => $originalString) {
    +                $sqlCleaned = str_replace($placeholderPrefix . $id . '__', $originalString, $sqlCleaned);
    +            }
    +        }
    +
    +        return trim($sqlCleaned);
    +    }
    +}
    \ No newline at end of file
    
87e0a26b0c06

DataSet: improve sanitization

https://github.com/xibosignage/xibo-cmsDan GarnerMar 5, 2026via nvd-ref
3 files changed · +119 7
  • lib/Controller/DataSetData.php+1 1 modified
    @@ -123,7 +123,7 @@ public function grid($dataSetId)
             $filter = trim($filter, 'AND');
     
             // Work out the limits
    -        $filter = $this->gridRenderFilter(['filter' => $this->getSanitizer()->getParam('filter', $filter)]);
    +        $filter = $this->gridRenderFilter(['filter' => $filter]);
     
             try {
                 $data = $dataSet->getData([
    
  • lib/Entity/DataSet.php+4 6 modified
    @@ -19,6 +19,7 @@
     use Xibo\Factory\DataSetFactory;
     use Xibo\Factory\DisplayFactory;
     use Xibo\Factory\PermissionFactory;
    +use Xibo\Helper\Sql;
     use Xibo\Service\ConfigServiceInterface;
     use Xibo\Service\DateServiceInterface;
     use Xibo\Service\LogServiceInterface;
    @@ -207,9 +208,6 @@ class DataSet implements \JsonSerializable
     
         private $countLast = 0;
     
    -    /** @var array Blacklist for SQL */
    -    private $blackList = array(';', 'INSERT', 'UPDATE', 'SELECT', 'DELETE', 'TRUNCATE', 'TABLE', 'FROM', 'WHERE');
    -
         /** @var  SanitizerServiceInterface */
         private $sanitizer;
     
    @@ -364,7 +362,7 @@ public function getUniqueColumnValues($columns)
                     if ($column->heading == $heading) {
                         // Formula column?
                         if ($column->dataSetColumnTypeId == 2) {
    -                        $select .= str_replace($this->blackList, '', htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
    +                        $select .= Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
                         }
                         else {
                             $select .= '`' . $column->heading . '`,';
    @@ -445,7 +443,7 @@ public function getData($filterBy = [], $options = [])
                         continue;
                     }
     
    -                $formula = str_ireplace($this->blackList, '', htmlspecialchars_decode($column->formula, ENT_QUOTES));
    +                $formula = Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES));
                     $formula = str_replace('[DisplayId]', $displayId, $formula);
     
                     $heading = str_replace('[DisplayGeoLocation]', $displayGeoLocation, $formula) . ' AS `' . $column->heading . '`';
    @@ -463,7 +461,7 @@ public function getData($filterBy = [], $options = [])
             if ($filter != '') {
                 // Support display filtering.
                 $filter = str_ireplace('[DisplayId]', $displayId, $filter);
    -            $filter = str_replace($this->blackList, '', $filter);
    +            $filter = Sql::cleanup($filter);
     
                 $body .= ' AND ' . $filter;
             }
    
  • lib/Helper/Sql.php+114 0 added
    @@ -0,0 +1,114 @@
    +<?php
    +/*
    + * Copyright (C) 2026 Xibo Signage Ltd
    + *
    + * Xibo - Digital Signage - http://www.xibo.org.uk
    + *
    + * This file is part of Xibo.
    + *
    + * Xibo is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU Affero General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * any later version.
    + *
    + * Xibo is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU Affero General Public License for more details.
    + *
    + * You should have received a copy of the GNU Affero General Public License
    + * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +namespace Xibo\Helper;
    +
    +class Sql
    +{
    +    const DISALLOWED_KEYWORDS = [
    +        ';', '@@', // Reduced symbols, handling comments via regex now
    +        'INSERT', 'UPDATE', 'SELECT', 'FROM', 'WHERE', 'DELETE', 'TRUNCATE',
    +        'TABLE', 'ALTER', 'GRANT', 'REVOKE', 'CREATE', 'DROP', 'UNION',
    +        'HAVING', 'GROUP', 'INTO', 'OUTFILE', 'DUMPFILE', 'PROCEDURE',
    +        'SLEEP', 'BENCHMARK', 'INFORMATION_SCHEMA', 'LOAD_FILE', 'LOCK',
    +        'EXECUTE', 'PREPARE', 'DEALLOCATE', 'SHOW', 'DESCRIBE', 'EXPLAIN',
    +        'CALL', 'HANDLER', 'RENAME', 'SHUTDOWN', 'SET', 'USE', 'FLUSH',
    +        'KILL', 'OPTIMIZE', 'REPAIR', 'ANALYZE', 'CHECK', 'CHECKSUM',
    +        'GET_LOCK', 'RELEASE_LOCK', 'IS_FREE_LOCK', 'IS_USED_LOCK',
    +        'MASTER_POS_WAIT', 'PASSWORD', 'USER', 'SYSTEM_USER', 'SESSION_USER',
    +        'CURRENT_USER', 'DATABASE', 'SCHEMA', 'VERSION', 'CONNECTION_ID',
    +        'LAST_INSERT_ID', 'ROW_COUNT', 'FOUND_ROWS', 'LOAD_XML', 'NAME_CONST',
    +        'DO', 'EXTRACTVALUE', 'UPDATEXML', 'XMLTYPE', 'DBMS_PIPE', 'PG_SLEEP',
    +        // Added String Builders & Encoders
    +        // we have specific use cases for 'CONCAT', so we keept that
    +        'CONCAT_WS', 'CHAR', 'UNHEX', 'HEX', 'ASCII', 'BIN', 'ORD', 'BASE64'
    +    ];
    +
    +    /**
    +     * Cleanup SQL (Maximum Paranoia for Legacy Code)
    +     * @param string $sql the SQL to clean
    +     * @param int $total the total number of replacements
    +     * @return string
    +     */
    +    public static function cleanup(string $sql, int &$total = 0): string
    +    {
    +        // 1. EXTRACT AND PROTECT STRING LITERALS (Preserve user data)
    +        $strings = [];
    +        $placeholderPrefix = '__SQL_STR_';
    +        $stringPattern = '/(\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*")/';
    +
    +        $sqlCleaned = preg_replace_callback($stringPattern, function ($matches) use (&$strings, $placeholderPrefix) {
    +            $id = count($strings);
    +            $strings[] = $matches[0];
    +            return $placeholderPrefix . $id . '__';
    +        }, $sql);
    +
    +        // 2. STRIP COMMENTS & ENCODINGS (Before keyword checks)
    +        $preCleanupCount = 0;
    +        $preCleanupPatterns = [
    +            '/(?:\/\*.*?\*\/|--[ \t].*?(?:\n|$)|#[^\n]*?(?:\n|$))/s', // Standard & Executable Comments
    +            '/\b0x[0-9a-fA-F]+\b/',                                   // Hex literals (e.g., 0x7e)
    +            '/\bb\'[01]+\'/i'                                         // Binary literals
    +        ];
    +        $sqlCleaned = preg_replace($preCleanupPatterns, '', $sqlCleaned, -1, $preCleanupCount);
    +        $total += $preCleanupCount;
    +
    +        // 3. PREPARE KEYWORD PATTERNS
    +        $wordKeywords = [];
    +        $symbolKeywords = [];
    +
    +        foreach (self::DISALLOWED_KEYWORDS as $keyword) {
    +            if (ctype_alnum(str_replace('_', '', $keyword))) {
    +                $wordKeywords[] = preg_quote($keyword, '/');
    +            } else {
    +                $symbolKeywords[] = $keyword;
    +            }
    +        }
    +
    +        $wordPattern = empty($wordKeywords) ? null : '/\b(' . implode('|', $wordKeywords) . ')\b/i';
    +
    +        // 4. RECURSIVE CLEANUP
    +        $count = 0;
    +        do {
    +            $symbolCount = 0;
    +            $wordCount = 0;
    +
    +            $sqlCleaned = str_ireplace($symbolKeywords, '', $sqlCleaned, $symbolCount);
    +
    +            if ($wordPattern) {
    +                $sqlCleaned = preg_replace($wordPattern, '', $sqlCleaned, -1, $wordCount);
    +            }
    +
    +            $count = $symbolCount + $wordCount;
    +            $total += $count;
    +        } while ($count > 0);
    +
    +        // 5. RESTORE STRING LITERALS
    +        if (!empty($strings)) {
    +            foreach ($strings as $id => $originalString) {
    +                $sqlCleaned = str_replace($placeholderPrefix . $id . '__', $originalString, $sqlCleaned);
    +            }
    +        }
    +
    +        return trim($sqlCleaned);
    +    }
    +}
    \ No newline at end of file
    
b8d25fe6cb02

DataSet: improve sanitization

https://github.com/dasgarner/xibo-cmsDan GarnerMar 5, 2026via nvd-ref
3 files changed · +122 7
  • lib/Controller/DataSetData.php+1 1 modified
    @@ -125,7 +125,7 @@ public function grid($dataSetId)
             $filter = trim($filter, 'AND');
     
             // Work out the limits
    -        $filter = $this->gridRenderFilter(['filter' => $this->getSanitizer()->getParam('filter', $filter)]);
    +        $filter = $this->gridRenderFilter(['filter' => $filter]);
     
             $this->getState()->template = 'grid';
             $this->getState()->setData($dataSet->getData([
    
  • lib/Entity/DataSet.php+4 6 modified
    @@ -19,6 +19,7 @@
     use Xibo\Factory\DataSetFactory;
     use Xibo\Factory\DisplayFactory;
     use Xibo\Factory\PermissionFactory;
    +use Xibo\Helper\Sql;
     use Xibo\Service\ConfigServiceInterface;
     use Xibo\Service\DateServiceInterface;
     use Xibo\Service\LogServiceInterface;
    @@ -189,9 +190,6 @@ class DataSet implements \JsonSerializable
     
         private $countLast = 0;
     
    -    /** @var array Blacklist for SQL */
    -    private $blackList = array(';', 'INSERT', 'UPDATE', 'SELECT', 'DELETE', 'TRUNCATE', 'TABLE', 'FROM', 'WHERE');
    -
         /** @var  SanitizerServiceInterface */
         private $sanitizer;
     
    @@ -337,7 +335,7 @@ public function getUniqueColumnValues($columns)
                     if ($column->heading == $heading) {
                         // Formula column?
                         if ($column->dataSetColumnTypeId == 2) {
    -                        $select .= str_replace($this->blackList, '', htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
    +                        $select .= Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES)) . ' AS `' . $column->heading . '`,';
                         }
                         else {
                             $select .= '`' . $column->heading . '`,';
    @@ -420,7 +418,7 @@ public function getData($filterBy = [], $options = [], $extraParams = [])
                         continue;
                     }
     
    -                $formula = str_replace($this->blackList, '', htmlspecialchars_decode($column->formula, ENT_QUOTES));
    +                $formula = Sql::cleanup(htmlspecialchars_decode($column->formula, ENT_QUOTES));
                     $formula = str_replace('[DisplayId]', $displayId, $formula);
     
                     $heading = str_replace('[DisplayGeoLocation]', $displayGeoLocation, $formula) . ' AS `' . $column->heading . '`';
    @@ -438,7 +436,7 @@ public function getData($filterBy = [], $options = [], $extraParams = [])
             if ($filter != '') {
                 // Support display filtering.
                 $filter = str_replace('[DisplayId]', $displayId, $filter);
    -            $filter = str_replace($this->blackList, '', $filter);
    +            $filter = Sql::cleanup($filter);
     
                 $body .= ' AND ' . $filter;
             }
    
  • lib/Helper/Sql.php+117 0 added
    @@ -0,0 +1,117 @@
    +<?php
    +/*
    + * Copyright (C) 2026 Xibo Signage Ltd
    + *
    + * Xibo - Digital Signage - http://www.xibo.org.uk
    + *
    + * This file is part of Xibo.
    + *
    + * Xibo is free software: you can redistribute it and/or modify
    + * it under the terms of the GNU Affero General Public License as published by
    + * the Free Software Foundation, either version 3 of the License, or
    + * any later version.
    + *
    + * Xibo is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    + * GNU Affero General Public License for more details.
    + *
    + * You should have received a copy of the GNU Affero General Public License
    + * along with Xibo.  If not, see <http://www.gnu.org/licenses/>.
    + */
    +
    +namespace Xibo\Helper;
    +
    +/**
    + * SQL definitions
    + */
    +class Sql
    +{
    +    /**
    +     * Cleanup SQL (Maximum Paranoia for Legacy Code)
    +     * @param string $sql the SQL to clean
    +     * @param int $total the total number of replacements
    +     * @return string
    +     */
    +    public static function cleanup($sql, &$total = 0)
    +    {
    +        $disallowedKeywords = [
    +            ';', '@@', // Reduced symbols, handling comments via regex now
    +            'INSERT', 'UPDATE', 'SELECT', 'FROM', 'WHERE', 'DELETE', 'TRUNCATE',
    +            'TABLE', 'ALTER', 'GRANT', 'REVOKE', 'CREATE', 'DROP', 'UNION',
    +            'HAVING', 'GROUP', 'INTO', 'OUTFILE', 'DUMPFILE', 'PROCEDURE',
    +            'SLEEP', 'BENCHMARK', 'INFORMATION_SCHEMA', 'LOAD_FILE', 'LOCK',
    +            'EXECUTE', 'PREPARE', 'DEALLOCATE', 'SHOW', 'DESCRIBE', 'EXPLAIN',
    +            'CALL', 'HANDLER', 'RENAME', 'SHUTDOWN', 'SET', 'USE', 'FLUSH',
    +            'KILL', 'OPTIMIZE', 'REPAIR', 'ANALYZE', 'CHECK', 'CHECKSUM',
    +            'GET_LOCK', 'RELEASE_LOCK', 'IS_FREE_LOCK', 'IS_USED_LOCK',
    +            'MASTER_POS_WAIT', 'PASSWORD', 'USER', 'SYSTEM_USER', 'SESSION_USER',
    +            'CURRENT_USER', 'DATABASE', 'SCHEMA', 'VERSION', 'CONNECTION_ID',
    +            'LAST_INSERT_ID', 'ROW_COUNT', 'FOUND_ROWS', 'LOAD_XML', 'NAME_CONST',
    +            'DO', 'EXTRACTVALUE', 'UPDATEXML', 'XMLTYPE', 'DBMS_PIPE', 'PG_SLEEP',
    +            // Added String Builders & Encoders
    +            // we have specific use cases for 'CONCAT', so we keept that
    +            'CONCAT_WS', 'CHAR', 'UNHEX', 'HEX', 'ASCII', 'BIN', 'ORD', 'BASE64'
    +        ];
    +
    +        // 1. EXTRACT AND PROTECT STRING LITERALS (Preserve user data)
    +        $strings = [];
    +        $placeholderPrefix = '__SQL_STR_';
    +        $stringPattern = '/(\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*")/';
    +
    +        $sqlCleaned = preg_replace_callback($stringPattern, function ($matches) use (&$strings, $placeholderPrefix) {
    +            $id = count($strings);
    +            $strings[] = $matches[0];
    +            return $placeholderPrefix . $id . '__';
    +        }, $sql);
    +
    +        // 2. STRIP COMMENTS & ENCODINGS (Before keyword checks)
    +        $preCleanupCount = 0;
    +        $preCleanupPatterns = [
    +            '/(?:\/\*.*?\*\/|--[ \t].*?(?:\n|$)|#[^\n]*?(?:\n|$))/s', // Standard & Executable Comments
    +            '/\b0x[0-9a-fA-F]+\b/',                                   // Hex literals (e.g., 0x7e)
    +            '/\bb\'[01]+\'/i'                                         // Binary literals
    +        ];
    +        $sqlCleaned = preg_replace($preCleanupPatterns, '', $sqlCleaned, -1, $preCleanupCount);
    +        $total += $preCleanupCount;
    +
    +        // 3. PREPARE KEYWORD PATTERNS
    +        $wordKeywords = [];
    +        $symbolKeywords = [];
    +
    +        foreach ($disallowedKeywords as $keyword) {
    +            if (ctype_alnum(str_replace('_', '', $keyword))) {
    +                $wordKeywords[] = preg_quote($keyword, '/');
    +            } else {
    +                $symbolKeywords[] = $keyword;
    +            }
    +        }
    +
    +        $wordPattern = empty($wordKeywords) ? null : '/\b(' . implode('|', $wordKeywords) . ')\b/i';
    +
    +        // 4. RECURSIVE CLEANUP
    +        $count = 0;
    +        do {
    +            $symbolCount = 0;
    +            $wordCount = 0;
    +
    +            $sqlCleaned = str_ireplace($symbolKeywords, '', $sqlCleaned, $symbolCount);
    +
    +            if ($wordPattern) {
    +                $sqlCleaned = preg_replace($wordPattern, '', $sqlCleaned, -1, $wordCount);
    +            }
    +
    +            $count = $symbolCount + $wordCount;
    +            $total += $count;
    +        } while ($count > 0);
    +
    +        // 5. RESTORE STRING LITERALS
    +        if (!empty($strings)) {
    +            foreach ($strings as $id => $originalString) {
    +                $sqlCleaned = str_replace($placeholderPrefix . $id . '__', $originalString, $sqlCleaned);
    +            }
    +        }
    +
    +        return trim($sqlCleaned);
    +    }
    +}
    

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.