fix: HIGH+MEDIUM bugs from full audit - listen error, JWT, upload, mailer, webhook
This commit is contained in:
@@ -4,8 +4,13 @@ const { getConfig } = require('../db');
|
||||
function getSecret() {
|
||||
try {
|
||||
const cfg = getConfig();
|
||||
return cfg?.jwt_secret || process.env.JWT_SECRET || 'mc-dev-secret';
|
||||
} catch { return process.env.JWT_SECRET || 'mc-dev-secret'; }
|
||||
const secret = cfg?.jwt_secret || process.env.JWT_SECRET;
|
||||
if (secret) return secret;
|
||||
throw new Error('JWT_SECRET not configured');
|
||||
} catch (e) {
|
||||
if (e.message === 'JWT_SECRET not configured') throw e;
|
||||
return process.env.JWT_SECRET || (() => { throw new Error('JWT_SECRET not configured'); })();
|
||||
}
|
||||
}
|
||||
|
||||
function generateToken(user) {
|
||||
|
||||
@@ -33,13 +33,14 @@ function sanitize(value) {
|
||||
}
|
||||
|
||||
function sanitizeBody(req, res, next) {
|
||||
if (req.body) {
|
||||
for (const key of Object.keys(req.body)) {
|
||||
if (typeof req.body[key] === 'string') {
|
||||
req.body[key] = sanitize(req.body[key]);
|
||||
}
|
||||
function walk(obj) {
|
||||
if (!obj || typeof obj !== 'object') return;
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (typeof obj[key] === 'string') obj[key] = sanitize(obj[key]);
|
||||
else if (typeof obj[key] === 'object') walk(obj[key]);
|
||||
}
|
||||
}
|
||||
walk(req.body);
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ function validateMagicBytes(buffer, mime) {
|
||||
'image/png': () => head[0] === 0x89 && head[1] === 0x50 && head[2] === 0x4E && head[3] === 0x47,
|
||||
'image/gif': () => head.toString('ascii', 0, 6) === 'GIF89a' || head.toString('ascii', 0, 6) === 'GIF87a',
|
||||
'image/webp': () => head.toString('ascii', 0, 4) === 'RIFF' && head.toString('ascii', 8, 12) === 'WEBP',
|
||||
'video/mp4': () => buffer.includes(Buffer.from('ftyp')),
|
||||
'video/mp4': () => buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70,
|
||||
'video/webm': () => head[0] === 0x1A && head[1] === 0x45 && head[2] === 0xDF && head[3] === 0xA3,
|
||||
};
|
||||
if (!sigs[mime]) return false;
|
||||
@@ -58,18 +58,22 @@ const upload = multer({
|
||||
|
||||
function finalizeUpload(req, res, next) {
|
||||
if (!req.files || req.files.length === 0) return next();
|
||||
for (const file of req.files) {
|
||||
const buf = fs.readFileSync(file.path);
|
||||
if (!ALLOWED_MIME[file.mimetype]) {
|
||||
fs.unlinkSync(file.path);
|
||||
return res.status(400).json({ error: `不支持的文件类型: ${file.mimetype}` });
|
||||
}
|
||||
if (!validateMagicBytes(buf, file.mimetype)) {
|
||||
fs.unlinkSync(file.path);
|
||||
return res.status(400).json({ error: '文件内容与声明类型不符,可能是恶意文件' });
|
||||
const filePaths = [];
|
||||
try {
|
||||
for (const file of req.files) {
|
||||
const fd = fs.openSync(file.path, 'r');
|
||||
const buf = Buffer.alloc(256);
|
||||
fs.readSync(fd, buf, 0, 256, 0);
|
||||
fs.closeSync(fd);
|
||||
filePaths.push(file.path);
|
||||
if (!ALLOWED_MIME[file.mimetype]) throw new Error(`不支持的文件类型: ${file.mimetype}`);
|
||||
if (!validateMagicBytes(buf, file.mimetype)) throw new Error('文件内容与声明类型不符,可能是恶意文件');
|
||||
}
|
||||
next();
|
||||
} catch (e) {
|
||||
for (const fp of filePaths) { try { fs.unlinkSync(fp); } catch {} }
|
||||
return res.status(400).json({ error: e.message || '文件校验失败' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = { upload, finalizeUpload, validateMagicBytes, UPLOAD_DIR, ALLOWED_MIME };
|
||||
|
||||
Reference in New Issue
Block a user