Coverage for graphqler / fuzzer / engine / detectors / detector.py: 91%
69 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-20 10:09 -0400
1from abc import ABC, abstractmethod
2from typing import Type
4import requests
6from graphqler.graph.node import Node
7from graphqler.utils.api import API
8from graphqler.utils.logging_utils import Logger
9from graphqler.utils.objects_bucket import ObjectsBucket
10from graphqler.utils.stats import Stats
11from graphqler.utils import plugins_handler
12from graphqler.fuzzer.engine.types import ResultEnum, Result
14from ..materializers.materializer import Materializer
17class Detector(ABC):
18 """Base Detector class that implements common functionality for all detectors.
20 Subclasses must implement:
21 - DETECTION_NAME (class attribute)
22 - materializer (property)
23 - _is_vulnerable (method)
24 """
26 @property
27 @abstractmethod
28 def DETECTION_NAME(self) -> str:
29 """Name of the detection type"""
30 pass
32 @property
33 def detect_only_once_for_api(self) -> bool:
34 """Whether the detector should be run only once on the API"""
35 return False
37 @property
38 def detect_only_once_for_node(self) -> bool:
39 """Whether the detector should be run only once on the node"""
40 return True
42 @property
43 @abstractmethod
44 def materializer(self) -> Type[Materializer]:
45 """Materializer class to be used for payload generation"""
46 pass
48 def __init__(self, api: API, node: Node, objects_bucket: ObjectsBucket, graphql_type: str):
49 self.api = api
50 self.node = node
51 self.name = node.name
52 self.objects_bucket = objects_bucket
53 self.graphql_type = graphql_type
54 self.detector_logger = Logger().get_detector_logger()
55 self.fuzzer_logger = Logger().get_fuzzer_logger()
56 self.payload = ""
57 self.confirmed_vulnerable = False
58 self.potentially_vulnerable = False
60 def detect(self) -> tuple[bool, bool]:
61 """Main function to run to detect the vulnerability.
63 Returns:
64 tuple[bool, bool]: (confirmed_vulnerable, potentially_vulnerable)
65 """
66 self.payload = self.get_payload()
67 self.fuzzer_logger.debug(f"[Fuzzer] Payload:\n{self.payload}")
68 self.detector_logger.info(f"[Detector] Payload:\n{self.payload}")
70 # Send the GraphQL request
71 graphql_response, request_response = plugins_handler.get_request_utils().send_graphql_request(self.api.url, self.payload)
72 result = Result(
73 result_enum=ResultEnum.GENERAL_SUCCESS,
74 payload=self.payload,
75 status_code=request_response.status_code,
76 graphql_response=graphql_response,
77 raw_response_text=request_response.text
78 )
79 Stats().add_http_status_code(self.name, request_response.status_code)
80 Stats().update_stats_from_result(self.node, result)
82 self.detector_logger.info(f"[{request_response.status_code}]Response: {request_response.text}")
83 self.fuzzer_logger.info(f"[{request_response.status_code}]Response: {graphql_response}")
85 self._parse_response(graphql_response, request_response)
86 Stats().add_vulnerability(self.DETECTION_NAME, self.name, self.confirmed_vulnerable, self.potentially_vulnerable)
87 return (self.confirmed_vulnerable, self.potentially_vulnerable)
89 def get_payload(self) -> str:
90 """Gets the materialized payload to be sent to the API"""
91 materializer_instance = self.materializer(
92 api=self.api,
93 fail_on_hard_dependency_not_met=False,
94 max_depth=3
95 )
96 payload, used_objects = materializer_instance.get_payload(self.name, self.objects_bucket, self.graphql_type)
97 return payload
99 def _parse_response(self, graphql_response: dict, request_response: requests.Response):
100 """Parses the response and checks for vulnerability"""
101 if "errors" in graphql_response:
102 self.detector_logger.info(f"Got errors: {graphql_response['errors']}")
103 # self.potentially_vulnerable = True
104 if self._is_vulnerable(graphql_response, request_response):
105 self.detector_logger.info(f"Vulnerable to {self.DETECTION_NAME}")
106 self.confirmed_vulnerable = True
107 if self._is_potentially_vulnerable(graphql_response, request_response):
108 self.detector_logger.info(f"Potentially vulnerable to {self.DETECTION_NAME}")
109 self.potentially_vulnerable = True
111 @abstractmethod
112 def _is_vulnerable(self, graphql_response: dict, request_response: requests.Response) -> bool:
113 """Checks if the response indicates a vulnerability.
115 Must be implemented by subclasses.
116 """
117 pass
119 @abstractmethod
120 def _is_potentially_vulnerable(self, graphql_response: dict, request_response: requests.Response) -> bool:
121 """Checks if the response indicates a potential vulnerability.
123 Must be implemented by subclasses.
124 """
125 pass