$BLOG_TITLE
GRAPHQL, SECURITY

GraphQL path enumeration for better permission testing

Let’s imagine the following GraphQL schema

GraphQL Schema

In an ideal scenario (from the developer’s perspective) the permission checks would be done on the object level, which means that no matter the path you take to reach Foo, it’s Foo that’s responsible for checking if you’re allowed to load it and not the Root or Bar objects as they load Foo. This is how GitLab does it and from what I can tell that’s also how HackerOne does it.

Some other websites however will program their authorization logic in the code that fetches the object, this means the authorization logic might have flaws in one path but not in another one. Applied to the schema above, Root -> Foo might be checked properly but Root -> Bar -> Foo might not check for permissions at all. It’s fairly easy to figure out all the paths in my FooBar schema, however some schemas are very complicated and tooling would help to figure out all the possible paths.

This is where my new tool with a very original name comes in: graphql-path-enum. Given that most graphs have loops and have an infinite amount of paths, the tool doesn’t list them all, but it does a relatively exhaustive listing nonetheless.

$ graphql-path-enum -i ./test_data/h1_introspection.json -t Skill
Found 27 ways to reach the "Skill" node from the "Query" node:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check_response) -> ChecklistCheckResponse (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_checks) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (clusters) -> Cluster (weaknesses) -> Weakness (critical_reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (embedded_submission_form) -> EmbeddedSubmissionForm (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_program) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_programs) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listing) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listings) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (me) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentest) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentests) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (skills) -> Skill
- Query (report) -> Report (bounties) -> Bounty (invitations) -> InvitationsClaimBounty (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (report_retest_user) -> ReportRetestUser (invitation) -> InvitationsRetest (report) -> Report (bounties) -> Bounty (invitations) -> InvitationsClaimBounty (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (skills) -> Skill
- Query (sla_statuses) -> SlaStatus (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (triage_inbox_items) -> TriageInboxItem (report) -> Report (bounties) -> Bounty (invitations) -> InvitationsClaimBounty (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (users) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (weaknesses) -> Weakness (critical_reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (webhook) -> Webhook (created_by) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill

$ graphql-path-enum --help
graphql-path-enum 1.0
dee-see (https://gitlab.com/dee-see/graphql-path-enum)
Use this tool to list the different paths that lead to one object in a GraphQL schema.

USAGE:
    graphql-path-enum [FLAGS] --introspect-query-path <FILE_PATH> --type <TYPE_NAME>

FLAGS:
        --expand-connections    Expand connection nodes (with pageInfo, edges, etc. edges), they are skipped by default
    -h, --help                  Prints help information
    -V, --version               Prints version information

OPTIONS:
    -i, --introspect-query-path <FILE_PATH>    Path to the introspection query result saved as JSON
    -t, --type <TYPE_NAME>                     The type to look for in the graph.

It’s open source and can be found here. Let me know if there are issues or cool features that could be added. It’s my first time writing in Rust so it might not be perfect, code reviews accepted and appreciated!

Happy hacking!