VYPR
Critical severityNVD Advisory· Published Sep 28, 2023· Updated Sep 23, 2024

Drupal core - Critical - Cache poisoning - SA-CORE-2023-006

CVE-2023-5256

Description

In certain scenarios, Drupal's JSON:API module will output error backtraces. With some configurations, this may cause sensitive information to be cached and made available to anonymous users, leading to privilege escalation.

This vulnerability only affects sites with the JSON:API module enabled, and can be mitigated by uninstalling JSON:API.

The core REST and contributed GraphQL modules are not affected.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Drupal's JSON:API module can output error backtraces that, when cached, expose sensitive information to anonymous users, enabling privilege escalation.

Vulnerability

Overview In certain scenarios, the JSON:API module in Drupal core outputs error backtraces that may contain sensitive information [2]. When caching is enabled on affected sites, these error responses can be cached and served to subsequent anonymous users, leading to unintended information disclosure and potential privilege escalation. The vulnerability stems from insufficient sanitization of error output and missing cache tags to prevent caching of error responses containing backtraces [4].

Exploitation

Conditions The vulnerability only affects sites where the JSON:API module is enabled, and it requires that the site's caching mechanism is active. An attacker can trigger error conditions (e.g., malformed API requests) that cause the module to return a backtrace. The response is then cached, making the backtrace available to any anonymous user without authentication. No special privileges are needed to exploit this; only the ability to send requests to the JSON:API endpoint [3].

Impact

Successful exploitation results in the exposure of sensitive system information such as file paths, database credentials, configuration details, or other secrets present in the error backtrace. This information can be used to further compromise the Drupal site, potentially leading to full administrative access. The Drupal security team has rated this vulnerability as critical due to the risk of privilege escalation [3].

Mitigation

The Drupal project released security updates for all supported versions (9.5.11, 10.0.11, 10.1.4) that fix the issue by hiding error backtraces and adding proper cache tags [3][4]. As an immediate workaround, administrators can uninstall the JSON:API module if it is not required. The core REST and contributed GraphQL modules are not affected [2][3]. Drupal Steward WAF partners may provide partial mitigation, but updating to the patched version is strongly recommended.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
drupal/corePackagist
>= 8.7.0, < 9.5.119.5.11
drupal/corePackagist
>= 10.0.0, < 10.0.1110.0.11
drupal/corePackagist
>= 10.1.0, < 10.1.410.1.4

Affected products

3

Patches

3
5495dc530e3a

SA-CORE-2023-006 by ghostccamm, effulgentsia, larowlan, xjm, pwolanin, catch, Wim Leers, mcdruid, benjifisher

