@@ -5,7 +5,7 @@ function Get-GraphQLVariableList {
55 . DESCRIPTION
66 Gets a list of variable (argument) names, types, and nullable status from a GraphQL operation.
77 . PARAMETER Query
8- The GraphQL operation (query or mutation ) to obtain the variable definitions from.
8+ The GraphQL operation (query, mutation, subscription, or fragment ) to obtain the variable definitions from.
99 . PARAMETER FilePath
1010 The path to a file containing a GraphQL query to obtain the variable definitions from.
1111 . EXAMPLE
@@ -23,51 +23,15 @@ function Get-GraphQLVariableList {
2323
2424 Gets a list of variable definitions from a file containing a GraphQL query and renders the results to the console as a table.
2525 . EXAMPLE
26- $mutation = '
27- mutation AddNewPet ($name: String!, $petType: PetType, $petLocation: String!, $petId: Int!) {
28- addPet(name: $name, petType: $petType, location: $petLocation, id: $petId) {
29- name
30- petType
31- location
32- id
33- }
26+ $fragment = '
27+ fragment UserInfo($includeEmail: Boolean!) on User {
28+ name
29+ email @include(if: $includeEmail)
3430 }'
3531
36- $wordListPath = ".\SQL.txt"
37- $words = [IO.File]::ReadAllLines($wordListPath)
38-
39- $uri = "https://mytargetserver/v1/graphql"
40-
41- # Array to store results from Invoke-GraphQLQuery -Detailed for later analysis:
42- $results = @()
43-
44- # Get the variable definition from the supplied mutation:
45- $variableList = $mutation | Get-GraphQLVariableList
46-
47- $words | ForEach-Object {
48- $queryVarTable = @{}
49- $word = $_
50-
51- $variableList | Select Parameter, Type | ForEach-Object {
52- $randomInt = Get-Random
53- if ($_.Type -eq "Int") {
54- if (-not($queryVarTable.ContainsKey($_.Parameter))) {
55- $queryVarTable.Add($_.Parameter, $randomInt)
56- }
57- }
58- else {
59- if (-not($queryVarTable.ContainsKey($_.Parameter))) {
60- $queryVarTable.Add($_.Parameter, $word)
61- }
62- }
63- }
64-
65- $gqlResult = Invoke-GraphQLQuery -Mutation $mutation -Variables $queryVarTable -Headers $headers -Uri $uri -Detailed
66- $result = [PSCustomObject]@{ParamValues=($queryVarTable);Result=($gqlResult)}
67- $results += $result
68- }
32+ Get-GraphQLVariableList -Query $fragment
6933
70- Iterate through a SQL injection word list, generate a random integer for parameters of type Int, use the current word in the word list for all other variables, attempt to fuzz each GraphQL parameter for the defined mutation at the target endpoint .
34+ Gets a list of variable definitions from a GraphQL fragment .
7135 . INPUTS
7236 System.String
7337 . LINK
@@ -83,7 +47,7 @@ function Get-GraphQLVariableList {
8347 (
8448 [Parameter (Mandatory = $true , ParameterSetName = " Query" ,
8549 ValueFromPipeline = $true ,
86- Position = 0 )][ValidateLength (12 , 1073741791 )][Alias (" Operation" , " Mutation" )][System.String ]$Query ,
50+ Position = 0 )][ValidateLength (12 , 1073741791 )][Alias (" Operation" , " Mutation" , " Fragment " )][System.String ]$Query ,
8751
8852 [Parameter (Mandatory = $false , ParameterSetName = " FilePath" , Position = 0 )][ValidateNotNullOrEmpty ()][Alias (' f' , ' Path' )][System.IO.FileInfo ]$FilePath
8953
@@ -102,7 +66,7 @@ function Get-GraphQLVariableList {
10266 }
10367 PROCESS {
10468 # Exception to be used through the function in the case that an invalid GraphQL query or mutation is passed:
105- $ArgumentException = New-Object - TypeName ArgumentException - ArgumentList " Not a valid GraphQL query or mutation . Verify syntax and try again."
69+ $ArgumentException = New-Object - TypeName ArgumentException - ArgumentList " Not a valid GraphQL operation ( query, mutation, subscription, or fragment) . Verify syntax and try again."
10670
10771 # Get the raw GraphQL query content
10872 [string ]$graphQlQuery = $Query
@@ -121,132 +85,93 @@ function Get-GraphQLVariableList {
12185 Write-Error - Exception $ArgumentException - Category InvalidArgument - ErrorAction Stop
12286 }
12387
124- # Attempt to determine if value passed to the query parameter is an actual GraphQL query or mutation. If not, throw:
125- # Updated regex pattern to be more flexible with GraphQL operation detection
126- $graphqlOperationPattern = ' ^\s*(query|mutation|subscription)(\s+\w+)?\s*[\(\{]'
127- if (-not ($graphQlQuery -match $graphqlOperationPattern )) {
88+ # Remove comments and normalize whitespace
89+ $cleanQuery = $graphQlQuery -replace ' #[^\r\n]*' , ' ' -replace ' \s+' , ' '
90+
91+ # Simple check for operation keywords
92+ if (-not ($cleanQuery -match ' (query|mutation|subscription|fragment)' )) {
12893 Write-Error - Exception $ArgumentException - Category InvalidArgument - ErrorAction Stop
12994 }
13095
13196 # List of objects that are returned by default:
13297 $results = [System.Collections.Generic.List [GraphQLVariable ]]::new()
13398
134- # Ensure $results is properly initialized
135- if ($null -eq $results ) {
136- $results = [System.Collections.Generic.List [GraphQLVariable ]]::new()
99+ # Parse the entire query at once using more comprehensive regex
100+ # This regex captures: operation type, optional name, optional variables in parentheses, optional "on Type" for fragments
101+ $fullOperationPattern = ' (query|mutation|subscription|fragment)\s*([a-zA-Z_][a-zA-Z0-9_]*)?\s*(\([^)]+\))?\s*(on\s+[a-zA-Z_][a-zA-Z0-9_]*)?\s*\{'
102+
103+ # Variable pattern to extract individual variables from the parentheses
104+ $variablePattern = ' \$([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z0-9_\[\]!]+)(?:\s*=\s*[^,)]+)?'
105+
106+ $operationMatches = [regex ]::Matches($cleanQuery , $fullOperationPattern , ' IgnoreCase' )
107+
108+ if ($operationMatches.Count -eq 0 ) {
109+ Write-Error - Exception $ArgumentException - Category InvalidArgument - ErrorAction Stop
137110 }
138111
139- # Updated parsing logic based on Parse-GraphQLVariables
140- # Regex to match variable definitions like $variableName: Type [= DefaultValue]
141- $variablePattern = ' \$(\w+):\s*([\w\[\]!]+)(?:\s*=\s*(".*?"|\d+|\w+|\[.*?\]|\{.*?\}))?'
142- # Regex to detect operation start (query or mutation or subscription) - more flexible
143- $operationPattern = ' ^\s*(query|mutation|subscription)(\s+(\w+))?\s*[\(\{]'
144-
145- # Split query into lines for processing
146- $lines = $graphQlQuery -split ' \r?\n'
147- $currentOperation = $null
148- $currentOperationType = $null
149- $variables = @ ()
150-
151- foreach ($line in $lines ) {
152- # Trim whitespace
153- $line = $line.Trim ()
154- # Skip empty lines or comments
155- if ([string ]::IsNullOrWhiteSpace($line ) -or $line.StartsWith (' #' )) {
156- continue
112+ foreach ($operationMatch in $operationMatches ) {
113+ $operationType = $operationMatch.Groups [1 ].Value.ToLower()
114+ $operationName = if ($operationMatch.Groups [2 ].Success) { $operationMatch.Groups [2 ].Value } else { $operationType }
115+ $variablesSection = if ($operationMatch.Groups [3 ].Success) { $operationMatch.Groups [3 ].Value } else { " " }
116+ $onType = if ($operationMatch.Groups [4 ].Success) { $operationMatch.Groups [4 ].Value } else { " " }
117+
118+ # For fragments, include the "on Type" part in the operation name if present
119+ if ($operationType -eq " fragment" -and $onType ) {
120+ $operationName = if ($operationMatch.Groups [2 ].Success) {
121+ " $ ( $operationMatch.Groups [2 ].Value) $onType "
122+ } else {
123+ $onType
124+ }
157125 }
158126
159- # Check for new operation (query or mutation or subscription)
160- if ($line -match $operationPattern ) {
161- # If we were processing a previous operation, save its variables
162- if ($currentOperation -and $variables.Count -gt 0 ) {
163- # Process variables for the previous operation
164- foreach ($variable in $variables ) {
127+ # Extract variables from the variables section
128+ if ($variablesSection ) {
129+ $variableMatches = [regex ]::Matches($variablesSection , $variablePattern )
130+
131+ if ($variableMatches.Count -gt 0 ) {
132+ foreach ($variableMatch in $variableMatches ) {
133+ $paramName = $variableMatch.Groups [1 ].Value
134+ $rawType = $variableMatch.Groups [2 ].Value
135+
165136 $gqlVariable = [GraphQLVariable ]::new()
166137 $gqlVariable.HasVariables = $true
167- $gqlVariable.Operation = $currentOperation
168- $gqlVariable.OperationType = $currentOperationType
169- $gqlVariable.Parameter = $variable.Name
170- $gqlVariable.Type = ($variable.Type.Replace (" !" , " " ).Replace(" [" , " " ).Replace(" ]" , " " ))
171-
172- if ($variable.Type.Contains (" !" )) {
173- $gqlVariable.Nullable = $false
174- }
175- else {
176- $gqlVariable.Nullable = $true
177- }
178-
179- if ($variable.Type.Contains (" [" ) -and $variable.Type.Contains (" ]" )) {
180- $gqlVariable.IsArray = $true
181- }
182- else {
183- $gqlVariable.IsArray = $false
184- }
185-
186- $gqlVariable.RawType = $variable.Type
187- $results.Add ($gqlVariable )
188- }
189- $variables = @ ()
190- }
138+ $gqlVariable.Operation = $operationName
139+ $gqlVariable.OperationType = $operationType
140+ $gqlVariable.Parameter = $paramName
141+ $gqlVariable.RawType = $rawType
191142
192- # Capture operation name (if provided) or type
193- $currentOperationType = $Matches [1 ].ToLower()
194- $currentOperation = if ($Matches [3 ]) { $Matches [3 ] } else { $Matches [1 ] }
195- }
143+ # Parse type information
144+ $cleanType = $rawType -replace ' [\[\]!]' , ' '
145+ $gqlVariable.Type = $cleanType
196146
197- # Check for variable definitions
198- if ($line -match $variablePattern ) {
199- foreach ($match in [regex ]::Matches($line , $variablePattern )) {
200- $variables += [PSCustomObject ]@ {
201- Name = $match.Groups [1 ].Value
202- Type = $match.Groups [2 ].Value
203- DefaultValue = if ($match.Groups [3 ].Success) { $match.Groups [3 ].Value } else { $null }
204- }
205- }
206- }
207- }
147+ # Check if nullable (! means non-null, so nullable = false if ! is present)
148+ $gqlVariable.Nullable = -not $rawType.Contains (' !' )
208149
209- # Save variables for the last operation
210- if ($currentOperation -and $variables.Count -gt 0 ) {
211- foreach ($variable in $variables ) {
212- $gqlVariable = [GraphQLVariable ]::new()
213- $gqlVariable.HasVariables = $true
214- $gqlVariable.Operation = $currentOperation
215- $gqlVariable.OperationType = $currentOperationType
216- $gqlVariable.Parameter = $variable.Name
217- $gqlVariable.Type = ($variable.Type.Replace (" !" , " " ).Replace(" [" , " " ).Replace(" ]" , " " ))
218-
219- if ($variable.Type.Contains (" !" )) {
220- $gqlVariable.Nullable = $false
221- }
222- else {
223- $gqlVariable.Nullable = $true
224- }
150+ # Check if array
151+ $gqlVariable.IsArray = $rawType.Contains (' [' ) -and $rawType.Contains (' ]' )
225152
226- if ($variable.Type.Contains (" [" ) -and $variable.Type.Contains (" ]" )) {
227- $gqlVariable.IsArray = $true
228- }
229- else {
230- $gqlVariable.IsArray = $false
153+ $results.Add ($gqlVariable )
154+ }
155+ } else {
156+ # Operation exists but no variables
157+ $gqlVariable = [GraphQLVariable ]::new()
158+ $gqlVariable.HasVariables = $false
159+ $gqlVariable.Operation = $operationName
160+ $gqlVariable.OperationType = $operationType
161+ $results.Add ($gqlVariable )
231162 }
232-
233- $gqlVariable.RawType = $variable.Type
163+ } else {
164+ # Operation exists but no variables section
165+ $gqlVariable = [GraphQLVariable ]::new()
166+ $gqlVariable.HasVariables = $false
167+ $gqlVariable.Operation = $operationName
168+ $gqlVariable.OperationType = $operationType
234169 $results.Add ($gqlVariable )
235170 }
236171 }
237172
238- # If no operations with variables were found, return a single object with just operation info
239173 if ($results.Count -eq 0 ) {
240- # Fallback to find at least one operation for basic info
241- if ($graphQlQuery -match $graphqlOperationPattern ) {
242- $gqlVariable = [GraphQLVariable ]::new()
243- $gqlVariable.Operation = if ($Matches [2 ]) { $Matches [2 ].Trim() } else { $Matches [1 ] }
244- $gqlVariable.OperationType = $Matches [1 ].ToLower()
245- $results.Add ($gqlVariable )
246- }
247- else {
248- Write-Error - Exception $ArgumentException - Category InvalidArgument - ErrorAction Stop
249- }
174+ Write-Error - Exception $ArgumentException - Category InvalidArgument - ErrorAction Stop
250175 }
251176
252177 return $results
0 commit comments