CVE-2023-47643 (PHP, published 2023-11-21T20:15Z)
Description: value: SuiteCRM is a Customer Relationship Management (CRM) software application. Prior to version 8.4.2, Graphql Introspection is enabled without authentication, exposing the scheme defining all object types, arguments, and functions. An attacker can obtain the GraphQL schema and understand the entire attack surface of the API, including sensitive fields such as UserHash. This issue is patched in version 8.4.2. There are no known workarounds.
Severity: nan (CVSS v3 base score: 5.3)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/salesagility/SuiteCRM-Core
Files changed:
README.md
VERSION
config/core_services.yaml
config/packages/api_platform.yaml
config/packages/security.php
config/routes/api_platform.yaml
config/services/graphql/graphql_allow_introspection.yaml
core/app/common/package.json
core/app/core/package.json
core/backend/Kernel.php
core/backend/Migrations/Version20231108164138.php
core/backend/Security/GraphqlIntrospectionManager.php
package.json
public/index.php
public/legacy/modules/AOP_Case_Updates/AOP_Case_Updates.php
Commit message: SuiteCRM 8.4.2 Release


Vulnerable code:
# SuiteCRM 8.4.1
8.4.1
      enabled: true
    graphql_playground:
        ['path' => '^/api/graphql/graphiql*', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            'access_control' => [
                ['path' => '^/login$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/session-status$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/logout$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/saml/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/saml/metadata', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/saml/acs', 'roles' => 'ROLE_USER'],
                ['path' => '^/saml/logout', 'roles' => 'ROLE_USER'],
                ['path' => '^/logged-out', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/auth', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/auth/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/auth/session-status', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/auth/logout', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/$', 'roles' => 'ROLE_USER'],
                ['path' => '^/api', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/api/graphql', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/api/graphql/graphiql*', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
                ['path' => '^/', 'roles' => 'IS_AUTHENTICATED_FULLY']
            ]
  controller: api_platform.graphql.action.graphiql
  "version": "8.4.1",
  "version": "8.4.1",
  "version": "8.4.1",
        $this->name = SugarCleaner::cleanHtml($this->name);

Fixed code:
# SuiteCRM 8.4.2
8.4.2
      $graphqlShowDocs: '%graphql.graphql_show_docs%'

  graphql.introspection_manager:
    alias: App\Security\GraphqlIntrospectionManager
    public: true

    graphql_playground:
      enabled: true

    $appEnv = $env['APP_ENV'] ?? 'prod';
    $showDocs = $env['GRAPHQL_SHOW_DOCS'] ?? ($appEnv === 'dev');
    if ($showDocs === 'false' || $showDocs === false) {
        $baseAccessControl = array_merge([['path' => '^/docs', 'roles' => 'NO_ACCESS']], $baseAccessControl);
    }

        $samlAccessControl = [
            ['path' => '^/login$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/session-status$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/logout$', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/saml/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/saml/metadata', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/saml/acs', 'roles' => 'ROLE_USER'],
            ['path' => '^/saml/logout', 'roles' => 'ROLE_USER'],
            ['path' => '^/logged-out', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/auth', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/auth/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/auth/session-status', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/auth/logout', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/$', 'roles' => 'ROLE_USER'],
            ['path' => '^/api', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/api/graphql', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
            ['path' => '^/', 'roles' => 'IS_AUTHENTICATED_FULLY']
        ];

        if (!$showDocs) {
            $samlAccessControl = array_merge([['path' => '^/docs', 'roles' => 'NO_ACCESS']], $samlAccessControl);
        }

            'access_control' => $samlAccessControl
  controller: api_platform.graphql.action.graphql_playground
parameters:
  graphql.graphql_show_docs: '%env(default::bool:GRAPHQL_SHOW_DOCS)%'
  "version": "8.4.2",
  "version": "8.4.2",

    /**
     * Enable/disable graphql introspection
     * @return void
     */
    public function configureGraphqlIntrospection(): void
    {
        if ($this->container->has('graphql.introspection_manager')) {
            $this->container->get('graphql.introspection_manager')->configure();
        }
    }
<?php

declare(strict_types=1);

namespace App\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20231108164138  extends BaseMigration implements ContainerAwareInterface
{
    use EnvHandlingMigrationTrait;

    /**
     * @var ContainerInterface
     */
    protected $container;

    /**
     * @var LoggerInterface
     */
    protected $upgradeLogger;

    public function getDescription() : string
    {
        return 'Remove pdf from allowed_preview';
    }

    public function up(Schema $schema) : void
    {
        $systemConfigsHandler = $this->container->get('app.system-configs');
        $systemConfigs = $systemConfigsHandler->getConfigs();
        if (isset($systemConfigs['allowed_preview']) && in_array('pdf', $systemConfigs['allowed_preview'])) {
            $key = array_search('pdf', $systemConfigs['allowed_preview']);
            unset($systemConfigs['allowed_preview'][$key]);
            $systemConfigsHandler->updateSystemConfig($systemConfigs);
            $this->log('Removed PDF from allowed_preview inside config file.');
            return;
        }

        $this->log('PDF was not found in allowed_preview config skipping...');

    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs

    }
}
<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2023 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

namespace App\Security;

use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\DisableIntrospection;

class GraphqlIntrospectionManager
{
    /**
     * @var bool|null
     */
    private $graphqlShowDocs;

    /**
     * @param bool|null $graphqlShowDocs
     */
    public function __construct(
        ?bool $graphqlShowDocs
    ) {
        $this->graphqlShowDocs = $graphqlShowDocs;
    }

    /**
     * Enable/disable graphql introspection
     * @return void
     */
    public function configure(): void
    {
        $env = $_ENV ?? [];
        $appEnv = $env['APP_ENV'] ?? 'prod';
        $showDocs = $this->graphqlShowDocs ?? ($appEnv === 'dev');

        if ($showDocs === false) {
            DocumentValidator::addRule(new DisableIntrospection());
        }
    }

}
  "version": "8.4.2",
    $kernel->configureGraphqlIntrospection();

        $this->name = SugarCleaner::cleanHtml($this->name ?? '');