Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lot of fixes, improvements etc... #66

Merged
merged 98 commits into from
Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
c4c363c
feat: fork for pypi
iCarossio Feb 9, 2023
1d3a7cb
fix: handle json decode errors
Feb 9, 2023
3466333
fix: error not defined
Feb 9, 2023
87f9ddf
feat: jmiroosh feature
QuentinN42 Feb 9, 2023
9f27142
Merge pull request #3 from Escape-Technologies/feat/jamboro-duplicate…
QuentinN42 Feb 9, 2023
31651c2
feat: duplicate query regex
QuentinN42 Feb 9, 2023
200f43b
fix: dockerhub token
Feb 9, 2023
2e97088
feat: proxy
QuentinN42 Feb 13, 2023
dedf6e9
feat: ignore error
QuentinN42 Feb 13, 2023
038c0f4
feat: add max retry param
QuentinN42 Feb 13, 2023
296d2d7
Merge pull request #5 from Escape-Technologies/feat/proxy
QuentinN42 Feb 14, 2023
e5474a4
fix: input object heuristics
iCarossio Feb 14, 2023
0d2365b
fix: typing context
iCarossio Feb 14, 2023
6918b0c
fix: input object for argmuments
nohehf Feb 14, 2023
02c6ea5
fix: double regex
iCarossio Feb 14, 2023
7e9b65f
Merge pull request #7 from Escape-Technologies/fix/double_regexes
iCarossio Feb 14, 2023
649eb83
fix: new field regex
iCarossio Feb 14, 2023
daad6b4
Merge branch 'main' of github.com:Escape-Technologies/clairvoyance in…
iCarossio Feb 14, 2023
6734c72
fix: did you mean
iCarossio Feb 14, 2023
472d8b1
fix: did you mean
iCarossio Feb 14, 2023
16ff371
fix: tests
iCarossio Feb 14, 2023
f6d2a0a
fix: tests
iCarossio Feb 14, 2023
db700a2
fix: typename regex
iCarossio Feb 14, 2023
3b66667
feat: skip regex
iCarossio Feb 14, 2023
2ba11b5
feat: fix regex
iCarossio Feb 14, 2023
80668bf
fix: was not provided regex
iCarossio Feb 14, 2023
594a34f
feat: dot in field
iCarossio Feb 14, 2023
7b45479
Merge pull request #8 from Escape-Technologies/fix/double_regexes
iCarossio Feb 14, 2023
f5fa684
feat: add backoff
QuentinN42 Feb 14, 2023
e53d931
Merge pull request #9 from Escape-Technologies/feat/bed
QuentinN42 Feb 14, 2023
87d6d94
feat: multiple did you mean
iCarossio Feb 14, 2023
7cc9a5d
fix: better regexps
iCarossio Feb 14, 2023
8b3f0fe
feat: move to triple quotes
iCarossio Feb 14, 2023
827b16d
feat: move to triple quotes
iCarossio Feb 14, 2023
d29ee85
feat: move to triple quotes
iCarossio Feb 14, 2023
e3debd8
feat: move to triple quotes
iCarossio Feb 14, 2023
7433fec
feat: profiles
QuentinN42 Feb 14, 2023
15b21cd
feat: field regexes above
iCarossio Feb 14, 2023
5f3cfa7
feat: triple quote
iCarossio Feb 14, 2023
4abd3a1
feat: compile arg regexes
iCarossio Feb 14, 2023
4e9851f
feat: compile typeref regexes
iCarossio Feb 14, 2023
4f00dd2
Merge branch 'main' of github.com:Escape-Technologies/clairvoyance in…
iCarossio Feb 14, 2023
4adeb28
--wip-- [skip ci]
iCarossio Feb 14, 2023
40eb539
--wip-- [skip ci]
iCarossio Feb 14, 2023
ba82658
fix: final regexes
iCarossio Feb 14, 2023
36a0217
fix: remove print
iCarossio Feb 14, 2023
abac515
docs: add help message to the doc
QuentinN42 Feb 14, 2023
91dda68
feat: V2.3
QuentinN42 Feb 14, 2023
ac00768
feat: @jamboro fixes issue 11
QuentinN42 Feb 14, 2023
68e6901
feat: retro compat
QuentinN42 Feb 14, 2023
cc5a811
feat: update python
QuentinN42 Feb 14, 2023
e1c7140
feat: lookup in stderr
QuentinN42 Feb 14, 2023
6d29060
Merge pull request #4 from Escape-Technologies/feat/wordlist-conforms…
QuentinN42 Feb 14, 2023
f842156
feat: ignore aiohttp warns
QuentinN42 Feb 14, 2023
a3e4525
chore: v2.3.1
QuentinN42 Feb 14, 2023
3929cad
Merge branch 'main' of github.com:Escape-Technologies/clairvoyance in…
iCarossio Feb 14, 2023
5f2c58c
fix: better did you mean for type field
iCarossio Feb 14, 2023
166b6fd
feat: did you mean skip field regex
iCarossio Feb 14, 2023
36989c9
fix: clairvoyance
iCarossio Feb 14, 2023
a434478
fix: inline fragments regexps
iCarossio Feb 14, 2023
76ee5eb
fix: inline fragments regexps
iCarossio Feb 14, 2023
c365e99
feat: skip ever useless error message
iCarossio Feb 15, 2023
9256728
fix: print context
iCarossio Feb 15, 2023
3705fe9
fix: input object Input
iCarossio Feb 15, 2023
3fe9831
fix: naming
iCarossio Feb 15, 2023
05d2dca
Merge pull request #11 from Escape-Technologies/feat/general-skip
iCarossio Feb 15, 2023
71afe22
feat: v2.4.0
QuentinN42 Feb 15, 2023
ef36d15
fix: unknown-message-log-error
nohehf Feb 15, 2023
ee43bf5
Merge pull request #13 from Escape-Technologies/fix/unknown-message-l…
iCarossio Feb 15, 2023
c9fb391
fix: input flagh
nohehf Feb 15, 2023
a56bae9
Merge pull request #14 from Escape-Technologies/fix/input-flag
nohehf Feb 15, 2023
0e503f4
fix: reccursive schemas loops
nohehf Feb 15, 2023
635dca5
chore: remove unused import
nohehf Feb 15, 2023
d7e7e63
Merge pull request #15 from Escape-Technologies/fix/reccursive-schema…
QuentinN42 Feb 16, 2023
f0270b8
chore: v2.4.1
QuentinN42 Feb 16, 2023
b5e8c71
feat: disable ssl verification
nohehf Feb 20, 2023
b4905e3
chore: update readme
nohehf Feb 20, 2023
94806df
Merge pull request #18 from Escape-Technologies/feat/disable-ssl-veri…
nohehf Feb 20, 2023
df6b6d2
fix: don't crash when no field suggestion
iCarossio Feb 21, 2023
cdf0076
docs: update readme
QuentinN42 Feb 22, 2023
200f686
feat: some rich progress bars
QuentinN42 Feb 22, 2023
099fba1
fix: use pathlib to work with any OS
QuentinN42 Feb 22, 2023
79ccf99
fix: updated an uniform tasks
QuentinN42 Feb 22, 2023
0c4902e
fix: optional progress
QuentinN42 Feb 22, 2023
56e3e12
Merge pull request #22 from Escape-Technologies/feat/tqdm
QuentinN42 Feb 27, 2023
4801d49
Merge pull request #23 from Escape-Technologies/feat/pathlib
QuentinN42 Feb 27, 2023
c2d86eb
feat: v2.5.0
QuentinN42 Feb 27, 2023
f98770f
refactor: allow over python 3.11
Mar 2, 2023
4e989bf
chore: typo
QuentinN42 Mar 15, 2023
2c6cc7e
Revert "fix: dockerhub token"
QuentinN42 Mar 15, 2023
c4f539d
feat: update debugger config
QuentinN42 Mar 15, 2023
fe18556
feat: revert one line
QuentinN42 Mar 15, 2023
81cfb79
fix: call super in the __init__
QuentinN42 Mar 15, 2023
c8ba3b3
fix: drop warluses
QuentinN42 Mar 15, 2023
26dc819
docs: move comments
QuentinN42 Mar 15, 2023
ac1018d
feat: fix typing
QuentinN42 Mar 15, 2023
924e286
feat: restore main project name
QuentinN42 Mar 15, 2023
56c8b22
fix: Closing angle bracket
QuentinN42 Mar 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install poetry
Expand All @@ -47,7 +49,9 @@ jobs:

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install poetry
Expand Down Expand Up @@ -89,7 +93,9 @@ jobs:
if: false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install poetry
Expand Down
37 changes: 33 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,42 @@
"cwd": "${workspaceFolder}",
"module": "clairvoyance",
"args": [
"-o", "/tmp/t.json",
"-w", "tests/data/wordlist-for-apollo-server.txt",
"http://localhost:4000", "--verbose"
"-o",
"/tmp/t.json",
"-w",
"tests/data/wordlist-for-apollo-server.txt",
"http://localhost:4000",
"--verbose"
],
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Clairvoyance on localhost:4000",
"type": "python",
"request": "launch",
"cwd": "${workspaceFolder}",
"module": "clairvoyance",
"args": [
"-o",
"target/t.json",
"http://localhost:4000",
"--verbose"
],
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Clairvoyance on rick and morty GQL application",
"type": "python",
"request": "launch",
"cwd": "${workspaceFolder}",
"module": "clairvoyance",
"args": [
"https://rickandmortyapi.com/graphql"
],
"console": "integratedTerminal",
"justMyCode": true
}

]
}
70 changes: 31 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,48 @@
# clairvoyance
# Clairvoyance

