VYPR
High severityNVD Advisory· Published Jan 11, 2023· Updated Mar 10, 2025

Post mentions can be used to read any post on the forum without access control

CVE-2023-22487

Description

Flarum is a forum software for building communities. Using the mentions feature provided by the flarum/mentions extension, users can mention any post ID on the forum with the special @"<username>"#p<id> syntax. The following behavior never changes no matter if the actor should be able to read the mentioned post or not: A URL to the mentioned post is inserted into the actor post HTML, leaking its discussion ID and post number. The mentionsPosts relationship included in the POST /api/posts and PATCH /api/posts/<id> JSON responses leaks the full JSON:API payload of all mentioned posts without any access control. This includes the content, date, number and attributes added by other extensions. An attacker only needs the ability to create new posts on the forum to exploit the vulnerability. This works even if new posts require approval. If they have the ability to edit posts, the attack can be performed even more discreetly by using a single post to scan any size of database and hiding the attack post content afterward. The attack allows the leaking of all posts in the forum database, including posts awaiting approval, posts in tags the user has no access to, and private discussions created by other extensions like FriendsOfFlarum Byobu. This also includes non-comment posts like tag changes or renaming events. The discussion payload is not leaked but using the mention HTML payload it's possible to extract the discussion ID of all posts and combine all posts back together into their original discussions even if the discussion title remains unknown. All Flarum versions prior to 1.6.3 are affected. The vulnerability has been fixed and published as flarum/core v1.6.3. As a workaround, user can disable the mentions extension.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
flarum/mentionsPackagist
< 1.6.31.6.3

Affected products

1

Patches

1
ab1c868b978e

Merge pull request from GHSA-22m9-m3ww-53h3

