CVE-2022-39233 (published 2022-10-19T11:15Z)

Tuleap is a Free & Open Source Suite to improve management of software developments and collaboration. In versions 12.9.99.228 and above, prior to 14.0.99.24, authorizations are not properly verified when updating the branch prefix used by the GitLab repository integration. Authenticated users can change the branch prefix of any of the GitLab repository integration they can see vie the REST endpoint `PATCH /gitlab_repositories/{id}`. This action should be restricted to Git administrators. This issue is patched in Tuleap Community Edition 14.0.99.24 and Tuleap Enterprise Edition 14.0-3. There are no known workarounds.

CVSS v3 base score: 5.4, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-862
cwe_name: Missing Authorization
description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are not applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/Enalean/tuleap
commit_details: repo_url: https://github.com/Enalean/tuleap
author: Yannis ROSSETTO
author_date: 2022-09-20 16:46:38+02:00
msg: fix!: GitLab repository branch prefix can be updated by any user

This closes request #28848 GitLab repository branch prefix can be updated by any user

Any user is able to update the branch prefix of any GitLab repository integration
using the REST route PATCH /gitlab_repositories/{id}.

This is wrong, only Git adminsitrators must be allowed to do that.

Change-Id: I70febc7cc3b49d5687d8f6a3f1a061710f6cf5ef
repository: repo_url: https://github.com/Enalean/tuleap
repo_name: Enalean/tuleap
description: Tuleap is an Open Source Suite to improve management of software developments and collaboration. With a single web-based solution, project managers, developers & quality managers can easily build, deploy software projects.
repo_language: PHP
owner: Enalean
file_changes: filename: CreateBranchPrefixUpdater.php
change_type: ModificationType.MODIFY
diff: @@ -21,6 +21,9 @@ declare(strict_types=1);
 
 namespace Tuleap\Gitlab\Artifact\Action;
 
+use GitPermissionsManager;
+use GitUserNotAdminException;
+use PFUser;
 use Tuleap\Git\Branch\BranchName;
 use Tuleap\Git\Branch\InvalidBranchNameException;
 use Tuleap\Gitlab\Repository\GitlabRepositoryIntegrationFactory;
@@ -32,6 +35,7 @@ final class CreateBranchPrefixUpdater
 
     public function __construct(
         private GitlabRepositoryIntegrationFactory $integration_factory,
+        private GitPermissionsManager $git_permissions_manager,
         private SaveIntegrationBranchPrefix $branch_prefix_saver,
     ) {
     }
@@ -39,14 +43,20 @@ final class CreateBranchPrefixUpdater
     /**
      * @throws GitlabRepositoryIntegrationNotFoundException
      * @throws InvalidBranchNameException
+     * @throws GitUserNotAdminException
      */
-    public function updateBranchPrefix(int $integration_id, string $prefix): void
+    public function updateBranchPrefix(PFUser $user, int $integration_id, string $prefix): void
     {
         $gitlab_repository = $this->integration_factory->getIntegrationById($integration_id);
         if (! $gitlab_repository) {
             throw new GitlabRepositoryIntegrationNotFoundException($integration_id);
         }
 
+        $project = $gitlab_repository->getProject();
+        if (! $this->git_permissions_manager->userIsGitAdmin($user, $project)) {
+            throw new GitUserNotAdminException();
+        }
+
         BranchName::fromBranchNameShortHand($prefix . self::FAKE_BRANCH_NAME);
 
         $this->branch_prefix_saver->setCreateBranchPrefixForIntegration(

programming_language: PHP
filename: GitlabRepositoryResource.php
change_type: ModificationType.MODIFY
diff: @@ -473,13 +473,15 @@ final class GitlabRepositoryResource
             $prefix_updater = new CreateBranchPrefixUpdater(
                 new GitlabRepositoryIntegrationFactory(
                     new GitlabRepositoryIntegrationDao(),
-                    ProjectManager::instance()
+                    ProjectManager::instance(),
                 ),
-                new CreateBranchPrefixDao()
+                $this->getGitPermissionsManager(),
+                new CreateBranchPrefixDao(),
             );
 
             try {
                 $prefix_updater->updateBranchPrefix(
+                    $current_user,
                     $id,
                     $patch_representation->create_branch_prefix
                 );
@@ -490,6 +492,8 @@ final class GitlabRepositoryResource
                     400,
                     $exception->getMessage()
                 );
+            } catch (GitUserNotAdminException $exception) {
+                throw new RestException(401, "User must be Git administrator.");
             }
         }
 

programming_language: PHP
filename: CreateBranchPrefixUpdaterTest.php
change_type: ModificationType.MODIFY
diff: @@ -22,11 +22,14 @@ declare(strict_types=1);
 
 namespace Tuleap\Gitlab\Artifact\Action;
 
