You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js(Koa框架)路径遍历漏洞修复技术咨询

问题描述

基于Koa框架的Node.js文件上传代码功能正常,但Veracode扫描指出const readStream = fs.createReadStream(file.filepath);存在路径遍历漏洞,需修复。

原代码如下:

const uploads = [];

const files = ctx.request.files;

for (let i = 0; i < Object.keys(files).length; i++) {
  const file = files[`file-${i}`];

  if (file) uploads.push(file);
}

if (uploads.length == 0) {
  ctx.log.error(NO_FILES);

  ctx.status = 404;

  return ctx;
}

for (const element of uploads) {
  const file = element;

  const readStream = fs.createReadStream(file.filepath);

  const key =
    file.filepath.substring(file.filepath.lastIndexOf("/") + 1) +
    file.filepath.substring(file.filepath.lastIndexOf("."));

  const params = {
    Body: readStream,

    Bucket: bucketName,

    ContentType: file.mimetype,

    Key: key,

    Metadata: { landParcelId },
  };

  let attach_url;

  try {
    const upload = s3.upload(params);

    const res = await upload.promise();

    attach_url = res.Location;
  } catch (err) {
    ctx.log.error(err);

    ctx.status = 500;

    return ctx;
  }

  attachments.push({
    landParcelId,

    description: file.originalFilename,

    fileName: file.originalFilename,

    typeId: ctx.request.body.documentType,

    fileType: file.mimetype,

    url: attach_url,
  });
}

try {
  await landAttachment.bulkCreate(attachments);
} catch (err) {
  ctx.log.error(err);

  ctx.status = 500;

  return ctx;
}

ctx.status = 200;

漏洞原因

直接信任file.filepath的值存在风险:若上传组件配置存在漏洞,或恶意请求篡改文件路径,可能导致代码读取服务器任意位置的文件,触发路径遍历攻击。

修复步骤

  • 校验文件路径范围:确保待读取文件位于系统指定的临时上传目录内,拒绝超出范围的路径。
  • 安全生成文件名:放弃从filepath截取文件名,改用上传组件提供的originalFilename,并过滤特殊字符避免注入。
  • 用内置工具处理路径:使用Node.js的path模块解析路径,避免手动字符串处理出错。

修复后的代码

const path = require('path');
const os = require('os');
const uploads = [];

const files = ctx.request.files;

for (let i = 0; i < Object.keys(files).length; i++) {
  const file = files[`file-${i}`];

  if (file) uploads.push(file);
}

if (uploads.length == 0) {
  ctx.log.error(NO_FILES);
  ctx.status = 404;
  return ctx;
}

// 定义允许的临时上传目录(根据实际上传组件配置调整,此处用系统默认临时目录)
const allowedUploadDir = path.resolve(os.tmpdir());

for (const element of uploads) {
  const file = element;
  const resolvedFilePath = path.resolve(file.filepath);

  // 校验文件是否在允许的目录内,拦截路径遍历尝试
  if (!resolvedFilePath.startsWith(allowedUploadDir)) {
    ctx.log.error('Invalid file path detected');
    ctx.status = 400;
    return ctx;
  }

  const readStream = fs.createReadStream(resolvedFilePath);

  // 过滤文件名特殊字符,生成安全的S3存储Key
  const safeFileName = file.originalFilename.replace(/[^a-zA-Z0-9_\-.]/g, '_');
  const key = `${landParcelId}_${safeFileName}`;

  const params = {
    Body: readStream,
    Bucket: bucketName,
    ContentType: file.mimetype,
    Key: key,
    Metadata: { landParcelId },
  };

  let attach_url;

  try {
    const upload = s3.upload(params);
    const res = await upload.promise();
    attach_url = res.Location;
  } catch (err) {
    ctx.log.error(err);
    ctx.status = 500;
    return ctx;
  }

  attachments.push({
    landParcelId,
    description: file.originalFilename,
    fileName: file.originalFilename,
    typeId: ctx.request.body.documentType,
    fileType: file.mimetype,
    url: attach_url,
  });
}

try {
  await landAttachment.bulkCreate(attachments);
} catch (err) {
  ctx.log.error(err);
  ctx.status = 500;
  return ctx;
}

ctx.status = 200;

额外注意事项

  • 确保上传中间件(如koa-body)开启strict: true配置,禁止上传超出临时目录的文件。
  • 对originalFilename的字符过滤可根据业务需求调整,但需覆盖常见特殊字符。
  • S3 Key加入业务标识(如landParcelId)可避免文件名重复导致的文件覆盖问题。

内容的提问来源于stack exchange,提问作者Partha Chowdhury

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 02:30:44