https://github.com/drupal/corexjmSep 19, 2023via ghsa
4 files changed · +30 3
  • modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php+10 1 modified
    @@ -51,6 +51,12 @@ public function __construct(AccountInterface $current_user) {
       public function normalize($object, $format = NULL, array $context = []) {
         $cacheability = new CacheableMetadata();
         $cacheability->addCacheableDependency($object);
    +
    +    $cacheability->addCacheTags(['config:system.logging']);
    +    if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
    +      $cacheability->setCacheMaxAge(0);
    +    }
    +
         return new HttpExceptionNormalizerValue($cacheability, static::rasterizeValueRecursive($this->buildErrorObjects($object)));
       }
     
    @@ -89,7 +95,10 @@ protected function buildErrorObjects(HttpException $exception) {
         if ($exception->getCode() !== 0) {
           $error['code'] = (string) $exception->getCode();
         }
    -    if ($this->currentUser->hasPermission('access site reports')) {
    +
    +    $is_verbose_reporting = \Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
    +    $site_report_access = $this->currentUser->hasPermission('access site reports');
    +    if ($site_report_access && $is_verbose_reporting) {
           // The following information may contain sensitive information. Only show
           // it to authorized users.
           $error['source'] = [
    
  • modules/jsonapi/tests/src/Functional/ResourceTestBase.php+10 1 modified
    @@ -221,6 +221,8 @@ public function setUp() {
     
         $this->serializer = $this->container->get('jsonapi.serializer');
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Ensure the anonymous user role has no permissions at all.
         $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
         foreach ($user_role->getPermissions() as $permission) {
    @@ -725,7 +727,14 @@ protected function assertResourceResponse($expected_status_code, $expected_docum
         // Expected cache tags: X-Drupal-Cache-Tags header.
         $this->assertSame($expected_cache_tags !== FALSE, $response->hasHeader('X-Drupal-Cache-Tags'));
         if (is_array($expected_cache_tags)) {
    -      $this->assertEqualsCanonicalizing($expected_cache_tags, explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]));
    +      $actual_cache_tags = explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]);
    +
    +      $tag = 'config:system.logging';
    +      if (!in_array($tag, $expected_cache_tags) && in_array($tag, $actual_cache_tags)) {
    +        $expected_cache_tags[] = $tag;
    +      }
    +
    +      $this->assertEqualsCanonicalizing($expected_cache_tags, $actual_cache_tags);
         }
     
         // Expected cache contexts: X-Drupal-Cache-Contexts header.
    
  • modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php+3 1 modified
    @@ -65,6 +65,8 @@ protected function setUpAuthorization($method) {
       public function setUp(): void {
         parent::setUp();
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Create a "Camelids" node type.
         NodeType::create([
           'name' => 'Camelids',
    @@ -99,7 +101,7 @@ public function testApiJsonNotSupportedInRest() {
           400,
           FALSE,
           $response,
    -      ['4xx-response', 'config:user.role.anonymous', 'http_response', 'node:1'],
    +      ['4xx-response', 'config:system.logging', 'config:user.role.anonymous', 'http_response', 'node:1'],
           ['url.query_args:_format', 'url.site', 'user.permissions'],
           'MISS',
           'MISS'
    
  • modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php+7 0 modified
    @@ -2,6 +2,8 @@
     
     namespace Drupal\Tests\jsonapi\Unit\Normalizer;
     
    +use Drupal\Core\Config\ConfigFactory;
    +use Drupal\Core\Config\ImmutableConfig;
     use Drupal\Core\Session\AccountInterface;
     use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
     use Drupal\Tests\UnitTestCase;
    @@ -26,6 +28,11 @@ public function testNormalize() {
         $request_stack->getCurrentRequest()->willReturn(Request::create('http://localhost/'));
         $container = $this->prophesize(ContainerInterface::class);
         $container->get('request_stack')->willReturn($request_stack->reveal());
    +    $config = $this->prophesize(ImmutableConfig::class);
    +    $config->get('error_level')->willReturn(ERROR_REPORTING_DISPLAY_VERBOSE);
    +    $config_factory = $this->prophesize(ConfigFactory::class);
    +    $config_factory->get('system.logging')->willReturn($config->reveal());
    +    $container->get('config.factory')->willReturn($config_factory->reveal());
         \Drupal::setContainer($container->reveal());
         $exception = new AccessDeniedHttpException('lorem', NULL, 13);
         $current_user = $this->prophesize(AccountInterface::class);
    
d4fe67562ee3

SA-CORE-2023-006 by ghostccamm, effulgentsia, larowlan, xjm, pwolanin, catch, Wim Leers, mcdruid, benjifisher

https://github.com/drupal/corexjmSep 19, 2023via ghsa
4 files changed · +30 3
  • modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php+10 1 modified
    @@ -51,6 +51,12 @@ public function __construct(AccountInterface $current_user) {
       public function normalize($object, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
         $cacheability = new CacheableMetadata();
         $cacheability->addCacheableDependency($object);
    +
    +    $cacheability->addCacheTags(['config:system.logging']);
    +    if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
    +      $cacheability->setCacheMaxAge(0);
    +    }
    +
         return new HttpExceptionNormalizerValue($cacheability, static::rasterizeValueRecursive($this->buildErrorObjects($object)));
       }
     
    @@ -89,7 +95,10 @@ protected function buildErrorObjects(HttpException $exception) {
         if ($exception->getCode() !== 0) {
           $error['code'] = (string) $exception->getCode();
         }
    -    if ($this->currentUser->hasPermission('access site reports')) {
    +
    +    $is_verbose_reporting = \Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
    +    $site_report_access = $this->currentUser->hasPermission('access site reports');
    +    if ($site_report_access && $is_verbose_reporting) {
           // The following information may contain sensitive information. Only show
           // it to authorized users.
           $error['source'] = [
    
  • modules/jsonapi/tests/src/Functional/ResourceTestBase.php+10 1 modified
    @@ -221,6 +221,8 @@ protected function setUp(): void {
     
         $this->serializer = $this->container->get('jsonapi.serializer');
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Ensure the anonymous user role has no permissions at all.
         $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
         foreach ($user_role->getPermissions() as $permission) {
    @@ -725,7 +727,14 @@ protected function assertResourceResponse($expected_status_code, $expected_docum
         // Expected cache tags: X-Drupal-Cache-Tags header.
         $this->assertSame($expected_cache_tags !== FALSE, $response->hasHeader('X-Drupal-Cache-Tags'));
         if (is_array($expected_cache_tags)) {
    -      $this->assertEqualsCanonicalizing($expected_cache_tags, explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]));
    +      $actual_cache_tags = explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]);
    +
    +      $tag = 'config:system.logging';
    +      if (!in_array($tag, $expected_cache_tags) && in_array($tag, $actual_cache_tags)) {
    +        $expected_cache_tags[] = $tag;
    +      }
    +
    +      $this->assertEqualsCanonicalizing($expected_cache_tags, $actual_cache_tags);
         }
     
         // Expected cache contexts: X-Drupal-Cache-Contexts header.
    
  • modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php+3 1 modified
    @@ -65,6 +65,8 @@ protected function setUpAuthorization($method) {
       protected function setUp(): void {
         parent::setUp();
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Create a "Camelids" node type.
         NodeType::create([
           'name' => 'Camelids',
    @@ -99,7 +101,7 @@ public function testApiJsonNotSupportedInRest() {
           400,
           FALSE,
           $response,
    -      ['4xx-response', 'config:user.role.anonymous', 'http_response', 'node:1'],
    +      ['4xx-response', 'config:system.logging', 'config:user.role.anonymous', 'http_response', 'node:1'],
           ['url.query_args:_format', 'url.site', 'user.permissions'],
           'MISS',
           'MISS'
    
  • modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php+7 0 modified
    @@ -2,6 +2,8 @@
     
     namespace Drupal\Tests\jsonapi\Unit\Normalizer;
     
    +use Drupal\Core\Config\ConfigFactory;
    +use Drupal\Core\Config\ImmutableConfig;
     use Drupal\Core\Session\AccountInterface;
     use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
     use Drupal\Tests\UnitTestCase;
    @@ -26,6 +28,11 @@ public function testNormalize() {
         $request_stack->getCurrentRequest()->willReturn(Request::create('http://localhost/'));
         $container = $this->prophesize(ContainerInterface::class);
         $container->get('request_stack')->willReturn($request_stack->reveal());
    +    $config = $this->prophesize(ImmutableConfig::class);
    +    $config->get('error_level')->willReturn(ERROR_REPORTING_DISPLAY_VERBOSE);
    +    $config_factory = $this->prophesize(ConfigFactory::class);
    +    $config_factory->get('system.logging')->willReturn($config->reveal());
    +    $container->get('config.factory')->willReturn($config_factory->reveal());
         \Drupal::setContainer($container->reveal());
         $exception = new AccessDeniedHttpException('lorem', NULL, 13);
         $current_user = $this->prophesize(AccountInterface::class);
    
1cd2741c2b43

SA-CORE-2023-006 by ghostccamm, effulgentsia, larowlan, xjm, pwolanin, catch, Wim Leers, mcdruid, benjifisher

https://github.com/drupal/corexjmSep 19, 2023via ghsa
4 files changed · +30 3
  • modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php+10 1 modified
    @@ -44,6 +44,12 @@ public function __construct(AccountInterface $current_user) {
       public function normalize($object, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
         $cacheability = new CacheableMetadata();
         $cacheability->addCacheableDependency($object);
    +
    +    $cacheability->addCacheTags(['config:system.logging']);
    +    if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
    +      $cacheability->setCacheMaxAge(0);
    +    }
    +
         return new HttpExceptionNormalizerValue($cacheability, static::rasterizeValueRecursive($this->buildErrorObjects($object)));
       }
     
    @@ -82,7 +88,10 @@ protected function buildErrorObjects(HttpException $exception) {
         if ($exception->getCode() !== 0) {
           $error['code'] = (string) $exception->getCode();
         }
    -    if ($this->currentUser->hasPermission('access site reports')) {
    +
    +    $is_verbose_reporting = \Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
    +    $site_report_access = $this->currentUser->hasPermission('access site reports');
    +    if ($site_report_access && $is_verbose_reporting) {
           // The following information may contain sensitive information. Only show
           // it to authorized users.
           $error['source'] = [
    
  • modules/jsonapi/tests/src/Functional/ResourceTestBase.php+10 1 modified
    @@ -221,6 +221,8 @@ protected function setUp(): void {
     
         $this->serializer = $this->container->get('jsonapi.serializer');
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Ensure the anonymous user role has no permissions at all.
         $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
         foreach ($user_role->getPermissions() as $permission) {
    @@ -725,7 +727,14 @@ protected function assertResourceResponse($expected_status_code, $expected_docum
         // Expected cache tags: X-Drupal-Cache-Tags header.
         $this->assertSame($expected_cache_tags !== FALSE, $response->hasHeader('X-Drupal-Cache-Tags'));
         if (is_array($expected_cache_tags)) {
    -      $this->assertEqualsCanonicalizing($expected_cache_tags, explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]));
    +      $actual_cache_tags = explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]);
    +
    +      $tag = 'config:system.logging';
    +      if (!in_array($tag, $expected_cache_tags) && in_array($tag, $actual_cache_tags)) {
    +        $expected_cache_tags[] = $tag;
    +      }
    +
    +      $this->assertEqualsCanonicalizing($expected_cache_tags, $actual_cache_tags);
         }
     
         // Expected cache contexts: X-Drupal-Cache-Contexts header.
    
  • modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php+3 1 modified
    @@ -65,6 +65,8 @@ protected function setUpAuthorization($method) {
       protected function setUp(): void {
         parent::setUp();
     
    +    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
    +
         // Create a "Camelids" node type.
         NodeType::create([
           'name' => 'Camelids',
    @@ -99,7 +101,7 @@ public function testApiJsonNotSupportedInRest() {
           400,
           FALSE,
           $response,
    -      ['4xx-response', 'config:user.role.anonymous', 'http_response', 'node:1'],
    +      ['4xx-response', 'config:system.logging', 'config:user.role.anonymous', 'http_response', 'node:1'],
           ['url.query_args:_format', 'url.site', 'user.permissions'],
           'MISS',
           'MISS'
    
  • modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php+7 0 modified
    @@ -2,6 +2,8 @@
     
     namespace Drupal\Tests\jsonapi\Unit\Normalizer;
     
    +use Drupal\Core\Config\ConfigFactory;
    +use Drupal\Core\Config\ImmutableConfig;
     use Drupal\Core\Session\AccountInterface;
     use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
     use Drupal\Tests\UnitTestCase;
    @@ -26,6 +28,11 @@ public function testNormalize() {
         $request_stack->getCurrentRequest()->willReturn(Request::create('http://localhost/'));
         $container = $this->prophesize(ContainerInterface::class);
         $container->get('request_stack')->willReturn($request_stack->reveal());
    +    $config = $this->prophesize(ImmutableConfig::class);
    +    $config->get('error_level')->willReturn(ERROR_REPORTING_DISPLAY_VERBOSE);
    +    $config_factory = $this->prophesize(ConfigFactory::class);
    +    $config_factory->get('system.logging')->willReturn($config->reveal());
    +    $container->get('config.factory')->willReturn($config_factory->reveal());
         \Drupal::setContainer($container->reveal());
         $exception = new AccessDeniedHttpException('lorem', NULL, 13);
         $current_user = $this->prophesize(AccountInterface::class);
    

Vulnerability mechanics

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

References

6

News mentions

1