From 5380603295d6380a074434ad9433430a32ae169c Mon Sep 17 00:00:00 2001 From: andreachild Date: Tue, 15 Oct 2024 09:28:48 -0700 Subject: [PATCH] Changed neptune queries to use parameters when able to prevent open cypher query injection. Added placeholder function for sanitization of query text that cannot be parameterized (such as node and edge labels in match clause). Sanitization logic is still to be determined after consultation with AWS. --- src/NeptuneSchema.js | 58 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/NeptuneSchema.js b/src/NeptuneSchema.js index e139e0e..7970097 100644 --- a/src/NeptuneSchema.js +++ b/src/NeptuneSchema.js @@ -57,34 +57,54 @@ function consoleOut(text) { } } +function sanitize(text) { + // TODO implement sanitization logic + // placeholder for sanitization of query text that cannot be parameterized + return text; +} -async function queryNeptune(q) { +/** + * Executes a neptune query + * @param query the query to execute + * @param params optional query params + * @returns {Promise} + */ +async function queryNeptune(query, params = {}) { if (useSDK) { - const response = await queryNeptuneSDK(q); - return response; + const response = await queryNeptuneSDK(query, params); + return response; } else { - try { - const response = await axios.post(`https://${HOST}:${PORT}/${language}`, `query=${encodeURIComponent(q)}`); - return response.data; + try { + let data = { + query: query, + parameters: JSON.stringify(params) + }; + const response = await axios.post(`https://${HOST}:${PORT}/${language}`, data, { + headers: { + 'Content-Type': 'application/json' + } + }); + return response.data; } catch (error) { console.error("Http query request failed: ", error.message); - consoleOut("Trying with the AWS SDK"); - const response = await queryNeptuneSDK(q); + consoleOut("Trying with the AWS SDK"); + const response = await queryNeptuneSDK(query, params); useSDK = true; - return response; + return response; } } } -async function queryNeptuneSDK(q) { +async function queryNeptuneSDK(query, params = {}) { try { const config = { endpoint: `https://${HOST}:${PORT}` }; const client = new NeptunedataClient(config); const input = { - openCypherQuery: q + openCypherQuery: query, + parameters: JSON.stringify(params) }; const command = new ExecuteOpenCypherQueryCommand(input); const response = await client.send(command); @@ -133,7 +153,7 @@ async function getEdgesNames() { async function findFromAndToLabels(edgeStructure) { - let query = `MATCH (from)-[r:${edgeStructure.label}]->(to) RETURN DISTINCT labels(from) as fromLabel, labels(to) as toLabel`; + let query = `MATCH (from)-[r:${sanitize(edgeStructure.label)}]->(to) RETURN DISTINCT labels(from) as fromLabel, labels(to) as toLabel`; let response = await queryNeptune(query); for (let result of response.results) { for (let fromLabel of result.fromLabel) { @@ -190,9 +210,10 @@ function addUpdateEdgeProperty(edgeName, name, value) { async function getEdgeProperties(edge) { - let query = `MATCH ()-[n:${edge.label}]->() RETURN properties(n) as properties LIMIT ${SAMPLE}`; + let query = `MATCH ()-[n:${sanitize(edge.label)}]->() RETURN properties(n) as properties LIMIT $sample`; + let parameters = {sample: SAMPLE}; try { - let response = await queryNeptune(query); + let response = await queryNeptune(query, parameters); let result = response.results; result.forEach(e => { Object.keys(e.properties).forEach(key => { @@ -214,9 +235,10 @@ async function getEdgesProperties() { async function getNodeProperties(node) { - let query = `MATCH (n:${node.label}) RETURN properties(n) as properties LIMIT ${SAMPLE}`; + let query = `MATCH (n:${sanitize(node.label)}) RETURN properties(n) as properties LIMIT $sample`; + let parameters = {sample: SAMPLE}; try { - let response = await queryNeptune(query); + let response = await queryNeptune(query, parameters); let result = response.results; result.forEach(e => { Object.keys(e.properties).forEach(key => { @@ -238,10 +260,10 @@ async function getNodesProperties() { async function checkEdgeDirectionCardinality(d) { - let queryFrom = `MATCH (from:${d.from})-[r:${d.edge.label}]->(to:${d.to}) WITH to, count(from) as rels WHERE rels > 1 RETURN rels LIMIT 1`; + let queryFrom = `MATCH (from:${sanitize(d.from)})-[r:${sanitize(d.edge.label)}]->(to:${sanitize(d.to)}) WITH to, count(from) as rels WHERE rels > 1 RETURN rels LIMIT 1`; let responseFrom = await queryNeptune(queryFrom); let resultFrom = responseFrom.results[0]; - let queryTo = `MATCH (from:${d.from})-[r:${d.edge.label}]->(to:${d.to}) WITH from, count(to) as rels WHERE rels > 1 RETURN rels LIMIT 1`; + let queryTo = `MATCH (from:${sanitize(d.from)})-[r:${sanitize(d.edge.label)}]->(to:${sanitize(d.to)}) WITH from, count(to) as rels WHERE rels > 1 RETURN rels LIMIT 1`; let responseTo = await queryNeptune(queryTo); let resultTo = responseTo.results[0]; let c = '';