1+ import random
12from typing import Type
23
34import requests
45
6+ from graphqler .utils import plugins_handler
7+ from graphqler .utils .stats import Stats
58from .field_suggestion_materializer import FieldSuggestionMaterializer
69from ..detector import Detector
710
811
912class FieldSuggestionsDetector (Detector ):
1013 """Field Suggestions Detector
14+
15+ Iterates over all known query names (shuffled) and sends each as a
16+ misspelled field name. Detection succeeds as soon as any response
17+ contains a "did you mean" hint, making the result independent of which
18+ individual query name happens to be blocked by a deny-list.
1119 """
20+
1221 @property
1322 def DETECTION_NAME (self ) -> str :
1423 return "Field Suggestions Enabled"
@@ -25,9 +34,63 @@ def detect_only_once_for_node(self) -> bool:
2534 def materializer (self ) -> Type [FieldSuggestionMaterializer ]:
2635 return FieldSuggestionMaterializer
2736
37+ # ── multi-query detect override ──────────────────────────────────────────
38+
39+ def detect (self ) -> tuple [bool , bool ]:
40+ query_names = list (self .api .queries .keys ())
41+ random .shuffle (query_names )
42+
43+ for query_name in query_names :
44+ misspelled = query_name + "abc"
45+ payload = f"query {{\n { misspelled } {{\n id\n }}\n }}"
46+
47+ graphql_response , request_response = (
48+ plugins_handler .get_request_utils ().send_graphql_request (
49+ self .api .url , payload
50+ )
51+ )
52+ Stats ().add_http_status_code (self .name , request_response .status_code )
53+
54+ if self ._is_vulnerable (graphql_response , request_response ):
55+ self .payload = payload
56+ self .confirmed_vulnerable = True
57+ self .potentially_vulnerable = True
58+ evidence = self ._get_evidence (graphql_response , request_response )
59+ Stats ().add_vulnerability (
60+ self .DETECTION_NAME ,
61+ self .name ,
62+ self .confirmed_vulnerable ,
63+ self .potentially_vulnerable ,
64+ payload = payload ,
65+ evidence = evidence ,
66+ )
67+ self .detector_logger .info (
68+ f"Detector { self .DETECTION_NAME } finished detecting - "
69+ f"is_vulnerable: True - potentially_vulnerable: True"
70+ )
71+ return (True , True )
72+
73+ self .detector_logger .info (
74+ f"Detector { self .DETECTION_NAME } finished detecting - "
75+ f"is_vulnerable: False - potentially_vulnerable: False"
76+ )
77+ Stats ().add_vulnerability (
78+ self .DETECTION_NAME ,
79+ self .name ,
80+ False ,
81+ False ,
82+ payload = "" ,
83+ evidence = "" ,
84+ )
85+ return (False , False )
86+
87+ # ── helpers ──────────────────────────────────────────────────────────────
88+
2889 def _is_vulnerable (self , graphql_response : dict , request_response : requests .Response ) -> bool :
29- return ("did you mean" in str (graphql_response ['errors' ][0 ]['message' ]).lower ()
30- or "did you mean" in request_response .text .lower ())
90+ errors = graphql_response .get ("errors" ) or []
91+ if errors and "did you mean" in str (errors [0 ].get ("message" , "" )).lower ():
92+ return True
93+ return "did you mean" in request_response .text .lower ()
3194
3295 def _is_potentially_vulnerable (self , graphql_response : dict , request_response : requests .Response ) -> bool :
3396 return self ._is_vulnerable (graphql_response , request_response )
0 commit comments