Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions source/app/blueprints/rest/dashboard_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import marshmallow
from sqlalchemy import or_
from datetime import datetime
from datetime import timedelta
from oic.oauth2.exception import GrantError
Expand Down Expand Up @@ -157,7 +158,7 @@ def get_gtasks():
@dashboard_rest_blueprint.route('/global/tasks/<int:cur_id>', methods=['GET'])
@ac_api_requires()
def view_gtask(cur_id):
task = get_global_task(task_id=cur_id)
task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).first()
if not task:
return response_error(f'Global task ID {cur_id} not found')

Expand Down Expand Up @@ -242,7 +243,7 @@ def add_gtask(caseid):
@ac_requires_case_identifier()
def edit_gtask(cur_id, caseid):
form = CaseGlobalTaskForm()
task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).first()
task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).first()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm `or_` is not imported in the file and that the new code references it.
rg -nP '\bor_\b' source/app/blueprints/rest/dashboard_routes.py
rg -nP '^\s*(from|import)\b.*\bor_\b' source/app/blueprints/rest/dashboard_routes.py

Repository: dfir-iris/iris-web

Length of output: 607


🏁 Script executed:

# Get the view_gtask function and surrounding context (lines 157-164)
sed -n '155,170p' source/app/blueprints/rest/dashboard_routes.py

# Get the gtask_delete function and surrounding context (lines 280-295)
sed -n '278,296p' source/app/blueprints/rest/dashboard_routes.py

# Also check imports at the top of the file to confirm no or_ import
sed -n '1,60p' source/app/blueprints/rest/dashboard_routes.py

Repository: dfir-iris/iris-web

Length of output: 3926


🏁 Script executed:

# Find and inspect the get_global_task function to see if it includes ownership filters
fd -n 'dashboard_db.py' source/

Repository: dfir-iris/iris-web

Length of output: 292


🏁 Script executed:

# Find dashboard_db.py
fd dashboard_db.py source/

# Then inspect the get_global_task function

Repository: dfir-iris/iris-web

Length of output: 107


🏁 Script executed:

# Search for get_global_task function definition and its implementation
rg -A 10 'def get_global_task' source/app/datamgmt/dashboard/dashboard_db.py

Repository: dfir-iris/iris-web

Length of output: 456


🏁 Script executed:

# Get more context of get_global_task function
rg -A 25 'def get_global_task' source/app/datamgmt/dashboard/dashboard_db.py

Repository: dfir-iris/iris-web

Length of output: 739


Critical: or_ is not imported — endpoints will raise NameError at runtime.

The new ownership filters at lines 245, 288, and 292 all reference or_, but sqlalchemy.or_ is not imported anywhere in this module. As soon as a user calls POST /global/tasks/update/<id> or POST /global/tasks/delete/<id>, Flask will raise NameError: name 'or_' is not defined and return a 500 — so the IDOR fix not only doesn't land, it also breaks legitimate edit/delete flows.

Additionally, the view_gtask endpoint (lines 157-164) is still vulnerable to IDOR. It calls get_global_task(task_id=cur_id), which only filters by task ID with no ownership check. Any user can retrieve details of any global task regardless of whether they created it or are assigned to it.

🛠️ Proposed fixes

Add missing import:

 import marshmallow
 from datetime import datetime
 from datetime import timedelta
 from oic.oauth2.exception import GrantError

+from sqlalchemy import or_
+
 from flask import Blueprint

Apply ownership filter in view_gtask:

 def view_gtask(cur_id):
-    task = get_global_task(task_id=cur_id)
+    task = GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).first()
     if not task:
         return response_error(f'Global task ID {cur_id} not found')

Minor refactor — avoid redundant query in delete:

     data = GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).first()
     if not data:
         return response_error("Invalid global task ID")

-    GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).delete()
+    db.session.delete(data)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/app/blueprints/rest/dashboard_routes.py` at line 245, Import
sqlalchemy.or_ into the module and apply ownership filtering wherever tasks are
fetched: update the imports to include or_ and change queries that fetch
GlobalTasks (e.g., the one in GlobalTasks.query.filter(...) used in the update
and delete flows) to use or_(GlobalTasks.task_userid_open == current_user.id,
GlobalTasks.task_assignee_id == current_user.id). Also fix the
view_gtask/get_global_task flow by either adding the same ownership filters
inside get_global_task or by applying the ownership filter in view_gtask before
returning the task (ensure get_global_task(task_id=cur_id) cannot return tasks
the current_user does not own), and simplify delete to use that filtered query
instead of re-querying redundantly.

form.task_assignee_id.choices = [(user.id, user.name) for user in User.query.filter(
User.active == True).order_by(User.name).all()]
form.task_status_id.choices = [(a.id, a.status_name)
Expand Down Expand Up @@ -285,11 +286,11 @@ def gtask_delete(cur_id, caseid):
if not cur_id:
return response_error("Missing parameter")

data = GlobalTasks.query.filter(GlobalTasks.id == cur_id).first()
data = GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).first()
if not data:
return response_error("Invalid global task ID")

GlobalTasks.query.filter(GlobalTasks.id == cur_id).delete()
GlobalTasks.query.filter(GlobalTasks.id == cur_id).filter(or_(GlobalTasks.task_userid_open == current_user.id, GlobalTasks.task_assignee_id == current_user.id)).delete()
db.session.commit()

call_modules_hook('on_postload_global_task_delete',
Expand Down