HackerOne · Report #781175
Unauthenticated users can obtain information about Checklist objects with unclaimed ChecklistCheck objects
How it was found
The Checklist objects that can be queried through GraphQL are supposed to only be accessible by program members, the users who claimed or responded to a check belonging to a checklist, and HackerOne Pentesters. The Checklist object is also supposed to be in the running state (e.g. when the platform collects responses for the checks) for HackerOne Pentesters to access them. The authorization check is implemented as follows:
class ProtectedChecklist
include ::ProtectedAttribute::Protector
model { Checklist }
copy_roles(ProtectedTeam) do
Checklist.joins(:team)
end
property(:RUNNING_CHECKLIST) do
Checklist.running
end
role(:CLAIMER) { |requester| claimed_by_user(requester) }
role(:RESPONDER) { |requester| responded_by_user(requester) }
group(
has_feature(RUNNING_CHECKLIST) & (
has_role(PUBLIC) |
has_role(H1_PENTESTER) |
has_role(WHITELISTED_REPORTER) |
has_role(INVITATION_RECIPIENT_WITH_SATISFIED_REQUIREMENTS)
) |
has_role(TEAM_MEMBER) |
has_role(CLAIMER) |
has_role(RESPONDER),
) do
allow :checklist_check_responses
allow :checklist_checks
allow :expires_at
allow :id
allow :name
allow :team
allow :unclaimed_checklist_checks_count
end
end
At first sight, the authorization check seems to be implemented correctly. However, the CLAIMER role is leveraging the claimed_by_user scope, which is implemented as follows:
scope :claimed_by_user, lambda { |user|
where(id: ChecklistCheck.where(user_id: user).select(:checklist_id))
}
Instead of an inner join, a query (ChecklistCheck.where(user_id: user).select(:checklist_id)) is used to fetch the checklist IDs that are claimed. Because not all checks are claimed, user_id can be set to NULL in the database. Because the HackerOne GraphQL endpoint can be queried as an anonymous user, this scope can be called with nil. This causes anonymous users to automatically get the CLAIMER role, thus exposing information about Checklist objects.
The other protectors correctly implement the claimed_by_user scope.
Any relations defined on the Checklist model, such as team, checklist_check_responses, and checklist_checks are protected separately and are not accessible by anonymous users. The exposed information is limited to Checklist objects, which doesn't expose any customer information.
The following query can be used to query a Checklist object. Make sure you're signed out when executing this query.
query {
node(id: "Z2lkOi8vaGFja2Vyb25lL0NoZWNrbGlzdC8x") {
... on Checklist {
name
expires_at
}
}
}
Impact
Anonymous users can obtain information about checklists.