This document has references and notes from the OWASP Global AppSec 2024 talk; "GraphQL Exploitation: Secondary Context Attacks and Business Logic Vulnerabilities".
- Github: BuffaloWill
- LinkedIn: Will Vandevanter
- Hacking Starbucks and Accessing Nearly 100 Million Customer Records - Sam Curry 06/20/2020
- Leaked Secrets and Unlimited Miles: Hacking the Largest Airline and Hotel Rewards Platform - Sam Curry 08/03/2023
- KernelCon 2020: Attacking Secondary Contexts in Web Applications - Sam Curry
- GraphQL Scalars - https://the-guild.dev/graphql/scalars/docs
- Escape Tech - How To Secure GraphQL APIs
GraphQL Operation Name in URL
return requestResponse.request().parameterValue("operationName", HttpParameterType.URL);
Show Content Type in Table Column:
return requestResponse.request().headerValue("Content-Type");
Highlight a request if it has ID! in it. This gets kind of janky trying to pull the mutation or query name:
// Get the request body
String body = requestResponse.request().bodyToString();
if (body.contains("ID!") || body.contains("String!")) {
requestResponse.annotations().setHighlightColor(HighlightColor.RED);
}
// Check for POST requests (most common for GraphQL)
if (requestResponse.request().method().equals("POST")) {
try {
// Pattern to match mutations, including inline definitions and input parameters
Pattern mutationPattern = Pattern.compile("\"mutation\"\\s*:\\s*\"\\s*(mutation\\s+(\\w+)(\\([^)]*\\))?|mutation)\\s*\\{|\"mutation\"\\s*:\\s*\"mutation\\s+(\\w+)(\\([^)]*\\))?");
Matcher mutationMatcher = mutationPattern.matcher(body);
if (mutationMatcher.find()) {
String operationName = mutationMatcher.group(2) != null ? mutationMatcher.group(2) : mutationMatcher.group(4);
String inputs = mutationMatcher.group(3) != null ? mutationMatcher.group(3) :
(mutationMatcher.group(5) != null ? mutationMatcher.group(5) : "");
if (operationName != null && !operationName.isEmpty()) {
return "mutation " + operationName + inputs;
} else {
return "mutation (unnamed)" + inputs;
}
}
// If no mutation found, check for queries
Pattern queryPattern = Pattern.compile("\"query\"\\s*:\\s*\"\\s*(query\\s+(\\w+)(\\([^)]*\\))?|query)\\s*\\{|\"query\"\\s*:\\s*\"query\\s+(\\w+)(\\([^)]*\\))?");
Matcher queryMatcher = queryPattern.matcher(body);
if (queryMatcher.find()) {
String operationName = queryMatcher.group(2) != null ? queryMatcher.group(2) : queryMatcher.group(4);
String inputs = queryMatcher.group(3) != null ? queryMatcher.group(3) :
(queryMatcher.group(5) != null ? queryMatcher.group(5) : "");
if (operationName != null && !operationName.isEmpty()) {
return "query " + operationName + inputs;
} else {
return "query (unnamed)" + inputs;
}
}
} catch (Exception e) {
// If parsing fails, try to find GraphQL operations in raw body
Pattern rawPattern = Pattern.compile("(mutation|query)\\s+(\\w+)(\\([^)]*\\))?");
Matcher rawMatcher = rawPattern.matcher(body);
if (rawMatcher.find()) {
String inputs = rawMatcher.group(3) != null ? rawMatcher.group(3) : "";
return rawMatcher.group(1) + " " + rawMatcher.group(2) + inputs;
} else {
// Check for unnamed operations
Pattern unnamedPattern = Pattern.compile("(mutation|query)\\s*(\\([^)]*\\))?\\s*\\{");
Matcher unnamedMatcher = unnamedPattern.matcher(body);
if (unnamedMatcher.find()) {
String inputs = unnamedMatcher.group(2) != null ? unnamedMatcher.group(2) : "";
return unnamedMatcher.group(1) + " (unnamed)" + inputs;
}
}
}
}