Table of Contents
There may be more than one way to exploit any given vulnerability, the solutions demonstrated below aim to illustrate one way of achieving successful exploitation.
Some solutions include code snippets that are written in Python and use the requests library for HTTP requests.
The first essential step in every security test is to gain a bit of insight into the technology the remote server is using. By knowing the technologies in use, you can start building up a plan how to attack the application or the underlying infrastructure.
For GraphQL, a tool called graphw00f exists. Let's explore how it can help us achieve detection and fingerprinting of GraphQL.
Detecting where GraphQL lives is pretty trivial, there are common places where you would typically see a graphql endpoint. For example, /graphql, /v1/graphql, etc.
Point graphw00f at DVGA to figure out where GraphQL lives:
$> python3 graphw00f.py -d -t http://localhost:5013/graphql
+-------------------+
| graphw00f |
+-------------------+
*** ***
** ***
** **
+--------------+ +--------------+
| Node X | | Node Y |
+--------------+ +--------------+
*** ***
** **
** **
+------------+
| Node Z |
+------------+
graphw00f - v1.0.3
The fingerprinting tool for GraphQL
Dolev Farhi (dolev@lethalbit.com)
Checking http://dvga.example.local:5013/graphql
[*] Found GraphQL at http://dvga.example.local:5013/graphql
[*] You can now try and fingerprint GraphQL using: graphw00f.py -t http://dvga.example.local:5013/graphql
graphw00f can try and fingerprint GraphQL servers in order to determine the underlying implementation. By knowing what specific engine runs GraphQL, you can map what security mechanisms you may face during an assessment.
Point graphw00f at DVGA to figure out what technology it's running.
$> python3 graphw00f.py -t http://dvga.example.local:5013/graphql -f
[*] Checking if GraphQL is available at http://dvga.example.local:5013/graphql...
[*] Found GraphQL...
[*] Attempting to fingerprint...
[*] Discovered GraphQL Engine: (Graphene)
[!] Attack Surface Matrix: https://github.com/dolevf/graphw00f/blob/main/docs/graphene.md
[!] Technologies: Python
[!] Homepage: https://graphene-python.org
[*] Completed.
As you can see, DVGA runs graphene. Use the Attack Surface Matrix to see how Graphene ships GrapQL by default from a security perspective.
GraphQL supports Request Batching. Batched requests are processed one after the other by GraphQL, which makes it a good candidate for Denial of Service attacks, as well as other attacks such as Brute Force and Enumeration.
If a resource intensive GraphQL query is identified, an attacker may leverage batch processing to call the query and potentially overwhelm the service for a prolonged period of time.
The query systemUpdate
seems to be taking a long time to complete, and can be used to overwhelm the server by batching a system update request query.
In GraphQL, when types reference eachother, it is often possible to build a circular query that grows exponentially to a point it could bring the server down to its knees. Countermeasures such as max_depth
can help mitigate these types of attacks.
The max_depth
functionality acts as a safeguard, and defines how deep a query can get, ensuring deeply constructed queries will not be accepted by GraphQL.
The application offers two types, namely Owner
and Paste
, which reference eachother (an owner has a paste, and a paste has an owner), allowing a recursive query to be executed successfully.
Sometimes, certain queries may be computationally more expensive than others. A query may include certain fields that would trigger more complex backend logic in order to fulfill the query resolution. As attackers, we can abuse it by calling these actions frequently in order to cause resource exhaustion.
In GraphQL, a concept called Query Cost Analysis exists, which assigns weight values to fields that are more expensive to resolve than others. Using this feature, we can then create an upper threshold to reject queries that are expensive. Alternatively, a cache feature can be implemented to avoid repeating the same request in a short time window.
Various GraphQL implementation do not bother de-duplicating repeating fields in GraphQL, allowing the user to multiply the same requested fields as they wish.
This causes extra load on the server to return the same fields over and over again.
There are a few ways in which this issue can be mitigated:
Field De-Duplication can be achieved by using a middleware function to traverse the schema and remove any duplications, or simply analyze repeating patterns in order to reject the query.
Query Cost Analysis will be beneficial against these attacks, since each field will ultimately result in increased cost.
In GraphQL, it is possible to run multiple queries without needing to batch them together.
If batching is disabled, you could build a query composed of multiple aliases calling the same query or mutation, if the server is not analyzing the cost of the query, it will be possible to overwhelm the server's resources by using expensive queries using aliases.
Query Middleware is needed to identify the use of aliases in order to make a decision (reject/allow) based on your business logic.
Query Cost Analysis will be beneficial against these attacks, since each query will ultimately result in increased cost.
GraphQL Introspection is a special query that uses the __schema
field to interrogate GraphQL for its schema.
Introspection in itself is not a weakness, but a feature. However, if it is made available, it can be used and abused by attackers seeking information about your GraphQL implementation, such as what queries or mutations exist.
It is recommended to disable introspection in production to avoid data leakages.
Note: If introspection query is disabled, attackers may fall back to using the Field Suggestion feature to understand what queries and fields are supported by your GraphQL. Refer to Information Disclosure :: GraphQL Field Suggestionsattack for more information.
GraphQL has a an Integrated Development Environment named GraphiQL
(note the i
) that allows constructing queries in a friendly user interface.
GraphiQL is usually found in paths such as: /graphiql
or /console
, however, it can be in other places too.
GraphQL has a feature for field and operation suggestions. When a developer wants to integrate with a GraphQL API and types an incorrect field, as an example, GraphQL will attempt to suggest nearby fields that are similar.
Field suggestions is not a vulnerability in itself, but a feature that can be abused to gain more insight into GraphQL's schema, especially when Introspection is not allowed.
The GraphQL mutation importPaste
accepts arbitrary host, port and scheme to import pastes from and does
not restrict input such as localhost or other internal servers from being used. This may allow
forging requests on behalf of the application server to target other network nodes.
The mutation importPaste
allows escaping from the parameters and introduce a UNIX command by chaining
commands. The GraphQL resolver does not sufficiently validate the input, and passes it directly
into cURL
.
The query systemDiagnostics
accepts certain UNIX binaries as parameters for debugging purposes, such as
whoami
, ps
, etc. It acts as a restricted shell. However, it is protected
with a username and password. After obtaining the correct
credentials, the restricted shell seems to be bypassable by chaining commands together.
The GraphQL mutations createPaste
and importPaste
allow creating and importing new pastes. The pastes may include any character without any restrictions. The pastes would then render in
the Public and Private paste pages, which would result in a Cross Site Scripting vulnerability (XSS).
GraphQL actions such as mutation
and query
have the ability to take an operation name
as part of the query.
Here is an example query that uses MyName
as an operation name:
query MyName { getMyName { first last } }
The application is keeping track of all queries and mutations users are executing on this system in order to display them in the audit log.
However, the application is not doing a fair job at verifying the operation name.
Similarly to the Cross Site Scripting problem, a paste can also include HTML tags that would render in the application, resulting in an HTML injection.
Pastes can be filtered using the filter
parameter and it allows sending raw strings as query filters which are prone to SQL injections.
GraphiQL is available at the path /graphiql
with a poorly implemented authorization check.
Creating an allow-list or deny-list for GraphQL is a common technique to prevent malicious queries from being resolved by GraphQL.
In general, the allow-list approach is easier to maintain and less error-prone, since we only allow certain things we trust. It does not mean it cannot have flaws too.
The application has a deny-list mechanism implemented that attempts to reject Health queries using the
systemHealth
query.
The problem with this mechanism is that it does not take into consideration queries can have operation names.
The mutation uploadPaste
allows uploading pastes from the user's computer by specifying the file along
with the filename. The pastes are then stored on the server under a dedicated folder. The filename
argument allows any string, effectively providing the ability to write the file to any location on the server's
filesystem by traversing folders using ../../
The query systemDiagnostics
is an administrative functionality that allows running a subset of system
commands on the server. The query is governed by a username and password before processing the
command.
The password is weak, and the server has no rate limiting protections. This allows attackers to easily conduct brute force attacks against the server.
The GraphQL API allows creating circular fragments, such that two fragments are cross-referencing eachother.
When a Spread Operator (...
) references a fragment, which in return references a 2nd fragment that leads to the former fragment, may cause a recursive loop and crash the server.
The dedicated GraphiQL API endpoint /graphiql
throws stack traces and debugging messages upon erroneous queries.
Without logging in a user is able to forge the user identity claim within the JWT token for the me
query operation.