From 1948851efaae9b762640408252fc3eca42bd31c8 Mon Sep 17 00:00:00 2001 From: dolevf Date: Mon, 20 Jun 2022 14:52:32 -0400 Subject: [PATCH] remove circular-fragment, add post-based csrf --- graphql-cop.py | 11 ++++---- lib/tests/dos_circular_fragment.py | 41 ---------------------------- lib/tests/info_get_based_mutation.py | 6 ++-- lib/tests/info_get_method_support.py | 8 +++--- lib/tests/info_graphiql.py | 4 +-- lib/tests/info_post_based_csrf.py | 26 ++++++++++++++++++ lib/utils.py | 9 ++++-- version.py | 2 +- 8 files changed, 47 insertions(+), 60 deletions(-) delete mode 100644 lib/tests/dos_circular_fragment.py create mode 100644 lib/tests/info_post_based_csrf.py diff --git a/graphql-cop.py b/graphql-cop.py index 019c7cc..708b78b 100644 --- a/graphql-cop.py +++ b/graphql-cop.py @@ -17,8 +17,8 @@ from lib.tests.dos_directive_overloading import directive_overloading from lib.tests.info_trace_mode import trace_mode from lib.tests.dos_circular_introspection import circular_query_introspection -from lib.tests.dos_circular_fragment import circular_fragment from lib.tests.info_get_based_mutation import get_based_mutation +from lib.tests.info_post_based_csrf import post_based_csrf from lib.utils import is_graphql, draw_art @@ -31,6 +31,7 @@ help='Sends the request through http://127.0.0.1:8080 proxy') parser.add_option('--version', '-v', dest='version', action='store_true', default=False, help='Print out the current version and exit.') + options, args = parser.parse_args() if options.version: @@ -67,21 +68,19 @@ print(url, 'does not seem to be running GraphQL.') sys.exit(1) -tests = [field_suggestions, introspection, detect_graphiql, +tests = [field_suggestions, introspection, detect_graphiql, get_method_support, alias_overloading, batch_query, field_duplication, trace_mode, directive_overloading, - circular_query_introspection, circular_fragment, - get_based_mutation] + circular_query_introspection, get_based_mutation, post_based_csrf] json_output = [] for test in tests: json_output.append(test(url, proxy, HEADERS)) - + if options.format == 'json': print(json_output) else: for i in json_output: if i['result']: print('[{}] {} - {} ({})'.format(i['severity'], i['title'], i['description'], i['impact'])) - \ No newline at end of file diff --git a/lib/tests/dos_circular_fragment.py b/lib/tests/dos_circular_fragment.py deleted file mode 100644 index 0d511ef..0000000 --- a/lib/tests/dos_circular_fragment.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Circular Fragment tests.""" -from lib.utils import graph_query, curlify - - -def circular_fragment(url, proxy, headers): - """Check for circular fragment.""" - res = { - 'result':False, - 'title':'Circular Fragment', - 'description':'Circular Fragment allowed in Query', - 'impact':'Denial of Service', - 'severity':'HIGH', - 'curl_verify':'' - } - - q = ''' - query { - __schema { - ...A - } - } - - fragment A on __Schema { - __typename - ...B - } - - fragment B on __Schema { - ...A - } -''' - gql_response = graph_query(url, proxies=proxy, headers=headers, payload=q) - res['curl_verify'] = curlify(gql_response) - - try: - if not 'errors' in gql_response.json(): - res['result'] = True - except: - pass - - return res diff --git a/lib/tests/info_get_based_mutation.py b/lib/tests/info_get_based_mutation.py index 5892734..24e2561 100644 --- a/lib/tests/info_get_based_mutation.py +++ b/lib/tests/info_get_based_mutation.py @@ -1,5 +1,5 @@ """Checks mutation support over on GET.""" -from lib.utils import request_get, curlify +from lib.utils import request, curlify def get_based_mutation(url, proxies, headers): @@ -14,9 +14,9 @@ def get_based_mutation(url, proxies, headers): q = 'mutation {__typename}' - response = request_get(url, proxies=proxies, headers=headers, params={'query':q}) + response = request(url, proxies=proxies, headers=headers, params={'query':q}) res['curl_verify'] = curlify(response) - + try: if response and response.json()['data']['__typename']: res['result'] = True diff --git a/lib/tests/info_get_method_support.py b/lib/tests/info_get_method_support.py index 300ad1c..330cc50 100644 --- a/lib/tests/info_get_method_support.py +++ b/lib/tests/info_get_method_support.py @@ -1,5 +1,5 @@ """Collect all supported methods.""" -from lib.utils import request_get, curlify +from lib.utils import request, curlify def get_method_support(url, proxies, headers): @@ -9,15 +9,15 @@ def get_method_support(url, proxies, headers): 'title':'GET Method Query Support', 'description':'GraphQL queries allowed using the GET method', 'impact':'Possible Cross Site Request Forgery (CSRF)', - 'severity':'LOW', + 'severity':'MEDIUM', 'curl_verify':'' } q = '{__typename}' - response = request_get(url, proxies=proxies, headers=headers, params={'query':q}) + response = request(url, proxies=proxies, headers=headers, params={'query':q}) res['curl_verify'] = curlify(response) - + try: if response and response.json()['data']['__typename']: res['result'] = True diff --git a/lib/tests/info_graphiql.py b/lib/tests/info_graphiql.py index 29fd2e7..0c4bac0 100644 --- a/lib/tests/info_graphiql.py +++ b/lib/tests/info_graphiql.py @@ -1,6 +1,6 @@ """Collect GraphiQL details.""" from urllib.parse import urlparse -from lib.utils import request_get, curlify +from lib.utils import request, curlify def detect_graphiql(url, proxy, headers): @@ -21,7 +21,7 @@ def detect_graphiql(url, proxy, headers): url = '{}://{}'.format(parsed.scheme, parsed.netloc) for endpoint in endpoints: - response = request_get(url + endpoint, proxies=proxy, headers=headers) + response = request(url + endpoint, proxies=proxy, headers=headers) res['curl_verify'] = curlify(response) try: if response and any(word in response.text for word in heuristics): diff --git a/lib/tests/info_post_based_csrf.py b/lib/tests/info_post_based_csrf.py new file mode 100644 index 0000000..9828415 --- /dev/null +++ b/lib/tests/info_post_based_csrf.py @@ -0,0 +1,26 @@ +"""Checks if queries are allowed over POST not in JSON.""" +from lib.utils import request, curlify + + +def post_based_csrf(url, proxies, headers): + res = { + 'result':False, + 'title':'POST based url-encoded query (possible CSRF)', + 'description':'GraphQL accepts non-JSON queries over POST', + 'impact':'Possible Cross Site Request Forgery', + 'severity':'MEDIUM', + 'curl_verify':'' + } + + q = 'query {__typename}' + + response = request(url, proxies=proxies, headers=headers, params={'query':q}, verb='POST') + res['curl_verify'] = curlify(response) + + try: + if response and response.json()['data']['__typename']: + res['result'] = True + except: + pass + + return res diff --git a/lib/utils.py b/lib/utils.py index 5f21a17..8fa2f69 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -51,22 +51,25 @@ def graph_query(url, proxies, headers, operation='query', payload={}, batch=Fals return {} -def request_get(url, proxies, headers, params=None, data=None): +def request(url, proxies, headers, params=None, data=None, verb='GET'): """Perform requests.""" try: - response = requests.get(url, + response = requests.request(verb, + url=url, params=params, headers=headers, cookies=None, verify=False, allow_redirects=True, proxies=proxies, - timeout=5, + timeout=5, data=data) return response except: return None + + def is_graphql(url, proxies, headers): """Check if the URL provides a GraphQL interface.""" query = ''' diff --git a/version.py b/version.py index ba75513..84f511c 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ """Version details of graphql-cop.""" -VERSION = '1.5' +VERSION = '1.6'