CVE-2026-40784

FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration <= 1.91.2 - Authenticated (Board Member+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.91.3
Patched in
7d
Time to patch

Description

The FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.91.2 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Custom-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.91.2
PublishedApril 15, 2026
Last updatedApril 21, 2026
Affected pluginfluent-boards

What Changed in the Fix

Changes introduced in v1.91.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-40784 FluentBoards IDOR ## 1. Vulnerability Summary The **FluentBoards** plugin (<= 1.91.2) contains an Insecure Direct Object Reference (IDOR) vulnerability in its comment management system. The `CommentController` does not sufficiently validate whether the authenticated …

Show full research plan

Research Plan: CVE-2026-40784 FluentBoards IDOR

1. Vulnerability Summary

The FluentBoards plugin (<= 1.91.2) contains an Insecure Direct Object Reference (IDOR) vulnerability in its comment management system. The CommentController does not sufficiently validate whether the authenticated user has the authority to modify or delete a specific comment based on its comment_id. While the API requires a board_id in the URL, it fails to verify if the provided comment_id actually belongs to that board or if the user is the author/administrator of that comment. This allows any user with "Board Member" access (or higher) to modify comments across the entire system by manipulating the ID.

2. Attack Vector Analysis

  • Endpoint: POST /wp-json/fluent-boards/v1/boards/{board_id}/comments/{comment_id}
  • Method: POST (used for updates in the Fluent framework)
  • Authentication: Required (User must have at least "Board Member" permissions within the plugin).
  • Vulnerable Parameter: {comment_id} in the REST URL.
  • Payload Parameter: comment (string) containing the new comment text.
  • Preconditions:
    • The attacker must be logged in.
    • The attacker must be a member of at least one Board (to access the API).
    • The attacker needs to know the ID of the comment they wish to modify.

3. Code Flow

  1. Entry Point: The request hits the REST API route associated with CommentController@update.
  2. Controller Logic (app/Http/Controllers/CommentController.php):
    • The update method (truncated in provided source, but visible at line 147) receives $board_id and $comment_id from the URL.
    • It calls $this->commentSanitizeAndValidate() to process the comment body.
    • It attempts to update the comment via $this->commentService->update($commentData, $comment_id).
  3. Missing Check: There is no logic in the controller to verify:
    • That the comment identified by $comment_id belongs to the board identified by $board_id.
    • That the current user is the owner of the comment or a Board Administrator.
  4. Sink: The database is updated with the new comment content regardless of ownership.

4. Nonce Acquisition Strategy

FluentBoards uses the standard WordPress REST API nonce. It is localized in the admin dashboard.

  1. Identify Trigger: The FluentBoards dashboard is located at /wp-admin/admin.php?page=fluent-boards.
  2. Access Dashboard: Navigate to the FluentBoards page as the "Board Member" user.
  3. Extract Nonce: Use browser_eval to extract the nonce from the window.FluentBoardsAdmin global object.
    • JS Key: window.FluentBoardsAdmin.nonce
  4. Alternative: The standard REST nonce wp_rest might also work if passed in the X-WP-Nonce header.

5. Exploitation Strategy

Step 1: Data Gathering

Identify the Target Comment ID. For this PoC, we will create a comment as a "Victim" user and capture its ID.

Step 2: Perform the IDOR Update

Use the http_request tool to send a spoofed update request.

  • URL: http://localhost:8080/wp-json/fluent-boards/v1/boards/{ATTACKER_BOARD_ID}/comments/{VICTIM_COMMENT_ID}
  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: {EXTRACTED_NONCE}
    • Cookie: {ATTACKER_SESSION_COOKIES}
  • Payload:
    {
      "comment": "This comment has been modified by an unauthorized user.",
      "mentionData": []
    }
    

6. Test Data Setup

  1. Users:
    • victim_user (Subscriber role)
    • attacker_user (Subscriber role)
  2. Boards:
    • Victim Board (ID: 1): victim_user added as "Admin".
    • Attacker Board (ID: 2): attacker_user added as "Member".
  3. Content:
    • A task created on Victim Board.
    • A comment created by victim_user on that task (Target ID: X).
  4. Plugin Configuration: Ensure both users are assigned their respective boards via the FluentBoards interface.

7. Expected Results

  • The server returns a 200 OK or 201 Created response.
  • The response JSON contains the updated comment object with the new description.
  • The comment ID in the response matches {VICTIM_COMMENT_ID}.

8. Verification Steps

  1. Check via CLI:
    wp db query "SELECT description FROM wp_fboards_comments WHERE id = {VICTIM_COMMENT_ID}"
    
  2. Confirm Content: Verify the description matches the string: "This comment has been modified by an unauthorized user.".
  3. Ownership Check: Verify the created_by field in the database remains the victim_user ID, proving the comment was modified by someone else.

9. Alternative Approaches

  • IDOR Deletion: If an update vulnerability exists, a delete vulnerability often exists at the same level.
    • Endpoint: DELETE /wp-json/fluent-boards/v1/boards/{attacker_board_id}/comments/{victim_comment_id}
  • Board Settings IDOR: Check if BoardController::modifyAuthenticationPermission (line 309 in BoardService.php reference) can be triggered by a non-admin by providing a valid boardId in a POST request to the settings update endpoint.
  • Task IDOR: Attempt to move a task from a victim board to an attacker board by changing the board_id and stage_id in a task update request. (Check TaskController.php).
Research Findings
Static analysis — not yet PoC-verified

Summary

The FluentBoards plugin is vulnerable to Insecure Direct Object Reference (IDOR) due to insufficient validation that a requested object (such as a task, comment, or stage) actually belongs to the board specified in the API request. This allows authenticated attackers with Board Member access to view, modify, or delete tasks and comments on other boards by manipulating the IDs in the REST API endpoints.

Vulnerable Code

// app/Http/Controllers/CommentController.php line 212
public function deleteComment($board_id, $comment_id)
{
    try {
        $this->commentService->delete($comment_id);

---

// app/Http/Controllers/TaskController.php line 188
public function find($board_id, $task_id)
{
    $board_id = absint($board_id);
    $task_id = absint($task_id);
    try {

        $stageService = new StageService();

        $task = Task::findOrFail($task_id);

---

// app/Http/Controllers/CommentController.php line 330
public function updateCommentPrivacy($board_id, $comment_id)
{
    $comment = Comment::findOrFail($comment_id);

    // Check if user has permission to update the comment
    if ($comment->created_by != get_current_user_id()) {

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.2/app/Http/Controllers/BoardController.php /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.3/app/Http/Controllers/BoardController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.2/app/Http/Controllers/BoardController.php	2026-01-26 10:36:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.3/app/Http/Controllers/BoardController.php	2026-04-08 13:22:28.000000000 +0000
@@ -968,7 +968,7 @@
 
     public function archiveAllTasksInStage($board_id, $stage_id)
     {
-        $updates = $this->stageService->archiveAllTasksInStage($stage_id);
+        $updates = $this->stageService->archiveAllTasksInStage($stage_id, $board_id);
         return [
             'message'      => __('Tasks have been archived', 'fluent-boards'),
             'updatedTasks' => $updates,
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.2/app/Http/Controllers/CommentController.php /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.3/app/Http/Controllers/CommentController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.2/app/Http/Controllers/CommentController.php	2025-12-24 12:28:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/fluent-boards/1.91.3/app/Http/Controllers/CommentController.php	2026-04-08 13:22:28.000000000 +0000
@@ -27,7 +27,10 @@
 
     public function getComments(Request $request, $board_id, $task_id)
     {
+        $board_id = absint($board_id);
+        $task_id = absint($task_id);
         try {
+            Task::where('board_id', $board_id)->where('id', $task_id)->firstOrFail();
             $filter = $request->getSafe('filter', 'sanitize_text_field');
             $per_page =  10;
 
@@ -105,7 +108,7 @@
 
             $usersToSendEmail = [];
             if ($comment->type == 'reply') {
-                $parentComment = Comment::findOrFail($comment->parent_id);
+                $parentComment = Comment::where('board_id', (int) $board_id)->where('id', $comment->parent_id)->firstOrFail();
                 $commenterId = $parentComment->created_by;
                 if ($commenterId != get_current_user_id())
                 {
@@ -212,7 +215,10 @@
 
     public function deleteComment($board_id, $comment_id)
     {
+        $board_id = absint($board_id);
+        $comment_id = absint($comment_id);
         try {
+            Comment::where('board_id', $board_id)->where('id', $comment_id)->firstOrFail();
             $this->commentService->delete($comment_id);
 
             return $this->sendSuccess([
@@ -261,7 +267,10 @@
 
     public function deleteReply($board_id, $reply_id)
     {
+        $board_id = absint($board_id);
+        $reply_id = absint($reply_id);
         try {
+            Comment::where('board_id', $board_id)->where('id', $reply_id)->firstOrFail();
             $this->commentService->deleteReply($reply_id);
 
             return $this->sendSuccess([
@@ -330,7 +339,9 @@
 
     public function updateCommentPrivacy($board_id, $comment_id)
     {
-        $comment = Comment::findOrFail($comment_id);
+        $board_id = absint($board_id);
+        $comment_id = absint($comment_id);
+        $comment = Comment::where('board_id', $board_id)->where('id', $comment_id)->firstOrFail();
 
         // Check if user has permission to update the comment
         if ($comment->created_by != get_current_user_id()) {

Exploit Outline

The exploit targets the FluentBoards REST API endpoints for comment and task management. 1. **Authentication**: The attacker must be authenticated and have at least "Board Member" permissions on at least one legitimate board. 2. **Information Gathering**: The attacker identifies the ID of a task or comment they wish to target (the 'victim' object) that belongs to a board they do not have access to. 3. **Request Construction**: The attacker crafts a REST request to a vulnerable endpoint, such as `DELETE /wp-json/fluent-boards/v1/boards/{ATTACKER_BOARD_ID}/comments/{VICTIM_COMMENT_ID}`. 4. **Payload Execution**: The attacker includes their valid `X-WP-Nonce` and session cookies. By providing an ID for a board they *do* have access to in the URL path, they bypass the initial authorization check. 5. **Impact**: Because the controller logic previously used `findOrFail($comment_id)` or direct service calls without verifying that the `$comment_id` belonged to `$board_id`, the action (delete, update, or view) is performed on the victim's object despite the lack of permission.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.