https://github.com/flarum/frameworkSami MazouzJan 10, 2023via ghsa
6 files changed · +52 7
  • extensions/mentions/composer.json+1 1 modified
    @@ -19,7 +19,7 @@
             }
         ],
         "require": {
    -        "flarum/core": "^1.6"
    +        "flarum/core": "^1.6.3"
         },
         "autoload": {
             "psr-4": {
    
  • extensions/mentions/extend.php+0 2 modified
    @@ -91,11 +91,9 @@
             ]),
     
         (new Extend\ApiController(Controller\CreatePostController::class))
    -        ->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy'])
             ->addOptionalInclude('mentionsGroups'),
     
         (new Extend\ApiController(Controller\UpdatePostController::class))
    -        ->addInclude(['mentionsPosts', 'mentionsPosts.mentionedBy'])
             ->addOptionalInclude('mentionsGroups'),
     
         (new Extend\ApiController(Controller\AbstractSerializeController::class))
    
  • extensions/mentions/src/ConfigureMentions.php+8 3 modified
    @@ -12,10 +12,12 @@
     use Flarum\Group\Group;
     use Flarum\Http\UrlGenerator;
     use Flarum\Post\CommentPost;
    +use Flarum\Post\PostRepository;
     use Flarum\Settings\SettingsRepositoryInterface;
     use Flarum\User\User;
     use Illuminate\Support\Str;
     use s9e\TextFormatter\Configurator;
    +use s9e\TextFormatter\Parser;
     
     class ConfigureMentions
     {
    @@ -115,7 +117,8 @@ private function configurePostMentions(Configurator $config)
     
             $tag->filterChain
                 ->prepend([static::class, 'addPostId'])
    -            ->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterPostMentions(tag); }');
    +            ->setJS('function(tag) { return flarum.extensions["flarum-mentions"].filterPostMentions(tag); }')
    +            ->addParameterByName('actor');
     
             $config->Preg->match('/\B@["|“](?<displayname>((?!"#[a-z]{0,3}[0-9]+).)+)["|”]#p(?<id>[0-9]+)\b/', $tagName);
         }
    @@ -124,9 +127,11 @@ private function configurePostMentions(Configurator $config)
          * @param $tag
          * @return bool
          */
    -    public static function addPostId($tag)
    +    public static function addPostId($tag, User $actor)
         {
    -        $post = CommentPost::find($tag->getAttribute('id'));
    +        $post = resolve(PostRepository::class)
    +            ->queryVisibleTo($actor)
    +            ->find($tag->getAttribute('id'));
     
             if ($post) {
                 $tag->setAttribute('discussionid', (int) $post->discussion_id);
    
  • extensions/mentions/tests/integration/api/PostMentionsTest.php+35 0 modified
    @@ -38,6 +38,7 @@ protected function setUp(): void
                 ],
                 'discussions' => [
                     ['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 2],
    +                ['id' => 50, 'title' => __CLASS__, 'is_private' => true, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 3, 'first_post_id' => 4, 'comment_count' => 1],
                 ],
                 'posts' => [
                     ['id' => 4, 'number' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="TobyFlarum___" id="5" number="2" discussionid="2" username="toby">@tobyuuu#5</POSTMENTION></r>'],
    @@ -49,6 +50,9 @@ protected function setUp(): void
                     ['id' => 10, 'number' => 11, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="Bad &quot;#p6 User" id="9" number="10" discussionid="2">@"Bad "#p6 User"#p9</POSTMENTION></r>'],
                     ['id' => 11, 'number' => 12, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 40, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="Bad &quot;#p6 User" id="9" number="10" discussionid="2">@"Bad "#p6 User"#p9</POSTMENTION></r>'],
                     ['id' => 12, 'number' => 13, 'discussion_id' => 2, 'created_at' => Carbon::now(), 'user_id' => 4, 'type' => 'comment', 'content' => '<r><POSTMENTION displayname="deleted_user" id="11" number="12" discussionid="2">@"acme"#p11</POSTMENTION></r>'],
    +
    +                // Restricted access
    +                ['id' => 50, 'number' => 1, 'discussion_id' => 50, 'created_at' => Carbon::now(), 'user_id' => 3, 'type' => 'comment', 'content' => '<r>no</r>'],
                 ],
                 'post_mentions_post' => [
                     ['post_id' => 4, 'mentions_post_id' => 5],
    @@ -128,6 +132,37 @@ public function mentioning_a_valid_post_with_new_format_works()
             $this->assertNotNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(4));
         }
     
    +    /**
    +     * @test
    +     */
    +    public function cannot_mention_a_post_without_access()
    +    {
    +        $response = $this->send(
    +            $this->request('POST', '/api/posts', [
    +                'authenticatedAs' => 1,
    +                'json' => [
    +                    'data' => [
    +                        'attributes' => [
    +                            'content' => '@"potato"#p50',
    +                        ],
    +                        'relationships' => [
    +                            'discussion' => ['data' => ['id' => 2]],
    +                        ],
    +                    ],
    +                ],
    +            ])
    +        );
    +
    +        $this->assertEquals(201, $response->getStatusCode());
    +
    +        $response = json_decode($response->getBody(), true);
    +
    +        $this->assertStringContainsString('potato', $response['data']['attributes']['contentHtml']);
    +        $this->assertEquals('@"potato"#p50', $response['data']['attributes']['content']);
    +        $this->assertStringNotContainsString('PostMention', $response['data']['attributes']['contentHtml']);
    +        $this->assertNull(CommentPost::find($response['data']['id'])->mentionsPosts->find(50));
    +    }
    +
         /**
          * @test
          */
    
  • framework/core/src/Formatter/Formatter.php+7 0 modified
    @@ -91,6 +91,13 @@ public function parse($text, $context = null, User $user = null)
         {
             $parser = $this->getParser($context);
     
    +         /*
    +          * Can be injected in tag or attribute filters by calling:
    +          * ->addParameterByName('actor') on the filter.
    +          * See the mentions extension's ConfigureMentions.php for an example.
    +          */
    +        $parser->registeredVars['actor'] = $user;
    +
             foreach ($this->parsingCallbacks as $callback) {
                 $text = $callback($parser, $context, $text, $user);
             }
    
  • framework/core/src/Post/PostRepository.php+1 1 modified
    @@ -29,7 +29,7 @@ public function query()
          * @param User|null $user
          * @return Builder<Post>
          */
    -    protected function queryVisibleTo(User $user = null)
    +    public function queryVisibleTo(User $user = null)
         {
             $query = $this->query();
     
    

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.