+use GitPermissionsManager;
+use GitUserNotAdminException;
 use Tuleap\Git\Branch\InvalidBranchNameException;
 use Tuleap\Gitlab\Repository\GitlabRepositoryIntegrationFactory;
 use Tuleap\Gitlab\Repository\GitlabRepositoryIntegrationNotFoundException;
 use Tuleap\Gitlab\Test\Builder\RepositoryIntegrationBuilder;
 use Tuleap\Gitlab\Test\Stubs\SaveIntegrationBranchPrefixStub;
+use Tuleap\Test\Builders\UserTestBuilder;
 use Tuleap\Test\PHPUnit\TestCase;
 
 final class CreateBranchPrefixUpdaterTest extends TestCase
@@ -38,11 +41,16 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
     private $integration_factory;
     private SaveIntegrationBranchPrefixStub $branch_prefix_saver;
     private string $branch_prefix;
+    /**
+     * @var GitPermissionsManager&\PHPUnit\Framework\MockObject\MockObject
+     */
+    private $git_permissions_manager;
 
     protected function setUp(): void
     {
-        $this->integration_factory = $this->createMock(GitlabRepositoryIntegrationFactory::class);
-        $this->branch_prefix_saver = SaveIntegrationBranchPrefixStub::withCallCount();
+        $this->integration_factory     = $this->createMock(GitlabRepositoryIntegrationFactory::class);
+        $this->git_permissions_manager = $this->createMock(GitPermissionsManager::class);
+        $this->branch_prefix_saver     = SaveIntegrationBranchPrefixStub::withCallCount();
 
         $this->branch_prefix = 'dev-';
     }
@@ -51,10 +59,15 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
     {
         $updater = new CreateBranchPrefixUpdater(
             $this->integration_factory,
-            $this->branch_prefix_saver
+            $this->git_permissions_manager,
+            $this->branch_prefix_saver,
         );
 
-        $updater->updateBranchPrefix(self::INTEGRATION_ID, $this->branch_prefix);
+        $updater->updateBranchPrefix(
+            UserTestBuilder::anActiveUser()->build(),
+            self::INTEGRATION_ID,
+            $this->branch_prefix,
+        );
     }
 
     public function testItStoresTheBranchPrefix(): void
@@ -65,6 +78,11 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
             ->with(self::INTEGRATION_ID)
             ->willReturn(RepositoryIntegrationBuilder::aGitlabRepositoryIntegration(self::INTEGRATION_ID)->build());
 
+        $this->git_permissions_manager
+            ->expects(self::once())
+            ->method('userIsGitAdmin')
+            ->willReturn(true);
+
         $this->updateBranchPrefix();
 
         self::assertSame(1, $this->branch_prefix_saver->getCallCount());
@@ -78,6 +96,11 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
             ->with(self::INTEGRATION_ID)
             ->willReturn(RepositoryIntegrationBuilder::aGitlabRepositoryIntegration(self::INTEGRATION_ID)->build());
 
+        $this->git_permissions_manager
+            ->expects(self::once())
+            ->method('userIsGitAdmin')
+            ->willReturn(true);
+
         $this->branch_prefix = 'dev/';
         $this->updateBranchPrefix();
 
@@ -98,6 +121,25 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
         self::assertSame(0, $this->branch_prefix_saver->getCallCount());
     }
 
+    public function testItThrowsAnExceptionIfUserIsNotGitAdministrator(): void
+    {
+        $this->integration_factory
+            ->expects(self::once())
+            ->method('getIntegrationById')
+            ->with(self::INTEGRATION_ID)
+            ->willReturn(RepositoryIntegrationBuilder::aGitlabRepositoryIntegration(self::INTEGRATION_ID)->build());
+
+        $this->git_permissions_manager
+            ->expects(self::once())
+            ->method('userIsGitAdmin')
+            ->willReturn(false);
+
+        $this->expectException(GitUserNotAdminException::class);
+        $this->updateBranchPrefix();
+
+        self::assertSame(0, $this->branch_prefix_saver->getCallCount());
+    }
+
     public function testItThrowsAnExceptionIfBranchPrefixIsNotValid(): void
     {
         $this->integration_factory
@@ -106,6 +148,11 @@ final class CreateBranchPrefixUpdaterTest extends TestCase
             ->with(self::INTEGRATION_ID)
             ->willReturn(RepositoryIntegrationBuilder::aGitlabRepositoryIntegration(self::INTEGRATION_ID)->build());
 
+        $this->git_permissions_manager
+            ->expects(self::once())
+            ->method('userIsGitAdmin')
+            ->willReturn(true);
+
         $this->branch_prefix = 'dev:';
 
         $this->expectException(InvalidBranchNameException::class);

programming_language: PHP