事出有因
最近 web 系统引来了黑客的攻击,经常被扫描,各种漏洞尝试。
分析攻击日志,有几种常见的攻击手段:
- 上传 webshell
- 远程执行命令漏洞
- sql 注入
- xxs 攻击
- 试探各种开源框架爆出来的漏洞
分析攻击信息的特点
说白了就是采用 web 渗透技术,利用 http 请求,黑客想尽办法,在 http header ,body, 等部分植入非法的命令,非法字符常见的有:exe,cmd,powershell,download,select,union,delete 等等。
解决问题思路
- 我们能不能开发个代理服务器,来分析 http 请求 header,body 里面的信息,如果有非法字符,就截断,拒绝服务。
- 配置允许请求的白名单,拒绝非法 Url.
网络拓扑
http proxy 拦截非法请求,拒绝服务。
技术选型
常见的代理服务器有 nginx,apache,不知道这 2 个代理服务器能不能灵活的配置,过滤,转发,没有深入了解。
因此选用 nodejs http-proxy。
nodejs 优点
- 轻量级
- 快速部署
- 灵活开发
- 高吞吐,异步 io
编码实现逻辑图
绝对干货,分享代码
代码依赖
- http-proxy 1.17.0
https://github.com/nodejitsu/… 代码地址
- “colors”: “~0.6.2”,
var util = require('util'),
colors = require('colors'),
http = require('http'),
httpProxy = require('./node_modules/http-proxy');
fs = require("fs");
var welcome = [
'# # ##### ##### ##### ##### ##### #### # # # #',
'# # # # # # # # # # # # # # # #',
'###### # # # # ##### # # # # # # ## #',
'# # # # ##### ##### ##### # # ## #',
'# # # # # # # # # # # # #',
'# # # # # # # # #### # # #'
].join('\n');
Date.prototype.Format = function(fmt) { //author: meizz
var o = {"M+": this.getMonth() + 1, // 月份
"d+": this.getDate(), // 日
"h+": this.getHours(), // 小时
"m+": this.getMinutes(), // 分
"s+": this.getSeconds(), // 秒
"S": this.getMilliseconds() // 毫秒};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
// 非法字符
var re = /php|exe|cmd|shell|select|union|delete|update|insert/;
/** 这里配置转发
*/
var proxyPassConfig = {
"/hello": "http://www.qingmiaokeji.cn",
"/": "http://127.0.0.1/"
}
var logRootPath ="g:/httpproxy/";
console.log(welcome.rainbow.bold);
function getCurrentDayFile(){// console.log(logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log");
return logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log";
}
//
// Basic Http Proxy Server
//
var proxy = httpProxy.createProxyServer({});
var server = http.createServer(function (req, res) {appendLog(req)
var postData = "";
req.addListener('end', function(){
// 数据接收完毕
console.log(postData);
if(!isValid(postData)){//post 请求非法参数
invalidHandler(res)
}
});
req.addListener('data', function(postDataStream){postData += postDataStream});
var result = isValid(req.url)
// 验证 http 头部是否非法
for(key in req.headers){result = result&& isValid(req.headers[key])
}
if (result) {var patternUrl = urlHandler(req.url);
console.log("patternUrl:" + patternUrl);
if (patternUrl) {proxy.web(req, res, {target: patternUrl});
} else {noPattern(res);
}
} else {invalidHandler(res)
}
});
proxy.on('error', function (err, req, res) {
res.writeHead(500, {'Content-Type': 'text/plain'});
res.end('Something went wrong.');
});
/**
* 验证非法参数
* @param value
* @returns {boolean} 非法返回 False
*/
function isValid(value) {return re.test(value) ? false : true;
}
/**
* 请求转发
* @param url
* @returns {*}
*/
function urlHandler(url) {var tempUrl = url.substring(url.lastIndexOf("/"));
return proxyPassConfig[tempUrl];
}
function invalidHandler(res) {res.writeHead(400, {'Content-Type': 'text/plain'});
res.write('Bad Request');
res.end();}
function noPattern(res) {res.writeHead(404, {'Content-Type': 'text/plain'});
res.write('not found');
res.end();}
function getClientIp(req){return req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
}
function appendLog(req) {console.log("request url:" + req.url);
var logData = (new Date()).Format("yyyy-MM-dd hh:mm:ss")+""+getClientIp(req)+" "+req.method+" "+req.url+"\n";
fs.exists(logRootPath,function(exists){if(!exists){fs.mkdirSync(logRootPath)
}
fs.appendFile(getCurrentDayFile(),logData,'utf8',function(err){if(err)
{console.log(err);
}
});
})
}
console.log("listening on port 80".green.bold)
server.listen(80);
思路扩展
- 拦截非法字符后可以发邮件通知管理员
- 可以把日志发送到日志系统,进行大数据分析
- 增加频繁访问,拒绝 Ip 功能。可以利用 redis 过期缓存实现。