diff --git a/public/index.html b/public/index.html
index f729840..10b2ba8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,7 +5,7 @@
-
COOL-AMIND 一个很酷的后台权限管理系统
+ COOL-ADMIN 一个很酷的后台权限管理系统
diff --git a/src/modules/base/service/sys/log.ts b/src/modules/base/service/sys/log.ts
index 8695c92..e2e4195 100644
--- a/src/modules/base/service/sys/log.ts
+++ b/src/modules/base/service/sys/log.ts
@@ -53,8 +53,8 @@ export class BaseSysLogService extends BaseService {
}
const keepDay = await this.baseSysConfService.getValue('logKeep');
if (keepDay) {
- const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate();
- await this.baseSysLogEntity.delete({ createTime: LessThan(beforeDate) });
+ const beforeDate = moment().add(-keepDay, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss');
+ await this.baseSysLogEntity.createQueryBuilder().delete().where('createTime < :beforeDate', { beforeDate }).execute();
} else {
await this.baseSysLogEntity.clear();
}
diff --git a/src/modules/plugin/hooks/upload/index.ts b/src/modules/plugin/hooks/upload/index.ts
index effb4b7..72a9c4c 100644
--- a/src/modules/plugin/hooks/upload/index.ts
+++ b/src/modules/plugin/hooks/upload/index.ts
@@ -12,6 +12,49 @@ import { pUploadPath } from '../../../../comm/path';
* 文件上传
*/
export class CoolPlugin extends BasePluginHook implements BaseUpload {
+ /**
+ * 验证路径安全性,防止路径遍历攻击
+ * @param userInput 用户输入的文件名或路径
+ * @returns 安全的文件名
+ */
+ private sanitizePath(userInput: string): string {
+ if (!userInput) {
+ return '';
+ }
+ // 检查是否包含路径遍历字符
+ if (
+ userInput.includes('..') ||
+ userInput.includes('./') ||
+ userInput.includes('.\\') ||
+ userInput.includes('\\') ||
+ userInput.includes('//') ||
+ userInput.includes('\0') ||
+ /^[a-zA-Z]:/.test(userInput) || // Windows绝对路径
+ userInput.startsWith('/')
+ ) {
+ throw new CoolCommException('非法的文件路径');
+ }
+ // 规范化路径后再次检查
+ const normalized = path.normalize(userInput);
+ if (normalized.includes('..') || normalized.startsWith('/')) {
+ throw new CoolCommException('非法的文件路径');
+ }
+ return normalized;
+ }
+
+ /**
+ * 验证最终路径是否在允许的目录内
+ * @param targetPath 目标路径
+ * @param basePath 基础路径
+ */
+ private validateTargetPath(targetPath: string, basePath: string): void {
+ const resolvedTarget = path.resolve(targetPath);
+ const resolvedBase = path.resolve(basePath);
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep)) {
+ throw new CoolCommException('文件路径超出允许范围');
+ }
+ }
+
/**
* 获得上传模式
* @returns
@@ -38,27 +81,40 @@ export class CoolPlugin extends BasePluginHook implements BaseUpload {
*/
async downAndUpload(url: string, fileName?: string) {
const { domain } = this.pluginInfo.config;
+ const basePath = pUploadPath();
+ const dateDir = moment().format('YYYYMMDD');
+
// 从url获取扩展名
const extend = path.extname(fileName ? fileName : url);
+
+ // 验证文件名安全性
+ let safeFileName: string;
+ if (fileName) {
+ safeFileName = this.sanitizePath(fileName);
+ // 只取文件名部分,去除可能的子目录
+ safeFileName = path.basename(safeFileName);
+ } else {
+ safeFileName = uuid() + extend;
+ }
+
const download = require('download');
// 数据
const data = url.includes('http')
? await download(url)
: fs.readFileSync(url);
+
// 创建文件夹
- const dirPath = path.join(pUploadPath(), `${moment().format('YYYYMMDD')}`);
+ const dirPath = path.join(basePath, dateDir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
- const uuidStr = uuid();
- const name = `${moment().format('YYYYMMDD')}/${
- fileName ? fileName : uuidStr + extend
- }`;
- fs.writeFileSync(
- `${dirPath}/${fileName ? fileName : uuid() + extend}`,
- data
- );
- return `${domain}/upload/${name}`;
+
+ const targetPath = path.join(dirPath, safeFileName);
+ // 验证最终路径
+ this.validateTargetPath(targetPath, basePath);
+
+ fs.writeFileSync(targetPath, data);
+ return `${domain}/upload/${dateDir}/${safeFileName}`;
}
/**
@@ -68,21 +124,28 @@ export class CoolPlugin extends BasePluginHook implements BaseUpload {
*/
async uploadWithKey(filePath: any, key: any) {
const { domain } = this.pluginInfo.config;
+ const basePath = pUploadPath();
+ const dateDir = moment().format('YYYYMMDD');
+
+ // 验证key安全性
+ const safeKey = this.sanitizePath(key);
+
const data = fs.readFileSync(filePath);
+
+ // 构建目标路径
+ const targetPath = path.join(basePath, dateDir, safeKey);
+ const dirPath = path.dirname(targetPath);
+
+ // 验证最终路径
+ this.validateTargetPath(targetPath, basePath);
+
// 如果文件夹不存在则创建
- const dirPath = path.join(
- pUploadPath(),
- moment().format('YYYYMMDD'),
- path.dirname(key)
- );
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
- fs.writeFileSync(
- path.join(pUploadPath(), moment().format('YYYYMMDD'), key),
- data
- );
- return `${domain}/upload/${moment().format('YYYYMMDD')}/${key}`;
+
+ fs.writeFileSync(targetPath, data);
+ return `${domain}/upload/${dateDir}/${safeKey}`;
}
/**
@@ -94,35 +157,45 @@ export class CoolPlugin extends BasePluginHook implements BaseUpload {
const { domain } = this.pluginInfo.config;
try {
const { key } = ctx.fields;
- if (
- key &&
- (key.includes('..') ||
- key.includes('./') ||
- key.includes('\\') ||
- key.includes('//'))
- ) {
- throw new CoolCommException('非法的key值');
+ const basePath = pUploadPath();
+ const dateDir = moment().format('YYYYMMDD');
+
+ // 验证key安全性
+ let safeKey: string | undefined;
+ if (key) {
+ safeKey = this.sanitizePath(key);
}
+
if (_.isEmpty(ctx.files)) {
throw new CoolCommException('上传文件为空');
}
- const basePath = pUploadPath();
const file = ctx.files[0];
- const extension = file.filename.split('.').pop();
- const name =
- moment().format('YYYYMMDD') + '/' + (key || `${uuid()}.${extension}`);
+ // 安全处理原始文件名
+ const originalFileName = path.basename(file.filename);
+ const extension = originalFileName.split('.').pop();
+
+ const finalName = safeKey || `${uuid()}.${extension}`;
+ const name = `${dateDir}/${finalName}`;
const target = path.join(basePath, name);
- const dirPath = path.join(basePath, moment().format('YYYYMMDD'));
+
+ // 验证最终路径
+ this.validateTargetPath(target, basePath);
+
+ const dirPath = path.join(basePath, dateDir);
if (!fs.existsSync(dirPath)) {
- fs.mkdirSync(dirPath);
+ fs.mkdirSync(dirPath, { recursive: true });
}
+
const data = fs.readFileSync(file.data);
fs.writeFileSync(target, data);
return domain + '/upload/' + name;
} catch (err) {
console.error(err);
- throw new CoolCommException('上传失败' + err.message);
+ if (err instanceof CoolCommException) {
+ throw err;
+ }
+ throw new CoolCommException('上传失败: ' + err.message);
}
}
}