zoukankan      html  css  js  c++  java
  • 【nodejs代理服务器四】代理服务器增加频繁访问的ip加入黑名单

    问题

    渗透者在扫站的时候会频繁请求,我们可以做一些策略来封堵这些频繁访问的ip,把ip加入黑名单。

    策略

    2秒之内访问次数超过100,加入黑名单。

    实现思路

    1. 初次访问把访问Ip作为键,访问ip,时间,次数(初始值为1)封装为一个对象作为value,放入map。
    2. 开启定时器,定时器每秒执行一次,在定时器里面循环map,2秒之内访问次数超过100的ip加入黑名单数组,同时清除加入黑名单ip对应的map key和value.
    3. 在代理程序之前,判断请求过来的ip是否在黑名单,如果在,就拒绝访问。

    核心代码

    /**
    www.qingmiaokeji.cn
     * ip 频繁访问限制策略
     * 2秒之内访问次数超过100,加入黑名单。
     * 可能存在并发问题
     * @constructor
     */
     function IPPolicy () {
        this.cache = {};
        this.blackIpList=[];
        var $this = this;
         setInterval (function () {
             var nowTime = new Date().getTime();
             for(ip in $this.cache){
                var item = $this.cache[ip];
                var timeDif = nowTime - item.visitTime;
                if(timeDif<2000 && item.count>100 ){
                    $this.blackIpList.push(ip)
                    delete  $this.cache[ip];
                }else{
                    item.count = 0;
                }
             }
         },1000)
    }
    IPPolicy.prototype.addVisitIp = function (ip) {
        if(this.cache[ip]){
            this.cache[ip].count =  this.cache[ip].count+1;
            this.cache[ip].visitTime =new Date().getTime();
        }else{
            this.cache[ip] ={"ip":ip,"count":1,"visitTime":new Date().getTime()}
        }
    }
    
    

    完整代码

    var util = require('util'),
        colors = require('colors'),
        http = require('http'),
        httpProxy = require('./node_modules/http-proxy'),
        fs = require("fs");
    
    var welcome = [
        '#    # ##### ##### #####        #####  #####   ####  #    # #   #',
        '#    #   #     #   #    #       #    # #    # #    #  #  #   # # ',
        '######   #     #   #    # ##### #    # #    # #    #   ##     #  ',
        '#    #   #     #   #####        #####  #####  #    #   ##     #  ',
        '#    #   #     #   #            #      #   #  #    #  #  #    #  ',
        '#    #   #     #   #            #      #    #  ####  #    #   #   '
    ].join('
    ');
    
    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;
    }
    
    String.prototype.startWith=function(str){
        var reg=new RegExp("^"+str);
        return reg.test(this);
    }
    
    // 非法字符
    var re = /php|exe|cmd|shell|select|union|delete|update|truncate|insert|eval|function/;
    /** 这里配置转发
     */
    var proxyPassConfig = {
        "/test": 'http://127.0.0.1:8080/hello',
        "/": "http://www.qingmiaokeji.cn/"
    }
    
    /**
     * ip 频繁访问限制策略
     * 2秒之内访问次数超过100,加入黑名单。
     * 可能存在并发问题
     * @constructor
     */
     function IPPolicy () {
        this.cache = {};
        this.blackIpList=[];
        var $this = this;
         setInterval (function () {
             var nowTime = new Date().getTime();
             for(ip in $this.cache){
                var item = $this.cache[ip];
                var timeDif = nowTime - item.visitTime;
                if(timeDif<2000 && item.count>100 ){
                    $this.blackIpList.push(ip)
                    delete  $this.cache[ip];
                }else{
                    item.count = 0;
                }
             }
         },1000)
    }
    IPPolicy.prototype.addVisitIp = function (ip) {
        if(this.cache[ip]){
            this.cache[ip].count =  this.cache[ip].count+1;
            this.cache[ip].visitTime =new Date().getTime();
        }else{
            this.cache[ip] ={"ip":ip,"count":1,"visitTime":new Date().getTime()}
        }
    }
    
    
    
    var iPPolicy = new IPPolicy();
    
    
    var logRootPath ="d:/httpproxy/";
    
    console.log(welcome.rainbow.bold);
    
    
    
    
    
    //
    // Basic Http Proxy Server
    //
    var proxy = httpProxy.createProxyServer({});
    var server = http.createServer(function (req, res) {
        appendLog(req)
    
        var ip = getClientIp(req)
        if(iPPolicy.blackIpList.indexOf(ip)>=0){
            console.log("ip在黑名单");
            backIpHandler(res)
            return
        }
        iPPolicy.addVisitIp(ip);
    
    
        var postData = "";
        req.addListener('end', function(){
            //数据接收完毕
            console.log(postData);
            if(!isValid(postData)){//post请求非法参数
                invalidHandler(res)
            }
        });
        req.addListener('data', function(postDataStream){
            postData += postDataStream
        });
    
    
        var patternUrl = urlHandler(req.url);
        console.log("patternUrl:" + patternUrl);
    
        if (patternUrl) {
            var result = isValid(req.url)
            //验证http头部是否非法
            for(key in req.headers){
                result = result&& isValid(req.headers[key])
            }
            if (result) {
                proxy.web(req, res, {target: patternUrl});
            } else {
                invalidHandler(res)
            }
        } else {
            noPattern(res);
        }
    });
    
    //代理异常捕获
    proxy.on('error', function (err, req, res) {
        console.error(err)
        try{
            res.writeHead(500, {
                'Content-Type': 'text/plain'
            });
    
            res.end('Something went wrong.');
        }catch (e) {
            console.error(err)
        }
    
    
    });
    
    /**
     * 验证非法参数
     * @param value
     * @returns {boolean} 非法返回False
     */
    function isValid(value) {
        return re.test(value.toLowerCase()) ? false : true;
    }
    
    /**
     * 请求转发
     * @param url
     * @returns {*}
     */
    function urlHandler(url) {
        if("/" == url)
            return proxyPassConfig["/"];
    
        for(patternUrl in proxyPassConfig ){
            if(url.startWith(patternUrl)){
                return proxyPassConfig[patternUrl]
            }
    
        }
        return proxyPassConfig[tempUrl];
    }
    //非法请求
    function invalidHandler(res) {
        res.writeHead(400, {'Content-Type': 'text/plain'});
        res.write('Bad Request ');
        res.end();
    }
    
    function  backIpHandler(res) {
        res.writeHead(500, {'Content-Type': 'text/plain',"charset":"utf-9"});
        res.write('ip frequent access ');
        res.end();
    }
    
    //匹配不到
    function noPattern(res) {
        res.writeHead(404, {'Content-Type': 'text/plain'});
        res.write('not found');
        res.end();
    }
    
    //获取访问id
    function getClientIp(req){
        return req.headers['x-forwarded-for'] ||
            req.connection.remoteAddress ||
            req.socket.remoteAddress ||
            req.connection.socket.remoteAddress;
    }
    
    //当天日志名称生成
    function getCurrentDayFile(){
        return logRootPath+"access_"+(new Date()).Format("yyyy-MM-dd")+".log";
    }
    
    //访问日志
    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+"
    ";
        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);
    
    
    

    不足之处

    • 对map操作可能不是线程安全?
    • 定时器循环map元素需要时间,2秒时间间隔上可能不是很准确。

    完善扩展

    • 可用redis的过期缓存机制来实现频繁访问的缓存功能。

    大概先这么多。。。

  • 相关阅读:
    C语言探索之旅 | 第二部分第二课:进击的指针,C语言的王牌!
    C语言探索之旅 | 第二部分第一课:模块化编程
    C语言探索之旅 | 第一部分练习题
    C语言探索之旅 | 第一部分第十一课:函数
    数据结构和算法 | 第一部分第五课:算法复杂度实践
    数据结构和算法 | 第一部分第四课:算法复杂度(下)
    数据结构和算法 | 第一部分第三课:算法复杂度(上)
    数据结构和算法 | 第一部分第二课:小鸭子们去旅行
    数据结构和算法 | 第一部分第一课:什么是数据结构和算法
    C语言探索之旅 | 第一部分第十课:第一个C语言小游戏
  • 原文地址:https://www.cnblogs.com/qingmiaokeji/p/11023396.html
Copyright © 2011-2022 走看看