CVE-2022-36084: Other (NVD-CWE-Other)
value: cruddl is software for creating a GraphQL API for a database, using the GraphQL SDL to model a schema. If cruddl starting with version 1.1.0 and prior to versions 2.7.0 and 3.0.2 is used to generate a schema that uses `@flexSearchFulltext`, users of that schema may be able to inject arbitrary AQL queries that will be forwarded to and executed by ArangoDB. Schemas that do not use `@flexSearchFulltext` are not affected. The attacker needs to have `READ` permission to at least one root entity type that has `@flexSearchFulltext` enabled. The issue has been fixed in version 3.0.2 and in version 2.7.0 of cruddl. As a workaround, users can temporarily remove `@flexSearchFulltext` from their schemas.
CWE description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
CVSS v3 base score: 8.8
Published: 2022-09-08T22:15Z
Repository: https://github.com/AEB-labs/cruddl
Language: TypeScript
Files changed: src/database/arangodb/aql-generator.ts, src/database/arangodb/arangodb-adapter.ts
Commit date: 2022-09-02 15:08:59 +0200
Commit message: Use regular aql framework for flex search expression tokenization

Diff: diff --git a/src/database/arangodb/aql-generator.ts b/src/database/arangodb/aql-generator.ts
index 4d9a027..68b9bd5 100644
--- a/src/database/arangodb/aql-generator.ts
+++ b/src/database/arangodb/aql-generator.ts
@@ -1760,14 +1760,13 @@ function getSimpleFollowEdgeFragment(node: FollowEdgeQueryNode, context: QueryCo
 
 function isStringCaseInsensitive(str: string) {
     return str.toLowerCase() === str.toUpperCase();
 }
 
-export function generateTokenizationQuery(tokensFiltered: ReadonlyArray<FlexSearchTokenizable>) {
-    const fragments: string[] = [];
+export function generateTokenizationQuery(tokensFiltered: ReadonlyArray<FlexSearchTokenizable>): AQLFragment {
+    const fragments: AQLFragment[] = [];
     for (let i = 0; i < tokensFiltered.length; i++) {
         const value = tokensFiltered[i];
-        fragments.push(`token_${i}: TOKENS("${value.expression}", "${value.analyzer}")`);
+        fragments.push(aql`${aql.identifier('token_' + i)}: TOKENS(${value.expression}, ${value.analyzer})`);
     }
-    const query = `RETURN { ${fragments.join(',\n')} }`;
-    return query;
+    return aql`RETURN { ${aql.join(fragments, aql`',\n`)} }`;
 }
diff --git a/src/database/arangodb/arangodb-adapter.ts b/src/database/arangodb/arangodb-adapter.ts
index 0f053bf..3e549ac 100644
--- a/src/database/arangodb/arangodb-adapter.ts
+++ b/src/database/arangodb/arangodb-adapter.ts
@@ -716,11 +716,13 @@ export class ArangoDBAdapter implements DatabaseAdapter {
                     (value2, index2) =>
                         value.expression === value2.expression && value.analyzer === value2.analyzer && index > index2
                 )
         );
 
-        const cursor = await this.db.query(generateTokenizationQuery(tokenizationsFiltered));
+        const aqlFragment = generateTokenizationQuery(tokenizationsFiltered);
+        const queryCode = aqlFragment.getCode();
+        const cursor = await this.db.query(queryCode.code, queryCode.boundValues);
 
         const result = await cursor.next();
         const resultArray: FlexSearchTokenization[] = [];
         for (let i = 0; i < tokenizationsFiltered.length; i++) {
             resultArray.push({

Vulnerable code: export function generateTokenizationQuery(tokensFiltered: ReadonlyArray<FlexSearchTokenizable>) {
    const fragments: string[] = [];
        fragments.push(`token_${i}: TOKENS("${value.expression}", "${value.analyzer}")`);
    const query = `RETURN { ${fragments.join(',\n')} }`;
    return query;
        const cursor = await this.db.query(generateTokenizationQuery(tokenizationsFiltered));
Fixed code: export function generateTokenizationQuery(tokensFiltered: ReadonlyArray<FlexSearchTokenizable>): AQLFragment {
    const fragments: AQLFragment[] = [];
        fragments.push(aql`${aql.identifier('token_' + i)}: TOKENS(${value.expression}, ${value.analyzer})`);
    return aql`RETURN { ${aql.join(fragments, aql`',\n`)} }`;
        const aqlFragment = generateTokenizationQuery(tokenizationsFiltered);
        const queryCode = aqlFragment.getCode();
        const cursor = await this.db.query(queryCode.code, queryCode.boundValues);