Skip to content

Missing index-to-sketch validation in EventUnTagResource, EventTaggingResource, and EventAddAttributeResource #3768

@Alearner12

Description

@Alearner12

Three event endpoints in timesketch/api/v1/resources/event.py accept a user-supplied _index parameter and perform write operations on it without verifying
the index belongs to the sketch in the URL. This allows an authenticated user to modify events (tags, attributes) in sketches they do not have access to.

Affected endpoints:

  • EventUnTagResource.post() (line 1552) — removes tags from events
  • EventTaggingResource.post() (line 560) — adds tags to events
  • EventAddAttributeResource.post() (line 429) — adds attributes to events

EventAnnotationResource.post() (line 1001) in the same file correctly validates the index against the sketch's timelines, confirming this omission is
unintentional.

To Reproduce

Setup: user "dev" owns private Sketch 1, user "attacker" owns Sketch 2, attacker has no access to Sketch 1.

  1. Confirm attacker cannot access Sketch 1:
    GET /api/v1/sketches/1/ → 403 Forbidden

  2. Attacker strips tags from Sketch 1's events using their own Sketch 2:
    curl -b attacker.cookies -X POST /api/v1/sketches/2/event/untag/
    -H "Content-Type: application/json"
    -d '{"events": [{"_id": "ZuZ9yZwBRv_2AkVDRN1P",
    "_index": "4bb035e2f3ae4cbd87eeadb08d53cf5d"}],
    "tags_to_remove": ["important", "confidential"]}'
    Returns 200 OK. Victim's tags removed.

  3. Same pattern works for POST /api/v1/sketches/2/event/tagging/ (tag injection) and POST /api/v1/sketches/2/event/add_attribute/ (attribute injection).

Tested on Timesketch dev Docker, commit 39fcea1.

Expected behavior

The server should verify that the _index in the request body belongs to a timeline in the sketch specified in the URL. If it does not, the request should
be rejected with 403.

Suggested fix

Add index validation before processing events, matching the pattern already used in EventAnnotationResource:

allowed_indices = {t.searchindex.index_name for t in sketch.timelines
if t.get_status.status.lower() not in ("deleted", "archived")}
if searchindex.index_name not in allowed_indices:
abort(403, "Search index does not belong to this sketch.")

Additional context

This was reported to Google VRP and closed as not severe enough for security tracking. Google suggested public disclosure. The _index value is a stable
OpenSearch index name visible in every event API response, so a revoked collaborator who previously observed it can continue modifying events after access
revocation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions