This document outlines a combined list of vulnerabilities identified in the GraphQL API, addressing information disclosure, lack of access control, and data integrity issues.
-
Vulnerability Name: Information Disclosure via DjangoDebugMiddleware
-
Description: When
DjangoDebugMiddleware
is enabled in a production environment and the GraphQL schema includes theDjangoDebug
type (typically exposed as a field named_debug
), an external attacker can query this_debug
field to retrieve sensitive debugging information. This includes:- Executed SQL queries: Full SQL queries executed against the database.
- SQL query parameters: Parameters used in parameterized SQL queries, which can contain sensitive data.
- Database Vendor and Alias: Information about the database system and connection alias.
- Query Duration and Performance Metrics: Timing information for queries.
- Stack traces of exceptions: Detailed stack traces for any exceptions occurring during query execution, revealing internal code paths and potentially sensitive application logic.
To trigger this vulnerability, an attacker needs to send a GraphQL query to the publicly accessible endpoint that includes the
_debug
field in the query selection set. The middleware, if enabled, will intercept the query execution and append the debug information to the response under the_debug
field. -
Impact: High. Exposure of SQL queries, parameters, and stack traces can lead to significant information disclosure. This information can reveal database schema details, data access patterns, potentially sensitive data within queries, and internal application logic, which can be leveraged for further attacks or unauthorized data access.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations:
- None. The code does not prevent the middleware from being used in production or warn against it.
-
Missing Mitigations:
- Documentation Warning: Clear and prominent documentation explicitly warning against enabling
DjangoDebugMiddleware
in production environments due to the risk of information disclosure. - Production Check/Setting: Implement a mechanism to detect or enforce that
DjangoDebugMiddleware
is not used in production, such as:- Checking
settings.DEBUG
and automatically disabling the middleware ifDEBUG=False
. - Introducing a dedicated setting (e.g.,
GRAPHENE_DEBUG_ENABLED
) to control the middleware's activation, defaulting toFalse
in production settings templates. - Providing guidance and best practices for securely disabling or removing the
DjangoDebug
field from production GraphQL schemas.
- Checking
- Documentation Warning: Clear and prominent documentation explicitly warning against enabling
-
Preconditions:
DjangoDebugMiddleware
must be added to theMIDDLEWARE
setting in Django'ssettings.py
.- The GraphQL schema must include the
DjangoDebug
type, typically added as a field named_debug
to the rootQuery
type. - The Django application must be deployed in a publicly accessible environment.
-
Source Code Analysis:
/code/graphene_django/debug/middleware.py
:Theclass DjangoDebugMiddleware: def resolve(self, next, root, info, **args): context = info.context django_debug = getattr(context, "django_debug", None) if not django_debug: if context is None: raise Exception("DjangoDebug cannot be executed in None contexts") try: context.django_debug = DjangoDebugContext() except Exception: raise Exception( "DjangoDebug need the context to be writable, context received: {}.".format( context.__class__.__name__ ) ) if info.schema.get_type("DjangoDebug") == info.return_type: return context.django_debug.get_debug_result() # Returns debug info if field type is DjangoDebug try: result = next(root, info, **args) except Exception as e: return context.django_debug.on_resolve_error(e) context.django_debug.add_result(result) return result
DjangoDebugMiddleware.resolve
method checks if the resolved field's return type isDjangoDebug
. If it is, it directly returns the result ofcontext.django_debug.get_debug_result()
, which contains the collected debug information (SQL queries, exceptions). This logic is executed unconditionally if the middleware is active and the schema includes theDjangoDebug
type./code/graphene_django/debug/sql/tracking.py
and/code/graphene_django/debug/exception/formating.py
: These modules are responsible for collecting SQL query details and formatting exception information, respectively, which are then exposed via theDjangoDebug
type./code/graphene_django/debug/types.py
: Defines theDjangoDebug
GraphQL type, which includes fields likesql
andexceptions
, making the debug data accessible via GraphQL queries.
-
Security Test Case:
- Deploy a Django application with
graphene-django
in a publicly accessible test environment. - Modify
settings.py
to includegraphene_django.debug.middleware.DjangoDebugMiddleware
in theMIDDLEWARE
list:MIDDLEWARE = [ # ... other middleware ... "graphene_django.debug.middleware.DjangoDebugMiddleware", ]
- In your
schema.py
, add theDjangoDebug
field to your rootQuery
type:import graphene from graphene_django import DjangoObjectType from graphene_django.debug import DjangoDebug from your_app.models import MyModel # Replace with your actual models class MyModelType(DjangoObjectType): class Meta: model = MyModel class Query(graphene.ObjectType): node = graphene.relay.Node.Field() my_model = graphene.Field(MyModelType) debug = graphene.Field(DjangoDebug, name='_debug') # Add DjangoDebug field def resolve_my_model(root, info): return MyModel.objects.first() # Example resolver that triggers a DB query schema = graphene.Schema(query=Query)
- Access the GraphiQL interface or use a tool like
curl
to send a GraphQL query to your application's public endpoint:query DebugQuery { myModel { id } _debug { sql { rawSql params } exceptions { message stack } } }
- Examine the GraphQL response. You should see the
_debug
field in the response data. - Within the
_debug
field, inspect thesql
list. It should contain entries for the SQL queries executed to resolve themyModel
field (e.g., the query to fetchMyModel.objects.first()
). Each SQL entry should includerawSql
(the SQL query string) andparams
(the query parameters). - If any errors occurred during query resolution, the
exceptions
list in_debug
will contain details about those exceptions, includingmessage
andstack
traces.
By successfully retrieving SQL queries and potentially exception details via the
_debug
field in a public environment, you have confirmed the Information Disclosure vulnerability. - Deploy a Django application with
- Vulnerability Name: Exposed GraphiQL and Introspection Endpoint
- Description:
An attacker who accesses a publicly available GraphQL endpoint may inadvertently encounter an interactive GraphiQL IDE and be able to execute introspection queries. This action discloses the full schema—including queries, mutations, types, fields, and their descriptions—which can help an adversary understand internal data structures, business logic, and potentially plan more focused attacks.
- For example, several URL configuration files now (in
/code/graphene_django/tests/urls_pretty.py
and/code/graphene_django/tests/urls_inherited.py
) configure the GraphQL view such that the GraphiQL parameter is either omitted (defaulting to a development setting) or explicitly set toTrue
. - As a result, if these configurations make their way into a production deployment, introspection and an interactive IDE may be exposed to external attackers.
- For example, several URL configuration files now (in
- Impact: Full disclosure of the GraphQL schema enables attackers to examine the internal data model, understand resolver behavior, and potentially craft queries or mutations that bypass non‑standard business rules or discover further weaknesses in the back‑end implementations.
- Vulnerability Rank: High
- Currently Implemented Mitigations:
- The example and test projects include documentation on how to configure the GraphQL view and highlight GraphiQL usage in a development/testing context.
- However, the provided configurations themselves always instantiate the GraphQL view with GraphiQL enabled (or do not check for production mode).
- Missing Mitigations:
- In production deployments, the GraphiQL interface and introspection queries should be disabled (for example, by setting
graphiql=False
whenDEBUG
is not enabled). - Additional access control (such as enforcing authentication or IP‑whitelisting) should be applied to introspection endpoints.
- In production deployments, the GraphiQL interface and introspection queries should be disabled (for example, by setting
- Preconditions:
- The application is deployed using one of the sample or test URL configurations, such as those in
/code/graphene_django/tests/urls_pretty.py
or/code/graphene_django/tests/urls_inherited.py
, without proper environment‐based gating.
- The application is deployed using one of the sample or test URL configurations, such as those in
- Source Code Analysis:
- The original issue was observed in
/code/examples/cookbook/cookbook/urls.py
, where the GraphQL view is instantiated withgraphiql=True
. - The new evidence from
/code/graphene_django/tests/urls_pretty.py
(usingpretty=True
) and/code/graphene_django/tests/urls_inherited.py
(with a custom subclass explicitly settinggraphiql=True
) confirms that multiple endpoint variants exist with GraphiQL enabled. - Without a runtime check (for example, verifying that
DEBUG
is False) to disable these features, an external attacker would be able to access the full schema via introspection.
- The original issue was observed in
- Security Test Case:
- As an external attacker, use a browser, cURL, or Postman to send a GET request to the public GraphQL endpoint (for example,
http://target.example.com/graphql
orhttp://target.example.com/graphql/inherited/
). - Confirm that the response includes the GraphiQL IDE (or otherwise allows interactive query execution).
- Using the IDE or a crafted introspection query (such as
{ __schema { types { name } } }
), verify that detailed schema information is returned. - Document that the full schema disclosure is possible due to the misuse of development settings in endpoint configuration.
- As an external attacker, use a browser, cURL, or Postman to send a GET request to the public GraphQL endpoint (for example,
- Description:
An attacker who accesses a publicly available GraphQL endpoint may inadvertently encounter an interactive GraphiQL IDE and be able to execute introspection queries. This action discloses the full schema—including queries, mutations, types, fields, and their descriptions—which can help an adversary understand internal data structures, business logic, and potentially plan more focused attacks.
- Vulnerability Name: Verbose Error Message Disclosure via GraphQL Errors
- Description:
When a client submits an invalid or intentionally malformed GraphQL query, the underlying exception is caught and its full, unsanitized details are returned in the response. This includes internal error messages, stack traces, and—based on new debug middleware tests—the executed SQL queries and debug information via the
_debug
field if requested.- Several test files (such as
/code/graphene_django/tests/test_views.py
and/code/graphene_django/tests/schema_view.py
) demonstrate that errors produce overly detailed responses. - Moreover, the debugging components in
/code/graphene_django/debug/middleware.py
and its tests (e.g./code/graphene_django/debug/tests/test_query.py
) reveal that when the GraphQL query explicitly requests the_debug
field, the response includes a complete list of executed SQL queries and full exception stack traces even in case of errors.
- Several test files (such as
- Impact: The detailed error output can inadvertently disclose critical internal system information (such as file paths, SQL statements, and internal logic) that an attacker can use to further refine exploits or identify other sensitive operations.
- Vulnerability Rank: High
- Currently Implemented Mitigations:
- Although errors are returned in a structured JSON format, there is no filtering mechanism or environment‐specific error suppression in the core GraphQL view or the dedicated debug middleware.
- Missing Mitigations:
- In production deployments, error messages should be sanitized to return only generic error responses (for example, “An error occurred”) without exposing internal details.
- Debug middleware (which exposes detailed SQL queries and stack traces) should be deactivated or restricted to authorized users only in production.
- Preconditions:
- The system is running with detailed error handling enabled (for example, with
DEBUG=True
or with the debug middleware active). - The public schema includes fields (such as
_debug
) that return sensitive information without proper access control.
- The system is running with detailed error handling enabled (for example, with
- Source Code Analysis:
- In
/code/graphene_django/views.py
, exception handling within the GraphQL view does not remove internal error details before they are sent to the client. - Test cases such as those in
/code/graphene_django/tests/test_views.py
demonstrate that error messages include strings like “Throws!” along with full stack information. - Additional evidence from
/code/graphene_django/debug/middleware.py
shows that when the GraphQL type namedDjangoDebug
is returned (for example, via a_debug
query), detailed debug information—including a list of executed SQL queries with full SQL texts—is provided, even in error scenarios.
- In
- Security Test Case:
- Send a deliberately malformed GraphQL query (or one targeting the
_debug
field) to the public endpoint (for example,http://target.example.com/graphql
). - Capture the JSON response and inspect the
errors
array and, if present, the_debug
field. - Verify that the error output includes detailed internal information (such as full stack traces, SQL query logs, or internal exception messages).
- Document that such verbose error details provide an attacker with sensitive insights into the system’s inner workings.
- Send a deliberately malformed GraphQL query (or one targeting the
- Description:
When a client submits an invalid or intentionally malformed GraphQL query, the underlying exception is caught and its full, unsanitized details are returned in the response. This includes internal error messages, stack traces, and—based on new debug middleware tests—the executed SQL queries and debug information via the
- Vulnerability Name: Lack of Access Control on GraphQL Endpoint (Unauthorized Data Access)
- Description:
The GraphQL endpoints in the example and test projects are configured without enforcing authentication or authorization checks either at the view level or within resolver functions.
- Testing in files such as
/code/graphene_django/tests/test_get_queryset.py
shows that queries can be submitted and immediately executed without credential verification. - Although some resolvers incorporate custom access checks (by raising exceptions in specific test cases), these checks are not applied by default across the exposed endpoints.
- Testing in files such as
- Impact: Without integrated access control, an unauthenticated attacker may submit arbitrary queries and mutations. This can lead not only to unauthorized data disclosure but also to potential modification of backend data, thus breaching confidentiality and integrity.
- Vulnerability Rank: High
- Currently Implemented Mitigations:
- The documentation and tests mention possible integrations with Django’s authentication and permissions frameworks.
- Some test resolvers simulate access control (for instance, by filtering querysets with custom
get_queryset
methods). - However, these measures are only demonstrated or applied in isolated test scenarios.
- Missing Mitigations:
- Robust authentication and authorization must be enforced on every GraphQL endpoint (for example, by applying Django middleware, decorators, or resolver‑level checks) so that only properly authenticated and authorized users can execute queries and mutations.
- Critical operations should require additional safeguards such as API keys or session verification.
- Preconditions:
- The GraphQL API is deployed using the example/test configurations without integrated access control middleware.
- Endpoint access to queries and mutations is unrestricted for unauthenticated users.
- Source Code Analysis:
- In the URL configuration files (such as
/code/graphene_django/tests/urls.py
), the GraphQL view is registered without attachment of access control middleware. - Resolver methods in test files (for example, in
/code/graphene_django/tests/test_get_queryset.py
) illustrate that although custom access checks can be written, no default enforcement exists on publicly exposed endpoints.
- In the URL configuration files (such as
- Security Test Case:
- As an unauthenticated user, send a query requesting sensitive data (for example, requesting all fields from a Reporter model that includes personal data).
- Verify that the endpoint returns the data without prompting for credentials.
- Next, attempt to execute a mutation that modifies data and confirm that the operation is performed without any authentication challenge.
- Document that the absence of access control permits unauthorized access and data modification.
- Description:
The GraphQL endpoints in the example and test projects are configured without enforcing authentication or authorization checks either at the view level or within resolver functions.
-
Vulnerability Name: GraphQL Mutation Mass Assignment
-
Description: The
IntroduceShip
mutation in the provided Star Wars example schema directly uses input values (ship_name
,faction_id
) to create a newShip
object without any input validation or sanitization. Step-by-step to trigger:- Access the publicly available GraphQL endpoint of the application.
- Craft a GraphQL mutation query targeting the
introduceShip
mutation. - In the mutation query, provide arbitrary values for
shipName
andfactionId
input fields. For example, include special characters, excessively long strings, or data in unexpected formats. - Send the crafted mutation query to the GraphQL endpoint.
- Observe that a new
Ship
object is created in the database with the provided, potentially malicious or unexpected, data.
-
Impact: Data integrity issues. In a more complex application, this vulnerability could allow an attacker to modify unintended fields of a model through GraphQL mutations. This can lead to data corruption, unauthorized data manipulation, or privilege escalation if model fields control access or permissions. Even in this example, while the impact is limited, it demonstrates a lack of input validation which is a security concern.
-
Vulnerability Rank: High
-
Currently Implemented Mitigations: None. The provided code example for
IntroduceShip
mutation directly creates aShip
object from input parameters without any validation. -
Missing Mitigations: Input validation and sanitization should be implemented within the
mutate_and_get_payload
method of theIntroduceShip
mutation.- Input Validation: Validate that
faction_id
corresponds to an existingFaction
object. Validate the format and length ofship_name
to ensure it meets expected criteria (e.g., prevent excessively long names or injection of special characters if not intended). Consider leveraging Django forms for structured input validation, especially when usingDjangoModelFormMutation
. - Authorization: Implement authorization checks to ensure that the user performing the mutation has the necessary permissions to create a
Ship
object and associate it with aFaction
. - Field Whitelisting (in more complex scenarios): If the
Ship
model had more fields, implement explicit whitelisting to only allow modification of intended fields through the mutation, preventing attackers from manipulating other sensitive fields via mass assignment.
- Input Validation: Validate that
-
Preconditions:
- The GraphQL API endpoint is publicly accessible.
- The GraphQL schema exposes mutations (like
introduceShip
) that create or update Django models. - These mutations directly use input values to create or update model instances without sufficient validation or sanitization.
-
Source Code Analysis: File:
/code/examples/starwars/schema.py
class IntroduceShip(relay.ClientIDMutation): class Input: ship_name = graphene.String(required=True) faction_id = graphene.String(required=True) ship = graphene.Field(Ship) faction = graphene.Field(Faction) @classmethod def mutate_and_get_payload( cls, root, info, ship_name, faction_id, client_mutation_id=None ): ship = create_ship(ship_name, faction_id) # [Vulnerable Code] Direct model creation without input validation faction = get_faction(faction_id) return IntroduceShip(ship=ship, faction=faction)
File:
/code/examples/starwars/data.py
def create_ship(ship_name, faction_id): new_ship = Ship(name=ship_name, faction_id=faction_id) # [Vulnerable Code] Direct attribute assignment from input new_ship.save() return new_ship
Visualization:
[GraphQL Client] --> Mutation Request (shipName="<script>...", factionId="1") --> [GraphQLView] --> IntroduceShip.mutate_and_get_payload() | V create_ship(ship_name, faction_id) --> [Ship Model Creation] --> [Database]
Explanation:
- The attacker sends a GraphQL mutation request to the
introduceShip
endpoint with crafted input values forshipName
andfactionId
. - The
GraphQLView
processes the request and calls themutate_and_get_payload
method of theIntroduceShip
mutation. - Inside
mutate_and_get_payload
, thecreate_ship
function is called, which directly uses theship_name
andfaction_id
from the input to instantiate aShip
model object. - The
Ship
object is saved to the database without any validation of the input data. - This direct assignment from input to model fields without validation is the root cause of the Mass Assignment vulnerability.
- The attacker sends a GraphQL mutation request to the
-
Security Test Case:
-
Setup: Deploy the
graphene-django
example project (starwars). Ensure the GraphQL endpoint is accessible. -
Craft Malicious Mutation Query: Prepare a GraphQL mutation query to call
introduceShip
with a potentially maliciousshipName
and a validfactionId
.mutation IntroduceMaliciousShip { introduceShip(input:{clientMutationId:"test", shipName: "Malicious Ship <script>alert('XSS')</script>", factionId: "1"}) { ship { id name } faction { name } } }
-
Execute Mutation: Send the crafted mutation query to the GraphQL endpoint using a tool like
curl
,Postman
, or a GraphQL client in a browser (if GraphiQL is enabled).Example
curl
command:curl -X POST -H "Content-Type: application/json" -d '{"query": "mutation IntroduceMaliciousShip { introduceShip(input:{clientMutationId:\"test\", shipName: \"Malicious Ship <script>alert('XSS')</script>\", factionId: \"1\"}) { ship { id name } faction { name } } }"}' http://127.0.0.1:8000/graphql
(Assuming the GraphQL endpoint is at
http://127.0.0.1:8000/graphql
) -
Verify in Database: Access the Django admin panel or use a database client to inspect the
starwars_ship
table. Check for the newly createdShip
object. Verify that thename
field of the newly createdShip
contains the malicious string "Malicious Ship <script>alert('XSS')</script>". -
Expected Result: The
Ship
object should be successfully created in the database, and thename
field should contain the injected malicious string. This confirms the Mass Assignment vulnerability as input is directly used to populate the model without validation. In a real application with more fields, this would indicate the potential to manipulate other fields as well if exposed in the mutation.
-