def probe_typename(input_document: str, config: graphql.Config) -> str: typename = "" wrong_field = "imwrongfield" document = input_document.replace("FUZZ", wrong_field) response = graphql.post(config.url, headers=config.headers, json={"query": document}) errors = response.json()["errors"] wrong_field_regexes = [ f'Cannot query field "{wrong_field}" on type "(?P<typename>[_0-9a-zA-Z\[\]!]*)".', f'Field "[_0-9a-zA-Z\[\]!]*" must not have a selection since type "(?P<typename>[_A-Za-z\[\]!][_0-9a-zA-Z\[\]!]*)" has no subfields.', ] match = None for regex in wrong_field_regexes: for error in errors: match = re.fullmatch(regex, error["message"]) if match: break if match: break if not match: raise Exception( f"Expected '{errors}' to match any of '{wrong_field_regexes}'.") typename = (match.group("typename").replace("[", "").replace("]", "").replace( "!", "")) return typename
def probe_input_fields(field: str, argument: str, wordlist: Set, config: graphql.Config) -> Set[str]: valid_input_fields = set(wordlist) document = f"mutation {{ {field}({argument}: {{ {', '.join([w + ': 7' for w in wordlist])} }}) }}" response = graphql.post(config.url, headers=config.headers, json={"query": document}) errors = response.json()["errors"] for error in errors: error_message = error["message"] # First remove field if it produced an error match = re.search( 'Field "(?P<invalid_field>[_0-9a-zA-Z\[\]!]*)" is not defined by type [_0-9a-zA-Z\[\]!]*.', error_message, ) if match: valid_input_fields.discard(match.group("invalid_field")) # Second obtain field suggestions from error message valid_input_fields |= get_valid_input_fields(error_message) return valid_input_fields
def probe_valid_args(field: str, wordlist: Set, config: graphql.Config, input_document: str) -> Set[str]: valid_args = set(wordlist) document = input_document.replace( "FUZZ", f"{field}({', '.join([w + ': 7' for w in wordlist])})") response = graphql.post(config.url, headers=config.headers, json={"query": document}) errors = response.json()["errors"] for error in errors: error_message = error["message"] if ("must not have a selection since type" in error_message and "has no subfields" in error_message): return set() # First remove arg if it produced an "Unknown argument" error match = re.search( 'Unknown argument "(?P<invalid_arg>[_A-Za-z][_0-9A-Za-z]*)" on field "[_A-Za-z][_0-9A-Za-z.]*"', error_message, ) if match: valid_args.discard(match.group("invalid_arg")) # Second obtain args suggestions from error message valid_args |= get_valid_args(error_message) return valid_args
def fetch_root_typenames(config: graphql.Config) -> Dict[str, Optional[str]]: documents = { "queryType": "query { __typename }", "mutationType": "mutation { __typename }", "subscriptionType": "subscription { __typename }", } typenames = { "queryType": None, "mutationType": None, "subscriptionType": None, } for name, document in documents.items(): response = graphql.post( config.url, headers=config.headers, json={"query": document}, verify=config.verify, ) data = response.json().get("data", {}) if data: typenames[name] = data["__typename"] logging.debug(f"Root typenames are: {typenames}") return typenames
def probe_typeref(documents: List[str], context: str, config: graphql.Config) -> Optional[graphql.TypeRef]: typeref = None for document in documents: response = graphql.post( config.url, headers=config.headers, json={"query": document}, verify=config.verify, ) errors = response.json().get("errors", []) for error in errors: typeref = get_typeref(error["message"], context) logging.debug( f"get_typeref('{error['message']}', '{context}') -> {typeref}") if typeref: return typeref if not typeref and context != 'InputValue': raise Exception( f"Unable to get TypeRef for {documents} in context {context}") return None
def probe_typeref(documents: List[str], context: str, config: graphql.Config) -> Optional[graphql.TypeRef]: typeref = None for document in documents: response = graphql.post(config.url, headers=config.headers, json={"query": document}) errors = response.json().get("errors", []) for error in errors: typeref = get_typeref(error["message"], context) if typeref: return typeref if not typeref: raise Exception(f"Unable to get TypeRef for {documents}") return None
def probe_valid_fields(wordlist: Set, config: graphql.Config, input_document: str) -> Set[str]: # We're assuming all fields from wordlist are valid, # then remove fields that produce an error message valid_fields = set(wordlist) for i in range(0, len(wordlist), config.bucket_size): bucket = wordlist[i:i + config.bucket_size] document = input_document.replace("FUZZ", " ".join(bucket)) response = graphql.post( config.url, headers=config.headers, json={"query": document}, verify=config.verify, ) errors = response.json()["errors"] logging.debug( f"Sent {len(bucket)} fields, recieved {len(errors)} errors in {response.elapsed.total_seconds()} seconds" ) for error in errors: error_message = error["message"] if ("must not have a selection since type" in error_message and "has no subfields" in error_message): return set() # First remove field if it produced an "Cannot query field" error match = re.search( 'Cannot query field [\'"](?P<invalid_field>[_A-Za-z][_0-9A-Za-z]*)[\'"]', error_message, ) if match: valid_fields.discard(match.group("invalid_field")) # Second obtain field suggestions from error message valid_fields |= get_valid_fields(error_message) return valid_fields
def test_retries_on_500(self): response = graphql.post("http://localhost:8000") self.assertEqual(response.status_code, 200)