diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b6e4761
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,129 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..5d6e0d9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2021, graphw00f
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dbb7a39
--- /dev/null
+++ b/README.md
@@ -0,0 +1,117 @@
+
+
+
+ graphw00f - GraphQL Fingerprinting
+
+
+graphw00f (inspired by [wafw00f](https://github.com/EnableSecurity/wafw00f)) is a GraphQL fingerprinting tool.
+
+# Table of Contents
+* [How does it work?](#how-does-it-work)
+* [Detections](#detections)
+* [GraphQL Technologies Defence Matrices](#graphql-technologies-defence-matrices)
+* [Prerequisites](#prerequisites)
+* [Installation](#installation)
+* [Support & Issues](#support-and-issues)
+* [Resources](#resources)
+
+
+# How does it work?
+graphw00f is a Python utility which attempts to send a mixture of benign and malformed queries to determine the GraphQL engine running behind the scenes.
+
+Different GraphQL servers respond uniquely to queries, mutations and subscriptions given the right payload, this makes it trivial to fingerprint and distinguish between the various GraphQL servers. (CWE: [CWE-200](#CWE-Reference))
+
+# Detections
+graphw00f currently attempts to discover the following GraphQL engines:
+* Graphene
+* Ariadne
+* Apollo
+* graphql-go
+* gqlgen
+* WPGraphQL
+* GraphQL API for Wordpress
+* Ruby GraphQL
+* graphql-php
+* Hasura
+* HyperGraphQL
+* GraphQL for Java
+
+# GraphQL Technologies Defence Matrices
+Each fingerprinted technology (e.g. Graphene, Ariadne, ...) has an associated document ([example for graphene](https://github.com/dolevf/graphw00f/blob/main/docs/graphene.md)) which covers the security defence mechanisms the specific technology supports to give a better idea how the implementation may be attacked.
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|------------|-----------------|
+| On by Default | No Support | No Support | No Support | Enabled by Default | N/A | Off by Default |
+```
+
+# Prerequisites
+* python3
+* requests
+
+# Installation
+## Clone Repository
+`git clone git@github.com:dolevf/graphw00f.git`
+
+## Run graphw00f
+`python3 main.py -h`
+
+```
+Usage: main.py -h
+
+Options:
+ -h, --help show this help message and exit
+ -r, --noredirect Do not follow redirections given by 3xx responses
+ -t URL, --target=URL target url with the path
+ -o OUTPUT_FILE, --output-file=OUTPUT_FILE
+ Output results to a file (CSV)
+ -l, --list List all GraphQL technologies graphw00f is able to
+ detect
+ -v, --version Print out the current version and exit.
+```
+
+# Example
+```
+python3 main.py -t http://127.0.0.1:5000/graphql
+
++-------------------+ +--------------------+
+| GRAPHQL | | FINGERPRINT |
++-------------------+ +--------------------+
+ ** **
+ *** ***
+ ** **
+ +-------------------+
+ | graphw00f |
+ +-------------------+
+ *** ***
+ ** ***
+ ** **
+ +--------------+ +--------------+
+ | Node X | | Node Y |
+ +--------------+ +--------------+
+ *** ***
+ ** **
+ ** **
+ +------------+
+ | Node Z |
+ +------------+
+
+ graphw00f - v1.0.0
+ The fingerprinting tool for GraphQL
+
+[*] Checking if GraphQL is available at http://127.0.0.1:8088/graphql...
+[*] Found GraphQL.
+[*] Attempting to fingerprint...
+[*] Discovered GraphQL Engine!
+[!] The site https://www.graphql-java.com is using: graphql-java - GraphQL for Java
+[!] Attack Surface Matrix: https://github.com/dolevf/graphw00f/blob/main/docs/graphql-java.md
+[!] Technologies: Java
+[!] Homepage: https://www.graphql-java.com
+[*] Completed.
+```
+
+# Support and Issues
+Any issues with graphw00f such as false/true positives, inaccurate detections, etc. please create a GitHub issue with environment details.
+
+# Resources
+Want to learn more about GraphQL? head over to my other project and hack GraphQL away: [Damn Vulnerable GraphQL Application](https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application/)
diff --git a/conf.py b/conf.py
new file mode 100644
index 0000000..1138bf4
--- /dev/null
+++ b/conf.py
@@ -0,0 +1,7 @@
+# Custom Headers
+# HEADERS = {"User-Agent":"My User Agent"}
+HEADERS = {'User-Agent':'graphw00f'}
+
+# Custom Cookies
+# COOKIES = {"PHPSESS":"DEADBEEF"}
+COOKIES = {}
diff --git a/docs/apollo.md b/docs/apollo.md
new file mode 100644
index 0000000..a0eda0e
--- /dev/null
+++ b/docs/apollo.md
@@ -0,0 +1,18 @@
+# Apollo
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+Apollo Server is a community-maintained open-source GraphQL server. It works with many Node.js HTTP server frameworks, or can run on its own with a built-in Express server. Apollo Server works with any GraphQL schema built with GraphQL.js--or define a schema's type definitions using schema definition language (SDL).
+Apollo uses TypeScript as its language.
+
+# Security Features
+Apollo offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|----------------------------------|----------------------------------|-----------------------------|------------------------------------------------|-------------------------------------------------------------------------------|-----------------|
+| On by Default | Supported via External Libraries | Supported via External Libraries | Supported | Enabled if NODE_ENV is not set to 'production' | exception.stacktrace exists if NODE_ENV is not set to 'production' or 'test' | On by default |
+```
\ No newline at end of file
diff --git a/docs/ariadne.md b/docs/ariadne.md
new file mode 100644
index 0000000..7b5c0a6
--- /dev/null
+++ b/docs/ariadne.md
@@ -0,0 +1,17 @@
+# Ariadne
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+Ariadne is a Python library for implementing GraphQL servers using a schema-first approach.
+
+# Security Features
+Ariadne offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|----------------|-----------------|
+| On by Default | Supported | Supported | No Support | Enabled by Default | Off by Default | No Support |
+```
\ No newline at end of file
diff --git a/docs/gqlgen.md b/docs/gqlgen.md
new file mode 100644
index 0000000..81660ee
--- /dev/null
+++ b/docs/gqlgen.md
@@ -0,0 +1,17 @@
+# gqlgen
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+gqlgen is a Go library for building GraphQL servers without any fuss, based on schema-first approach.
+
+# Security Features
+gqlgen provides the following security features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|---------------|----------------|-----------------|
+| On by Default | No Support | Off by Default | Off by Default | On by Default | Off by Default | Off by Default |
+```
\ No newline at end of file
diff --git a/docs/graphene.md b/docs/graphene.md
new file mode 100644
index 0000000..d361a8f
--- /dev/null
+++ b/docs/graphene.md
@@ -0,0 +1,17 @@
+# Graphene
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+Graphene-Python is a library for building GraphQL APIs in Python easily, its main goal is to provide a simple but extendable API for making developers' lives easier.
+
+# Security Features
+Graphene offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|------------|-----------------|
+| On by Default | No Support | No Support | No Support | Enabled by Default | N/A | Off by Default |
+```
\ No newline at end of file
diff --git a/docs/graphql-go.md b/docs/graphql-go.md
new file mode 100644
index 0000000..b50d538
--- /dev/null
+++ b/docs/graphql-go.md
@@ -0,0 +1,17 @@
+# GraphQL-Go
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+An implementation of GraphQL in Go.
+
+# Security Features
+graphql-go offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|----------------|-----------------|
+| On by Default | No Support | No Support | No Support | Enabled by Default | Off by Default | No Support |
+```
diff --git a/docs/graphql-java.md b/docs/graphql-java.md
new file mode 100644
index 0000000..5d6b475
--- /dev/null
+++ b/docs/graphql-java.md
@@ -0,0 +1,17 @@
+# GraphQL Java
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+The GraphQL Java is an implementation of the GraphQL specification for the Java language.
+
+# Security Features
+GraphQL Java offers the following security features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|------------|-----------------|
+| On by Default | Off by Default | Off by Default | No Support | Enabled by Default | No Support | Off by Default |
+```
\ No newline at end of file
diff --git a/docs/graphql-php.md b/docs/graphql-php.md
new file mode 100644
index 0000000..16f362e
--- /dev/null
+++ b/docs/graphql-php.md
@@ -0,0 +1,17 @@
+# ProductName
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+graphql-php is a PHP implementation of the GraphQL specification.
+
+# Security Features
+graphql-php offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|---------------------------------|---------------------------------|-----------------------------|--------------------|----------------|---------------------------------|
+| On by Default | Supported - Disabled by Default | Supported - Disabled by Default | No Support | Enabled by Default | Off by Default | Supported - Disabled by Default |
+```
\ No newline at end of file
diff --git a/docs/graphqlapiforwp.md b/docs/graphqlapiforwp.md
new file mode 100644
index 0000000..7cca930
--- /dev/null
+++ b/docs/graphqlapiforwp.md
@@ -0,0 +1,25 @@
+# GraphQL API For WordPress
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+GraphQL API For WordPress bring the most powerful GraphQL experience into your WordPress site
+
+# Security Features
+GraphQL API For WordPress provides the followign security features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|---------------|------------|-----------------|
+| On by Default | No Support | No Support | Off by Default | N/A | No Support | No Support |
+```
+
+While GraphQL API for Wordpress does not provide common security mechanisms out of the box, it does provide additional controls:
+
+* Access Control Lists
+* Persisted Queries on custom endpoints
+* Access granularity on schemas
+
+The existence of these features in practice depends on the WordPress Admin, they may or may not be enabled.
\ No newline at end of file
diff --git a/docs/hasura.md b/docs/hasura.md
new file mode 100644
index 0000000..1798fb6
--- /dev/null
+++ b/docs/hasura.md
@@ -0,0 +1,19 @@
+# Hasura
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+The Hasura GraphQL engine makes your data instantly accessible over a real-time GraphQL API, so you can build and ship modern apps and APIs faster. Hasura connects to your databases, REST servers, GraphQL servers, and third party APIs to provide a unified realtime GraphQL API across all your data sources.
+
+# Security Features
+While Hasura Cloud provides some security mechanisms, Hasura API (the non-cloud version) provides a limited set of security features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|---------------|------------|-----------------|
+| On by Default | No Support | No Support | No Support | N/A | No Support | No Support |
+```
+
+Hasura non-cloud provides Access Control Lists options, however, they must be explicitly enabled and used.
\ No newline at end of file
diff --git a/docs/hypergraphql.md b/docs/hypergraphql.md
new file mode 100644
index 0000000..f2f2170
--- /dev/null
+++ b/docs/hypergraphql.md
@@ -0,0 +1,17 @@
+# HyperGraphQL
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+An implementation of GraphQL in Java
+
+# Security Features
+HyperGraphQL offers the following features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|------------|-----------------|
+| No Support | No Support | No Support | No Support | Enabled by Default | No Support | No Support |
+```
diff --git a/docs/ruby-graphql.md b/docs/ruby-graphql.md
new file mode 100644
index 0000000..cf6b180
--- /dev/null
+++ b/docs/ruby-graphql.md
@@ -0,0 +1,17 @@
+# Ruby GraphQL
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+ruby-graphql is a Ruby implementation of the GraphQL specification.
+
+# Security Features
+Ruby GraphQL provides the following security features:
+
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|--------------------|------------|-----------------|
+| On by Default | No Support | Off by Default | Off by Default | Enabled by Default | No Support | On by Default |
+```
\ No newline at end of file
diff --git a/docs/templ.md b/docs/templ.md
new file mode 100644
index 0000000..39bb073
--- /dev/null
+++ b/docs/templ.md
@@ -0,0 +1,9 @@
+# ProductName
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+
+# Security Features
\ No newline at end of file
diff --git a/docs/wpgraphql.md b/docs/wpgraphql.md
new file mode 100644
index 0000000..928ea2e
--- /dev/null
+++ b/docs/wpgraphql.md
@@ -0,0 +1,16 @@
+# WPGraphQL
+
+# Table of Contents
+* [About](#About)
+* [Security Features](#Security-Features)
+
+# About
+WPGraphQL is a WordPress plugin which provides a WordPress instance with immediate GraphQL API support.
+
+# Security Features
+WPGraphQL offers the following security features:
+```
+| Field Suggestions | Query Depth Limit | Query Cost Analysis | Automatic Persisted Queries | Introspection | Debug Mode | Batch Requests |
+|-------------------|-------------------|---------------------|-----------------------------|----------------|----------------|-----------------|
+| On by Default | Off by Default | No Support | No Support | Off by Default | Off by Default | On by Default |
+```
\ No newline at end of file
diff --git a/graphw00f/helpers.py b/graphw00f/helpers.py
new file mode 100644
index 0000000..6bdab62
--- /dev/null
+++ b/graphw00f/helpers.py
@@ -0,0 +1,133 @@
+
+import datetime
+from urllib.parse import urlparse
+from version import VERSION
+
+class bcolors:
+ OKBLUE = '\033[94m'
+ OKCYAN = '\033[96m'
+ OKGREEN = '\033[92m'
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+
+def error_contains(response, word_to_match):
+ if isinstance(response, dict):
+ if response.get('errors'):
+ for i in response['errors']:
+ err_message = i.get('message', '')
+ if word_to_match in err_message:
+ return True
+ return False
+
+def get_time():
+ return datetime.datetime.now().strftime('%Y-%m-%d')
+
+def draw_art():
+ return '''
++-------------------+ +--------------------+
+| GRAPHQL | | FINGERPRINT |
++-------------------+ +--------------------+
+ ** **
+ *** ***
+ ** **
+ +-------------------+
+ | graphw00f |
+ +-------------------+
+ *** ***
+ ** ***
+ ** **
+ +--------------+ +--------------+
+ | Node X | | Node Y |
+ +--------------+ +--------------+
+ *** ***
+ ** **
+ ** **
+ +------------+
+ | Node Z |
+ +------------+
+
+ graphw00f - v{version}
+ The fingerprinting tool for GraphQL
+ '''.format(version=VERSION)
+
+def get_engines():
+ return {
+ 'apollo':{
+ 'name':'Apollo',
+ 'url':'https://www.apollographql.com',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/apollo.md',
+ 'technology':['JavaScript', 'Node.js', 'TypeScript']
+ },
+ 'graphene':{
+ 'name':'Graphene',
+ 'url':'https://graphene-python.org',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/graphene.md',
+ 'technology':['Python']
+ },
+ 'hasura':{
+ 'name':'Hasura',
+ 'url':'https://hasura.io',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/hasura.md',
+ 'technology':['Haskell']
+ },
+ 'graphql-php':{
+ 'name':'GraphQL PHP',
+ 'url':'https://webonyx.github.io/graphql-php',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/graphql-php.md',
+ 'technology':['PHP']
+ },
+ 'ruby-graphql':{
+ 'name':'Ruby GraphQL',
+ 'url':'https://graphql-ruby.org',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/ruby-graphql.md',
+ 'technology':['Ruby']
+ },
+ 'hypergraphql':{
+ 'name':'HyperGraphQL',
+ 'url':'https://www.hypergraphql.org',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/hypergraphql.md',
+ 'technology':['Java']
+ },
+ 'ariadne':{
+ 'name':'Ariadne',
+ 'url':'https://ariadnegraphql.org',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/ariadne.md',
+ 'technology':['Python']
+ },
+ 'graphql-api-for-wp':{
+ 'name':'GraphQL API for Wordpress',
+ 'url':'https://graphql-api.com',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/graphqlapiforwp.md',
+ 'technology':['PHP'],
+ },
+ 'wpgraphql':{
+ 'name':'WPGraphQL WordPress Plugin',
+ 'url':'https://www.wpgraphql.com',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/wpgraphql.md',
+ 'technology':['PHP']
+ },
+ 'gqlgen':{
+ 'name':'gqlgen - GraphQL for Go',
+ 'url':'https://gqlgen.com',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/gqlgen.md',
+ 'technology':['Go']
+ },
+ 'graphql-go':{
+ 'name':'graphql-go -GraphQL for Go',
+ 'url':'https://github.com/graphql-go/graphql',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/graphql-go.md',
+ 'technology':['Go']
+ },
+ 'graphql-java':{
+ 'name':'graphql-java - GraphQL for Java',
+ 'url':'https://www.graphql-java.com',
+ 'ref':'https://github.com/dolevf/graphw00f/blob/main/docs/graphql-java.md',
+ 'technology':['Java']
+ }
+ }
+
+def user_confirmed(choice):
+ if choice in ('yes', 'y'):
+ return True
+ return False
diff --git a/graphw00f/lib.py b/graphw00f/lib.py
new file mode 100644
index 0000000..a78512a
--- /dev/null
+++ b/graphw00f/lib.py
@@ -0,0 +1,429 @@
+import requests
+
+from graphw00f.helpers import error_contains
+
+requests.packages.urllib3.disable_warnings()
+
+class GraphQLDetectionFailed(Exception):
+ pass
+
+class GraphQLUnknownState(Exception):
+ pass
+
+class GraphQLError(Exception):
+ pass
+
+class GRAPHW00F:
+ def __init__(self, headers,
+ cookies,
+ follow_redirects=False):
+ self.url = 'http://example.com'
+ self.cookies = cookies
+ self.headers = headers
+ self.follow_redirects = follow_redirects
+
+ def check(self, url):
+ query = '''
+ query {
+ __typename
+ }
+ '''
+ response = self.graph_query(url, payload=query)
+ if response.get('data', {}).get('__typename', '') in ('Query', 'QueryRoot', 'query_root'):
+ return True
+ elif response.get('errors') and any('locations' in i for i in response['errors']):
+ return True
+ elif response.get('data'):
+ return True
+ else:
+ raise GraphQLDetectionFailed
+ raise GraphQLUnknownState
+
+ def execute(self, url):
+ self.url = url
+ if self.engine_graphene():
+ return 'graphene'
+ elif self.engine_ariadne():
+ return 'ariadne'
+ elif self.engine_apollo():
+ return 'apollo'
+ elif self.engine_hasura():
+ return 'hasura'
+ elif self.engine_wpgraphql():
+ return 'wpgraphql'
+ elif self.engine_graphqlapiforwp():
+ return 'graphql-api-for-wp'
+ elif self.engine_graphqljava():
+ return 'graphql-java'
+ elif self.engine_hypergraphql():
+ return 'hypergraphql'
+ elif self.engine_ruby():
+ return 'ruby-graphql'
+ elif self.engine_graphqlphp():
+ return 'graphql-php'
+ elif self.engine_gqlgen():
+ return 'gqlgen'
+ elif self.engine_graphqlgo():
+ return 'graphql-go'
+ return None
+
+ def graph_query(self, url, operation='query', payload={}):
+ try:
+ response = requests.post(url,
+ headers=self.headers,
+ cookies=self.cookies,
+ verify=False,
+ allow_redirects=self.follow_redirects,
+ json={operation:payload})
+ return response.json()
+ except GraphQLError:
+ return {}
+ except:
+ return {}
+
+ def engine_apollo(self):
+ query = '''
+ query @skip {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Directive "@skip" argument "if" of type "Boolean!" is required, but it was not provided.'):
+ return True
+
+ query = '''
+ query @deprecated {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Directive "@deprecated" may not be used on QUERY.'):
+ return True
+
+ def engine_graphene(self):
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Must provide query string.'):
+ return True
+
+ query = '''aaa'''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Syntax Error GraphQL (1:1)'):
+ return True
+
+ return False
+
+ def engine_hasura(self):
+ query = '''
+ query @cached {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if response.get('data'):
+ if response.get('data', {}).get('__typename', '') == 'query_root':
+ return True
+
+ query = '''
+ query {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'missing selection set for "__Schema"'):
+ return True
+
+ query = '''
+ query {
+ aa
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'field "aaa" not found in type: \'query_root\''):
+ return True
+
+ query = '''
+ query @skip {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'directive "skip" is not allowed on a query'):
+ return True
+
+ return False
+
+ def engine_graphqlphp(self):
+ query = '''
+ query ! {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Syntax Error: Cannot parse the unexpected character "?".'):
+ return True
+
+ query = '''
+ subscription {
+ s
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Schema is not configured for subscriptions.'):
+ return True
+
+ return False
+
+ def engine_ruby(self):
+ query = '''
+ query @skip {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, '\'@skip\' can\'t be applied to queries (allowed: fields, fragment spreads, inline fragments)'):
+ return True
+ elif error_contains(response, 'Directive \'skip\' is missing required arguments: if'):
+ return True
+
+ query = '''
+ query @deprecated {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, '\'@deprecated\' can\'t be applied to queries'):
+ return True
+
+ query = '''
+ query aa@aa {
+ __schema {
+ directives {
+ description
+ }
+ }
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Directive @aa is not defined'):
+ return True
+
+ query = '''
+ query {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Field must have selections (field \'__schema\' returns __Schema but has no selections.'):
+ return True
+
+ return False
+
+ def engine_hypergraphql(self):
+ query = '''
+ zzz {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Validation error of type InvalidSyntax: Invalid query syntax.'):
+ return True
+
+ query = '''
+ query {
+ __schema {
+ directives {
+ descriptio
+ }
+ }
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if response.get('errors'):
+ for i in response['errors']:
+ qp = i.get('queryPath', [])
+ matches = ['__schema', 'directives', 'descriptio']
+ if qp == matches:
+ return True
+
+ return False
+
+ def engine_graphqljava(self):
+ query = '''
+ queryy {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Invalid Syntax : offending token \'queryy\''):
+ return True
+
+ query = '''
+ query @aaa@aaa {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Validation error of type DuplicateDirectiveName: Directives must be uniquely named within a location.'):
+ return True
+
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Invalid Syntax : offending token \'\''):
+ return True
+
+ return False
+
+ def engine_ariadne(self):
+ query = '''
+ query {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Field \'__schema\' of type \'__Schema!\' must have a selection of subfields.'):
+ return True
+
+ query = '''
+ subscription {
+ s
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Could not connect to websocket endpoint'):
+ return True
+
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'The query must be a string.'):
+ return True
+
+ return False
+
+ def engine_graphqlapiforwp(self):
+ query = '''
+ query {
+ alias1$1:__schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if response.get('data'):
+ if response.get('data').get('alias1$1', '') == 'schema':
+ return True
+
+ query = '''query aa#aa { __typename }'''
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'Unexpected token "END"'):
+ return True
+
+ query = '''
+ query @skip {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Argument \'if\' cannot be empty, so directive \'skip\' has been ignored'):
+ return True
+
+ query = '''
+ query @doesnotexist {
+ __typename
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'No DirectiveResolver resolves directive with name \'doesnotexist\''):
+ return True
+
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'The query in the body is empty'):
+ return True
+
+ return False
+
+ def engine_wpgraphql(self):
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'GraphQL Request must include at least one of those two parameters: "query" or "queryId"'):
+ return True
+
+ query = '''
+ query {
+ alias1$1:__schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if not error_contains(response, 'Syntax Error: Expected Name, found $'):
+ return False
+
+ try:
+ debug_msg = response['extensions']['debug'][0]
+ if debug_msg['type'] == 'DEBUG_LOGS_INACTIVE' or \
+ debug_msg['message'] == 'GraphQL Debug logging is not active. To see debug logs, GRAPHQL_DEBUG must be enabled.':
+ return True
+ except KeyError:
+ pass
+
+ return False
+
+ def engine_gqlgen(self):
+ query = '''
+ query @aa@aa {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'The directive "aa" can only be used once at this location.'):
+ return True
+
+ query = '''
+ queryyy {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Unexpected Name "queryyy"'):
+ return True
+ return False
+
+ def engine_graphqlgo(self):
+ query = '''
+ query @skip {
+ abc
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+ if error_contains(response, 'Directive "skip" may not be used on QUERY. Directive "@skip" argument "if" of type "Boolean!" is required but not provided'):
+ return True
+
+ query = ''
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'Must provide an operation.'):
+ return True
+
+ query = '''
+ query ? {
+ __schema
+ }
+ '''
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'Unexpected character "?"'):
+ return True
+
+ query = '''
+ query ? {
+ __schema
+ }
+ '''
+
+ response = self.graph_query(self.url, payload=query)
+
+ if error_contains(response, 'Syntax Error GraphQL request'):
+ return True
+
+ return False
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..9167e48
--- /dev/null
+++ b/main.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import sys
+import conf
+
+from graphw00f.helpers import (
+ get_time,
+ draw_art,
+ get_engines,
+ user_confirmed,
+ bcolors
+)
+from time import sleep
+from urllib.parse import urlparse
+from optparse import OptionParser
+
+from version import VERSION
+from graphw00f.lib import (
+ GRAPHW00F,
+ GraphQLDetectionFailed,
+ GraphQLUnknownState
+)
+
+
+def main():
+ parser = OptionParser(usage='%prog -t http://example.com/graphql')
+ parser.add_option('-r', '--noredirect', action='store_false', dest='followredirect', default=True,
+
+ help='Do not follow redirections given by 3xx responses')
+ parser.add_option('-t', '--target', dest='url', help='target url with the path')
+ parser.add_option('-o', '--output-file', dest='output_file',
+ help='Output results to a file (CSV)', default=None)
+ parser.add_option('-l', '--list', dest='list', action='store_true', default=False,
+ help='List all GraphQL technologies graphw00f is able to detect')
+ parser.add_option('--version', '-v', dest='version', action='store_true', default=False,
+ help='Print out the current version and exit.')
+ options, args = parser.parse_args()
+
+ if options.list:
+ print(draw_art())
+ for k, v in graphw00f.helpers.get_engines().items():
+ print('{key}: {name} ({language})'.format(
+ key=k,
+ name=v['name'],
+ language=', '.join(v['language']))
+ )
+ sys.exit(0)
+
+ if options.version:
+ print('version:', VERSION)
+ sys.exit(0)
+
+ if not options.url:
+ parser.print_help()
+ sys.exit(1)
+
+ url = options.url
+ url_path = urlparse(url).path
+ url_scheme = urlparse(url).scheme
+ url_netloc = urlparse(url).netloc
+
+ print(draw_art())
+
+ if url_scheme not in ('http', 'https'):
+ print('URL is missing a scheme (http|https)')
+ sys.exit(1)
+
+ if not url_netloc:
+ print('url {url} does not seem right.'.format(url=url))
+ sys.exit(1)
+
+ if not url_path:
+ print('[*] No URL path was provided.')
+ print('[*[ are you sure you want to fingerprint the server without a path? [y/n]')
+ choice = input().lower()
+ if not user_confirmed(choice):
+ sys.exit(1)
+
+
+ print('[*] Checking if GraphQL is available at {url}...'.format(url=url))
+
+ g = GRAPHW00F(follow_redirects=options.followredirect,
+ headers=conf.HEADERS,
+ cookies=conf.COOKIES)
+ detected = None
+ try:
+ if g.check(url):
+ print('[*] Found GraphQL.')
+ except GraphQLDetectionFailed:
+ print(bcolors.FAIL + '[x] Could not determine existence of GraphQL (GraphQLDetectionFailed)' + bcolors.ENDC)
+ print('[*] Continue anyway? [y/n]'.format(url=url))
+ choice = input().lower()
+ if not user_confirmed(choice):
+ print('Quitting.')
+ sys.exit(1)
+ except GraphQLUnknownState:
+ print('Something went wrong.')
+ sys.exit(1)
+
+ print('[*] Attempting to fingerprint...')
+ result = g.execute(url)
+
+ if result:
+ name = get_engines()[result]['name']
+ url = get_engines()[result]['url']
+ ref = get_engines()[result]['ref']
+ technologies = ', '.join(get_engines()[result]['technology'])
+ detected = name
+ print(bcolors.OKGREEN + '[*] Discovered GraphQL Engine!')
+ print('[!] The site {} is using: {}'.format(url, name))
+ print('[!] Attack Surface Matrix: {}'.format(ref))
+ print('[!] Technologies: {}'.format(technologies))
+ print('[!] Homepage: {}'.format(url))
+ else:
+ print('[x] Nothing was found :-(')
+
+ if options.output_file:
+ f = open(options.output_file, 'w')
+ f.write('url,detected_engine,timestamp\n')
+ f.write('{},{},{}\n'.format(url_netloc, detected, get_time()))
+ f.close()
+
+ print('[*] Completed.')
+
+if __name__ == '__main__':
+ main()
+
\ No newline at end of file
diff --git a/static/graphw00f.png b/static/graphw00f.png
new file mode 100644
index 0000000..1fd8057
Binary files /dev/null and b/static/graphw00f.png differ
diff --git a/version.py b/version.py
new file mode 100644
index 0000000..1dea037
--- /dev/null
+++ b/version.py
@@ -0,0 +1 @@
+VERSION = '1.0.1'