From f4106ef26d614e09adcd8de813748235b195cd06 Mon Sep 17 00:00:00 2001 From: Omar2535 Date: Mon, 11 Nov 2024 14:13:33 -0500 Subject: [PATCH] Add detectors to endpoint outputs --- graphqler/fuzzer/engine/dengine.py | 40 +++++++++---------- graphqler/fuzzer/engine/detectors/detector.py | 16 +++++++- .../query_deny_bypass_detector.py | 15 +++++++ graphqler/fuzzer/fuzzer.py | 2 +- graphqler/utils/stats.py | 2 + 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/graphqler/fuzzer/engine/dengine.py b/graphqler/fuzzer/engine/dengine.py index 3712347..4ae9374 100644 --- a/graphqler/fuzzer/engine/dengine.py +++ b/graphqler/fuzzer/engine/dengine.py @@ -1,12 +1,11 @@ -from .detectors import injection_detectors -from .detectors import api_detectors -from .detectors import misc_detectors -from .detectors.detector import Detector +import graphqler.config as config +from graphqler.graph.node import Node from graphqler.utils.api import API from graphqler.utils.logging_utils import Logger from graphqler.utils.objects_bucket import ObjectsBucket -import graphqler.config as config +from .detectors import api_detectors, injection_detectors, misc_detectors +from .detectors.detector import Detector class DEngine: @@ -29,7 +28,8 @@ def run_detections_on_api(self): - Uses API as the key for nodes_ran marking """ for api_detector in api_detectors: - detector = api_detector(api=self.api, name=self.api.url, objects_bucket=ObjectsBucket(self.api), graphql_type="") + node = Node(graphql_type='misc', name=self.api.url, body={}) # Create a dummy node object for the API + detector = api_detector(api=self.api, node=node, objects_bucket=ObjectsBucket(self.api), graphql_type="") if not self.__should_run_detection(detector, self.api.url): continue try: @@ -39,55 +39,55 @@ def run_detections_on_api(self): except Exception as e: self.logger.error(f"Detector {detector.DETECTION_NAME} failed with error: {e}") - def run_detections_on_graphql_object(self, name: str, objects_bucket: ObjectsBucket, graphql_type: str): + def run_detections_on_graphql_object(self, node: Node, objects_bucket: ObjectsBucket, graphql_type: str): """Runs all detectors on a specific GraphQL object (either QUERY or MUTATION) Args: - name (str): Name of the query or mutation + node (Node): The node object objects_bucket (ObjectsBucket): The objects bucket graphql_type (str): The GraphQL type """ if not config.SKIP_INJECTION_ATTACKS: - self.__run_injection_detections(name, objects_bucket, graphql_type) + self.__run_injection_detections(node, objects_bucket, graphql_type) if not config.SKIP_MISC_ATTACKS: - self.__run_misc_detections(name, objects_bucket, graphql_type) + self.__run_misc_detections(node, objects_bucket, graphql_type) - def __run_misc_detections(self, name: str, objects_bucket: ObjectsBucket, graphql_type: str): + def __run_misc_detections(self, node: Node, objects_bucket: ObjectsBucket, graphql_type: str): """Runs miscellaneous detections Args: - name (str): The name of the node + node (Node): The node object objects_bucket (ObjectsBucket): The objects bucket graphql_type (str): The type of the GraphQL operation """ for misc_detector in misc_detectors: - detector = misc_detector(api=self.api, name=name, objects_bucket=objects_bucket, graphql_type=graphql_type) - if not self.__should_run_detection(detector, name): + detector = misc_detector(api=self.api, node=node, objects_bucket=objects_bucket, graphql_type=graphql_type) + if not self.__should_run_detection(detector, node.name): continue try: is_vulnerable, potentially_vulnerable = detector.detect() self.logger.info(f"Detector {detector.DETECTION_NAME} finished detecting - is_vulnerable: {is_vulnerable} - potentially_vulnerable: {potentially_vulnerable}") - self.__add_ran_node(name, detector.DETECTION_NAME) + self.__add_ran_node(node.name, detector.DETECTION_NAME) except Exception as e: self.logger.error(f"Detector {detector.DETECTION_NAME} failed with error: {e}") - def __run_injection_detections(self, name: str, objects_bucket: ObjectsBucket, graphql_type: str): + def __run_injection_detections(self, node: Node, objects_bucket: ObjectsBucket, graphql_type: str): """Runs injection detections Args: - name (str): The name of the node + node (Node): The node object objects_bucket (ObjectsBucket): The objects bucket graphql_type (str): The type of the GraphQL operation """ for injection_detector in injection_detectors: - detector = injection_detector(api=self.api, name=name, objects_bucket=objects_bucket, graphql_type=graphql_type) - if not self.__should_run_detection(detector, name): + detector = injection_detector(api=self.api, node=node, objects_bucket=objects_bucket, graphql_type=graphql_type) + if not self.__should_run_detection(detector, node.name): continue try: is_vulnerable, potentially_vulnerable = detector.detect() self.logger.info(f"Detector {detector.DETECTION_NAME} finished detecting - is_vulnerable: {is_vulnerable} - potentially_vulnerable: {potentially_vulnerable}") - self.__add_ran_node(name, detector.DETECTION_NAME) + self.__add_ran_node(node.name, detector.DETECTION_NAME) except Exception as e: self.logger.error(f"Detector {detector.DETECTION_NAME} failed with error: {e}") diff --git a/graphqler/fuzzer/engine/detectors/detector.py b/graphqler/fuzzer/engine/detectors/detector.py index 90cd122..0405ee3 100644 --- a/graphqler/fuzzer/engine/detectors/detector.py +++ b/graphqler/fuzzer/engine/detectors/detector.py @@ -3,11 +3,13 @@ import requests +from graphqler.graph.node import Node from graphqler.utils.api import API from graphqler.utils.logging_utils import Logger from graphqler.utils.objects_bucket import ObjectsBucket from graphqler.utils.stats import Stats from graphqler.utils import plugins_handler +from graphqler.fuzzer.engine.types import ResultEnum, Result from ..materializers.materializer import Materializer @@ -43,9 +45,10 @@ def materializer(self) -> Type[Materializer]: """Materializer class to be used for payload generation""" pass - def __init__(self, api: API, name: str, objects_bucket: ObjectsBucket, graphql_type: str): + def __init__(self, api: API, node: Node, objects_bucket: ObjectsBucket, graphql_type: str): self.api = api - self.name = name + self.node = node + self.name = node.name self.objects_bucket = objects_bucket self.graphql_type = graphql_type self.detector_logger = Logger().get_detector_logger() @@ -64,8 +67,17 @@ def detect(self) -> tuple[bool, bool]: self.fuzzer_logger.debug(f"[Fuzzer] Payload:\n{self.payload}") self.detector_logger.info(f"[Detector] Payload:\n{self.payload}") + # Send the GraphQL request graphql_response, request_response = plugins_handler.get_request_utils().send_graphql_request(self.api.url, self.payload) + result = Result( + result_enum=ResultEnum.GENERAL_SUCCESS, + payload_string=self.payload, + status_code=request_response.status_code, + graphql_response=graphql_response, + raw_response_text=request_response.text + ) Stats().add_http_status_code(self.name, request_response.status_code) + Stats().update_stats_from_result(self.node, result) self.detector_logger.info(f"[{request_response.status_code}]Response: {request_response.text}") self.fuzzer_logger.info(f"[{request_response.status_code}]Response: {graphql_response}") diff --git a/graphqler/fuzzer/engine/detectors/query_deny_bypass/query_deny_bypass_detector.py b/graphqler/fuzzer/engine/detectors/query_deny_bypass/query_deny_bypass_detector.py index b690b7d..140df82 100644 --- a/graphqler/fuzzer/engine/detectors/query_deny_bypass/query_deny_bypass_detector.py +++ b/graphqler/fuzzer/engine/detectors/query_deny_bypass/query_deny_bypass_detector.py @@ -2,6 +2,7 @@ import requests +from graphqler.fuzzer.engine.types import Result, ResultEnum from graphqler.utils.api import API from graphqler.fuzzer.engine.materializers.getter import Getter from graphqler.fuzzer.engine.detectors.detector import Detector @@ -122,8 +123,22 @@ def detect(self) -> tuple[bool, bool]: else: self.potentially_vulnerable = False self.confirmed_vulnerable = False + + non_aliased_result = Result(ResultEnum.GENERAL_SUCCESS, + payload_string=non_aliased_payload, + status_code=non_aliased_request_response.status_code, + graphql_response=non_aliased_graphql_response, + raw_response_text=non_aliased_request_response.text) + aliased_result = Result(ResultEnum.GENERAL_SUCCESS, + payload_string=aliased_payload, + status_code=aliased_request_response.status_code, + graphql_response=aliased_graphql_response, + raw_response_text=aliased_request_response.text) + Stats().add_http_status_code(self.name, non_aliased_request_response.status_code) Stats().add_http_status_code(self.name, aliased_request_response.status_code) + Stats().update_stats_from_result(self.node, non_aliased_result) + Stats().update_stats_from_result(self.node, aliased_result) Stats().add_vulnerability(self.DETECTION_NAME, self.name, self.confirmed_vulnerable, self.potentially_vulnerable) return (self.confirmed_vulnerable, self.potentially_vulnerable) diff --git a/graphqler/fuzzer/fuzzer.py b/graphqler/fuzzer/fuzzer.py index 73ad13f..dab51c7 100644 --- a/graphqler/fuzzer/fuzzer.py +++ b/graphqler/fuzzer/fuzzer.py @@ -319,7 +319,7 @@ def __fuzz(self, node: Node, visit_path: list[Node]): def __detect_vulnerabilities_on_node(self, node: Node): if node.graphql_type in ["Query", "Mutation"]: - self.dengine.run_detections_on_graphql_object(node.name, self.objects_bucket, node.graphql_type) + self.dengine.run_detections_on_graphql_object(node, self.objects_bucket, node.graphql_type) def _get_new_visit_path_with_neighbors(self, neighboring_nodes: list[Node], visit_path: list[Node]) -> list[list[Node]]: """Gets the new visit path with the neighbors by creating a new path for each neighboring node diff --git a/graphqler/utils/stats.py b/graphqler/utils/stats.py index 2724724..2ed315b 100644 --- a/graphqler/utils/stats.py +++ b/graphqler/utils/stats.py @@ -244,6 +244,8 @@ def save_endpoint_results(self): unique_results = {} # Filter out for only unique results for node_name, results in self.results.items(): + # If the node name has slashes, replace them with underscores + node_name = node_name.replace("/", "_") for result in results: result_type = "success" if result.success else "failure" result_file_path = Path(self.endpoint_results_dir) / node_name / result_type / f"{result.status_code}"