Some GraphQL APIs have disabled introspection. For example, [Apollo Server disables introspection automatically if the `NODE_ENV` environment variable is set to `production`](https://www.apollographql.com/docs/tutorial/schema/#explore-your-schema).

Clairvoyance allows us to get GraphQL API schema when introspection is disabled. It produces schema in JSON format suitable for other tools like [GraphQL Voyager](https://github.com/APIs-guru/graphql-voyager), [InQL](https://github.com/doyensec/inql) or [graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum).
Obtain GraphQL API Schema even if the introspection is disabled.

## Acknowledgments
[![PyPI](https://img.shields.io/pypi/v/clairvoyance)](https://pypi.org/project/clairvoyance/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/clairvoyance)](https://pypi.org/project/clairvoyance/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/clairvoyance)](https://pypi.org/project/clairvoyance/)
[![GitHub](https://img.shields.io/github/license/nikitastupin/clairvoyance)](https://github.com/nikitastupin/clairvoyance/blob/main/LICENSE)

Thanks to [Swan](https://github.com/c3b5aw) from [Escape-Technologies](https://github.com/Escape-Technologies) for 2.0 version.
## Introduction

## Usage
Some GraphQL APIs have disabled introspection. For example, [Apollo Server disables introspection automatically if the `NODE_ENV` environment variable is set to `production`](https://www.apollographql.com/docs/tutorial/schema/#explore-your-schema).

You may find more details on how the tool works in the second half of the [GraphQL APIs from bug hunter's perspective by Nikita Stupin](https://youtu.be/nPB8o0cSnvM) talk.
Clairvoyance allows us to get GraphQL API schema when introspection is disabled. It produces schema in JSON format suitable for other tools like [GraphQL Voyager](https://github.com/APIs-guru/graphql-voyager), [InQL](https://github.com/doyensec/inql) or [graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum).

### From PyPI
## Contributors

```bash
pip install clairvoyance
```
Thanks to the [contributors](#contributors) for their work.

### From Python interpreter
- [nikitastupin](https://github.com/nikitastupin)
- [Escape](https://escape.tech) team :
- [iCarossio](https://github.com/iCarossio)
- [Swan](https://github.com/c3b5aw)
- [QuentinN42](https://github.com/QuentinN42)
- [Nohehf](https://github.com/Nohehf)
- [i-tsaturov](https://github.com/i-tsaturov)
- [EONRaider](https://github.com/EONRaider)
- [noraj](https://github.com/noraj)
- [belane](https://github.com/belane)

```bash
git clone https://github.com/nikitastupin/clairvoyance.git
cd clairvoyance
pip install poetry
poetry config virtualenvs.in-project true
poetry install --no-dev
source .venv/bin/activate
```
## Getting started

```bash
python3 -m clairvoyance --help
```

```bash
python3 -m clairvoyance -o /path/to/schema.json https://swapi-graphql.netlify.app/.netlify/functions/index
pip install clairvoyance
clairvoyance https://rickandmortyapi.com/graphql -o schema.json
# should take about 2 minute
```

### From Docker Image
## Docker Image

```bash
docker run --rm nikitastupin/clairvoyance --help
```

```bash
# Assuming the wordlist.txt file is found in $PWD
docker run --rm -v $(pwd):/tmp/ nikitastupin/clairvoyance -vv -o /tmp/schema.json -w /tmp/wordlist.txt https://swapi-graphql.netlify.app/.netlify/functions/index
```

### From BlackArch Linux

> NOTE: this distribution is supported by a third-party (i.e. not by the mainainters of clairvoyance)

```bash
pacman -S clairvoyance
```
Comment on lines -51 to -57
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind bringing this back? Any arguments against this?

## Advanced Usage

### Which wordlist should I use?

Expand All @@ -80,3 +68,7 @@ In case of question or issue with clairvoyance please refer to [wiki](https://gi
## Contributing

Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change. For more information about tests, internal project structure and so on refer to [Development](https://github.com/nikitastupin/clairvoyance/wiki/Development) wiki page.

## Documentation

- You may find more details on how the tool works in the second half of the [GraphQL APIs from bug hunter's perspective by Nikita Stupin](https://youtu.be/nPB8o0cSnvM) talk.
39 changes: 37 additions & 2 deletions clairvoyance/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import asyncio
import json
import logging
import re
import sys
import os
from pathlib import Path
from typing import Dict, List, Optional

from clairvoyance import graphql, oracle
Expand All @@ -18,6 +19,10 @@ def setup_context(
logger: logging.Logger,
headers: Optional[Dict[str, str]] = None,
concurrent_requests: Optional[int] = None,
proxy: Optional[str] = None,
max_retries: Optional[int] = None,
backoff: Optional[int] = None,
disable_ssl_verify: Optional[bool] = None,
) -> None:
"""Initialize objects and freeze them into the context."""

Expand All @@ -26,12 +31,16 @@ def setup_context(
url,
headers=headers,
concurrent_requests=concurrent_requests,
proxy=proxy,
max_retries=max_retries,
backoff=backoff,
disable_ssl_verify=disable_ssl_verify,
)
logger_ctx.set(logger)


def load_default_wordlist() -> List[str]:
wl = os.path.join(os.path.dirname(__file__), 'wordlist.txt')
wl = Path(__file__).parent / 'wordlist.txt'
with open(wl, 'r', encoding='utf-8') as f:
return [w.strip() for w in f.readlines() if w.strip()]

Expand All @@ -45,6 +54,10 @@ async def blind_introspection(
input_document: Optional[str] = None,
input_schema_path: Optional[str] = None,
output_path: Optional[str] = None,
proxy: Optional[str] = None,
max_retries: Optional[int] = None,
backoff: Optional[int] = None,
disable_ssl_verify: Optional[bool] = None,
) -> str:
wordlist = wordlist or load_default_wordlist()
assert wordlist, 'No wordlist provided'
Expand All @@ -54,6 +67,10 @@ async def blind_introspection(
logger=logger,
headers=headers,
concurrent_requests=concurrent_requests,
proxy=proxy,
max_retries=max_retries,
backoff=backoff,
disable_ssl_verify=disable_ssl_verify,
)

logger.info(f'Starting blind introspection on {url}...')
Expand All @@ -65,7 +82,10 @@ async def blind_introspection(

input_document = input_document or 'query { FUZZ }'
ignored = set(e.value for e in GraphQLPrimitive)
iterations = 1
while True:
logger.info(f'Iteration {iterations}')
iterations += 1
schema = await oracle.clairvoyance(
wordlist,
input_document=input_document,
Expand Down Expand Up @@ -107,6 +127,17 @@ def cli(argv: Optional[List[str]] = None) -> None:
wordlist = []
if args.wordlist:
wordlist = [w.strip() for w in args.wordlist.readlines() if w.strip()]
# de-dupe the wordlist.
wordlist = list(set(wordlist))

# remove wordlist items that don't conform to graphQL regex github-issue #11
if args.validate:
wordlist_parsed = [w for w in wordlist if re.match(r'[_A-Za-z][_0-9A-Za-z]*', w)]
logging.info(
f'Removed {len(wordlist) - len(wordlist_parsed)} items from Wordlist, to conform to name regex. '
f'https://spec.graphql.org/June2018/#sec-Names'
)
wordlist = wordlist_parsed

asyncio.run(
blind_introspection(
Expand All @@ -118,5 +149,9 @@ def cli(argv: Optional[List[str]] = None) -> None:
input_schema_path=args.input_schema,
output_path=args.output,
wordlist=wordlist,
proxy=args.proxy,
max_retries=args.max_retries,
backoff=args.backoff,
disable_ssl_verify=args.no_ssl,
)
)
21 changes: 19 additions & 2 deletions clairvoyance/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import json
from typing import Dict, Optional

import aiohttp
Expand All @@ -8,20 +9,26 @@


class Client(IClient):

def __init__(
self,
url: str,
max_retries: Optional[int] = None,
headers: Optional[Dict[str, str]] = None,
concurrent_requests: Optional[int] = None,
proxy: Optional[str] = None,
backoff: Optional[int] = None,
disable_ssl_verify: Optional[bool] = None,
) -> None:
self._url = url
self._session = None

self._headers = headers or {}
self._max_retries = max_retries or 3
self._semaphore = asyncio.Semaphore(concurrent_requests or 50)
self.proxy = proxy
self.backoff = backoff
self._backoff_semaphore = asyncio.Lock()
self.disable_ssl_verify = disable_ssl_verify or False

client_ctx.set(self)

Expand All @@ -37,7 +44,8 @@ async def post(

async with self._semaphore:
if not self._session:
self._session = aiohttp.ClientSession(headers=self._headers)
connector = aiohttp.TCPConnector(verify_ssl=(not self.disable_ssl_verify))
self._session = aiohttp.ClientSession(headers=self._headers, connector=connector)

# Translate an existing document into a GraphQL request.
gql_document = {'query': document} if document else None
Expand All @@ -46,6 +54,7 @@ async def post(
response = await self._session.post(
self._url,
json=gql_document,
proxy=self.proxy,
)

if response.status >= 500:
Expand All @@ -57,9 +66,17 @@ async def post(
except (
aiohttp.client_exceptions.ClientConnectionError,
aiohttp.client_exceptions.ClientPayloadError,
asyncio.TimeoutError,
json.decoder.JSONDecodeError,
) as e:
log().warning(f'Error posting to {self._url}: {e}')

if self.backoff:
async with self._backoff_semaphore:
delay = 0.5 * self.backoff**retries
log().debug(f'Waiting for backoff {delay} seconds.')
await asyncio.sleep(delay)

return await self.post(document, retries + 1)

async def close(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion clairvoyance/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

# pylint: disable=too-few-public-methods
class Config(IConfig):

def __init__(self) -> None:
super().__init__()
self._bucket_size: int = 64

config_ctx.set(self)
1 change: 0 additions & 1 deletion clairvoyance/entities/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class IConfig(ABC):

_bucket_size: int

@property
Expand Down
1 change: 0 additions & 1 deletion clairvoyance/entities/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@


class MetaEnum(EnumMeta):

"""Meta class for Enum."""

# pylint: disable=no-value-for-parameter
Expand Down
9 changes: 9 additions & 0 deletions clairvoyance/entities/oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Oracle definitions."""

from enum import Enum


class FuzzingContext(str, Enum):
"""Contexts."""
ARGUMENT = 'InputValue'
FIELD = 'Field'
2 changes: 0 additions & 2 deletions clairvoyance/entities/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

@unique
class GraphQLPrimitive(str, Enum, metaclass=MetaEnum):

"""The default GraphQL Scalar primitives.

ref: https://spec.graphql.org/draft/#sec-Input-Values
Expand All @@ -22,7 +21,6 @@ class GraphQLPrimitive(str, Enum, metaclass=MetaEnum):

@unique
class GraphQLKind(str, Enum, metaclass=MetaEnum):

"""The default GraphQL kinds.

ref: https://spec.graphql.org/draft/#sec-Types
Expand Down
Loading