UNPKG

mm_os

Version:

MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。

385 lines (346 loc) 11.2 kB
const { Ip } = require('mm_ip'); /** * web防火墙 */ module.exports = { /** * 初始化 * @param {object} adapter 适配器集合 */ init(adapter) { // 获取web服务 var web = adapter.web; // 获取配置 let cg = { ...this.config, ...adapter.web.config }; // 初始化全局IP管理器 this._initIpManager(cg); // 设置WAF中间件 let self = this; web.use(async (ctx, next) => { let waf_check_ret = await self._handleWafCheck(ctx, cg); if (waf_check_ret) { await next(); } }); }, /** * 初始化IP管理器 * @private * @param {object} config 配置对象 */ _initIpManager(config) { // 确保全局IP管理器存在 if (!$.ip || ($.ip && !$.is_plugin)) { $.ip = new Ip(); $.is_plugin = true; } // 配置IP管理器 if (config && config.web) { let ipConfig = { auto_black_enable: config.web.auto_black_enable !== false, max_req_per_min: config.web.max_req_per_min || 100, auto_black_limit: config.web.auto_black_threshold || 50 }; $.ip.setConfig(ipConfig); } }, /** * 处理WAF检查 * @private * @param {object} ctx 上下文 * @param {object} cg 配置 * @returns {boolean} 是否通过检查 */ async _handleWafCheck(ctx, cg) { // 获取客户端IP var ip = this._getClientIp(ctx.req); // 获取请求路径 let path = ctx.path; // 检查路径是否在白名单中 if (this.isPathWhiteListed(path, cg)) { return true; } // 使用mm_ip进行IP检查和频率限制 if (!this._checkIpAccess(ip)) { this._sendWafResponse(ctx, 403, '访问被WAF阻止,IP访问受限'); return false; } // 检查基本攻击特征 let danger = this.wafCheck(ctx.url); if (danger) { this._sendWafResponse(ctx, 403, '访问被WAF阻止,请求包含潜在的攻击特征', danger.toString()); return false; } // 检查路径遍历攻击 let path_trav_ret = this._checkPathTrav(ctx); if (path_trav_ret) { this._sendWafResponse(ctx, 403, '访问被WAF阻止,检测到路径遍历攻击尝试'); return false; } return true; }, /** * 检查IP访问权限 * @private * @param {string} ip IP地址 * @returns {boolean} 是否允许访问 */ _checkIpAccess(ip) { return true; try { // 使用mm_ip的record方法进行综合检查(包含白名单、黑名单、频率限制) return $.ip.record(ip); } catch { // 如果mm_ip不可用,使用备用方案 this.log('warn', 'mm_ip不可用,使用备用IP检查'); // 备用方案:检查本地白名单 const LOCAL_WHITE_LIST = ['127.0.0.1', '::1', 'localhost']; return LOCAL_WHITE_LIST.includes(ip); } }, /** * 获取客户端IP * @private * @param {object} req 请求对象 * @returns {string} 客户端IP */ _getClientIp(req) { // 标准IP获取逻辑 return req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress; }, /** * 检查路径遍历攻击 * @private * @param {object} ctx 上下文 * @returns {string|boolean} 检测到的攻击类型或false */ _checkPathTrav(ctx) { var path = ctx.path; // 检查路径本身 if (this.checkPathTrav(path)) { return '路径本身包含攻击特征: ' + path; } if (!this.isSafePath(path)) { return '路径不安全: ' + path; } // 检查请求参数 var query_params = ctx.query; for (var k in query_params) { var val = query_params[k]; if (typeof val === 'string' && this.checkPathTrav(val)) { return '查询参数包含攻击特征: ' + k + '=' + val; } } // 检查POST请求体 if (ctx.method === 'POST' && ctx.request.body) { var body_content = JSON.stringify(ctx.request.body); if (this.checkPathTrav(body_content)) { return '请求体包含攻击特征'; } } return false; }, /** * 发送WAF响应 * @private * @param {object} ctx 上下文 * @param {number} status 状态码 * @param {string} msg 消息 * @param {string} [rule] 规则 */ _sendWafResponse(ctx, status, msg, rule) { ctx.status = status; ctx.body = { code: status, msg: msg, rule: rule }; }, /** * 获取SQL注入检测规则 * @private * @returns {RegExp[]} SQL注入规则列表 */ _getSqlRules() { return [ /select.+(from|limit)/i, /(?:(union(.*?)select))/i, /sleep\((\s*)(\d*)(\s*)\)/i, /group\s+by.+\(/i, /(?:from\W+information_schema\W)/i, /(?:(?:current_)user|database|schema|connection_id)\s*\(/i, /\s*or\s+.*=.*/i, /order\s+by\s+.*--$/i, /benchmark\((.*)\,(.*)\)/i, /base64_decode\(/i, /(?:(?:current_)user|database|version|schema|connection_id)\s*\(/i, /(?:etc\/\W*passwd)/i, /into(\s+)+(?:dump|out)file\s*/i ]; }, /** * 获取代码注入和XSS检测规则 * @private * @returns {RegExp[]} 代码注入和XSS规则列表 */ _getCodeInjectRules() { return [ /xwork.MethodAccessor/i, /(?:define|eval|file_get_contents|include|require|require_once)\(/i, /(?:shell_exec|phpinfo|system|passthru|preg_\w+|execute)\(/i, /(?:echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\(/i, /\<(iframe|script|body|img|layer|div|meta|style|base|object|input)/i, /(onmouseover|onmousemove|onerror|onload)\=/i, /javascript:/i ]; }, /** * 获取路径遍历检测规则 * @private * @returns {RegExp[]} 路径遍历规则列表 */ _getPathTravRules() { return [ // 增强的路径遍历检测规则 /\.\.\//i, // 基础 ../ /\.\.\\/i, // Windows格式 ..\ /\%2e\%2e\//i, // URL编码 ../ /\%2e%2e\//i, // URL编码 ../ /\%252e%252e%2f/i, // 双重URL编码 ../ /\%252e\%252e\%2f/i, // 双重URL编码 ../ /\.\%2e\//i, // 混合编码 /\%2e\./i, // 变体形式 /\%5c/i, // 反斜杠URL编码 /\%255c/i, // 反斜杠双重URL编码 // 系统文件路径检测 - 改进以避免误判 new RegExp('(?:\\/etc\\/|\\/proc\\/|C:\\\\Windows\\|' + 'C:\\\\winnt\\|C:\\\\Program Files\\|\\/sys\\/)', 'i') ]; }, /** * 获取命令注入检测规则 * @private * @returns {RegExp[]} 命令注入规则列表 */ _getCmdInjectRules() { return [ // 命令注入检测 new RegExp('\\|\\|.*(?:ls|pwd|whoami|ll|ifconfog|' + 'ipconfig|&&|chmod|cd|mkdir|rmdir|cp|mv)', 'i'), new RegExp('(?:ls|pwd|whoami|ll|ifconfog|ipconfig|' + '&&|chmod|cd|mkdir|rmdir|cp|mv).*\\|\\|', 'i'), new RegExp('(gopher|doc|php|glob|file|phar|zlib|ftp|' + 'ldap|dict|ogg|data)\\:\/', 'i') ]; }, /** * 获取WAF规则列表 * @private * @returns {RegExp[]} WAF规则列表 */ _getWafRules() { return [ ...this._getSqlRules(), ...this._getCodeInjectRules(), ...this._getPathTravRules(), ...this._getCmdInjectRules() ]; }, /** * 使用正则表达式,检测字符串是否含有攻击特征,检测到攻击特征返回true,没检测到返回false * @param {string} url 网址 * @returns {RegExp|null} 检测到的攻击规则或null */ wafCheck(url) { var rules = this._getWafRules(); for (var i = 0; i < rules.length; i++) { if (rules[i].test(url)) { return rules[i]; } } return null; }, /** * 检查路径是否包含路径遍历攻击 * @param {string} path 路径 * @returns {boolean} 是否包含路径遍历 */ checkPathTrav(path) { // 处理URL编码变体 let url_decoded = decodeURIComponent(path); let double_url_decoded = decodeURIComponent(url_decoded); // 检查路径是否包含危险模式 let danger_patterns = [ '../', '../../', '../../../', // Unix/Linux格式 '..\\', '..\\\\', '..\\\\\\\\', // Windows格式 '/%2e%2e/', '/%2e%2e%2f', // URL编码变体 '\\%2e%2e\\', '\\%2e%2e\\\\' // Windows URL编码变体 ]; // 检查系统关键文件路径(绝对路径攻击) let sys_paths = [ '/etc/passwd', '/etc/shadow', '/etc/group', '/etc/hosts', '/proc/', '/bin/', '/usr/bin/', 'C:\\Windows\\', 'C:\\winnt\\', 'C:\\Program Files\\' ]; // 检查原始路径、单次解码和双重解码后的路径 // 1. 检查相对路径遍历模式 let has_trav_pattern = danger_patterns.some(pattern => path.includes(pattern) || url_decoded.includes(pattern) || double_url_decoded.includes(pattern) ); // 2. 检查绝对路径攻击(包含系统关键文件路径) let has_abs_attack = sys_paths.some(sys_path => path.toLowerCase().includes(sys_path.toLowerCase()) || url_decoded.toLowerCase().includes(sys_path.toLowerCase()) || double_url_decoded.toLowerCase().includes(sys_path.toLowerCase()) ); return has_trav_pattern || has_abs_attack; // 移除starts_with_slash检查 }, /** * 检查请求路径是否规范化,防止路径遍历攻击 * @param {string} path 请求路径 * @returns {boolean} 是否为安全路径 */ isSafePath(path) { // 特殊处理根路径,直接返回安全 if (path === '/') { return true; } // 获取规范化的路径 let norm_path = path.split('/') .filter(segment => segment !== '') .reduce((acc, segment) => { // 防止路径回溯 if (segment === '..') { acc.pop(); } else if (segment !== '.') { acc.push(segment); } return acc; }, []) .join('/'); // 重新构建规范化的完整路径 let safe_path = '/' + norm_path; // 检查规范化后的路径长度是否小于原始路径(表示存在路径回溯) return safe_path.length >= path.length - 2; // 允许末尾的 '/' 差异 }, /** * 检查路径是否在白名单中 * @param {string} path 请求路径 * @param {object} config WAF配置 * @returns {boolean} 是否在白名单中 */ isPathWhiteListed(path, config) { // 获取配置中的路径白名单 let path_white_list = (config && config.web && config.web.path_whitelist) || (config && config.path_whitelist) || ['/static', '/favicon.ico', '/api', '/public', '/assets']; // 检查路径是否以白名单中的任何路径开头 return Array.isArray(path_white_list) && path_white_list.some(white_path => path.startsWith(white_path)); } };