mm_os
Version:
MM_OS服务端架构,用于快速构建应用程序,支持网站建设、小程序后台、AI应用、物联网(IOT/AIOT)、游戏服务端等多种场景。
385 lines (346 loc) • 11.2 kB
JavaScript
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));
}
};