Jump to content
新域网络技术论坛

*NIX及*BSD可使用的主动防火墙[PHP/原创]


Jamers
 Share

Recommended Posts

我们平常能够使用到的防火墙大多是规则设置好后,然后就等人工分析相应日志后更新相应规则,这样就没有多少时效性,不能及时保证系统的安全。所以才萌发出搞个主动屏蔽的功能。

 

目前这套系统仅能够运行在启用ipfw的类*NIX或*BSD的系统中,当然PHP和mysql必不可少。另外程序只能够以root的身份在命令行状态进行执行,因为IPFW必须有root权限才能够操作。

 

系统原理为:定期执行相应程序分析指定日志文件(可以设置多个),从日志文件中挑出对系统不利的操作,一旦达到设定阀值后,即将指定IP临时屏蔽(按目前系统设定,那么接下来的24小时,此IP将无法访问本服务器),如果多次被屏蔽,将会被直接移至黑名单中,那样将永久屏蔽此IP的访问。具体可在config.php配置文件中修改。

 

以下帖出部分核心文件,完整的文件将在最下面提供下载。此程序仅供抛砖引玉,如果有更好的方案,可以一起探讨共同成长。如果此工具能够帮到一些朋友,我将不胜荣幸,如果有任何BUG之类的,也可以及时与我联系共同把此工具做好。

 

安装方式: 注意本程序仅供有足够unix操作经验的网友使用,权限方面的问题不多讲。

1. 首先没有conn.php,需要选执行一次 install.php,程序会自动生成conn.php

2. 待conn.php生成后,修改并创建相应mysql数据库及用户后再次运行install.php

3. 然后修改config.php,重要的是将各需要扫描的apache访问日志文件完整路径写上,原配置文件中的文件仅供测试使用

4. 修改crontab以root身份,每10到15分钟运行一下 php firewall.php  ,这个时间可以根据自己的实际情况进行调整

5. 如果有需要加入白名单的IP地址以防止误封,可以手工在whitelist表中添加相应记录。

6. 如果事先有需要加入黑名单的IP地址也可以手工在blacklist表中添加相应记录

 

config.php

<?php
    DEFINED('ROOT_DIR') || DEFINE('ROOT_DIR',dirname(__FILE__));
    if (file_exists(ROOT_DIR.'/conn.php'))
        include_once(ROOT_DIR.'/conn.php');
       
    class config {
        /**
         * 配置文件 Jamers 2015.1.5
         */
         static $dellogtime     =   -1;    //删除数据时间 31*24*60*60 -1为不删除
         static $blockvalue     =   50;         //屏蔽阀值,一天触发规则多少次
        
         static $step           =   1;      //ID步行
         static $white_start    =   1100;   //白名单起始ID
         static $white_set      =   10;     //白名单规则集
         static $black_start    =   1500;   //黑名单起始ID
         static $black_set      =   11;     //黑名单规则集
         static $temp_start     =   10000;  //临时屏蔽起始ID
         static $temp_set       =   15;     //临时屏蔽规则集
         static $temp_durt      =   array(0 => 1,1 => 7,2 => -1);  //临时屏蔽时间天数 -1为永久
        
         static $ipfw           =   '/sbin/ipfw';   //IPFW完整路径,否则CRON无法执行
         static $debug                    =        false;        //DEBUG标志
         static $debug_out            =        'debug.txt';    //DEBUG输出文件
        
         static $list           =   array(
            'data/bbs.zomew.net-access_log',
                );    //需要扫描的日志文件
               
         static $filter         =   array(
            ' 404 ','%20and%20','%20or%20','/**/or/**/','phpinfo.php','act=phpinfo','phpmyadmin','%20union%20','select%20','><script>','=alert(',
                );    //需要过滤的内容
               
         static $ignore        = array(
            'robots.txt','google=',
                );  //忽略列表数据,比如robots.txt
        
         //调用语句:cat 文件名 | grep -i "19/Dec/2014" | grep -i " 404 \|phpinfo.php" | grep -vi "robots.txt"
        
    }
?>

cls_firewall.php  核心文件

<?php
    DEFINED('ROOT_DIR') || DEFINE('ROOT_DIR',dirname(__FILE__));
    include_once(ROOT_DIR.'/config.php');
    include_once(ROOT_DIR.'/cls_mysql.php');
   
    class cls_firewall {
        /**
         * IPFW防火墙处理脚本,原设想自动根据apache日志屏蔽相应恶意访问IP
         *
         * Jamers 2015.1.5
         */
        public $vars=array();
        public $DB;
        public $step = 1;
        private $dt,$ipfw,$debug,$debug_out;
        private $filter,$ignore;
        private $type = array('white','black','temp');
        public $ids = array('white'=>1100,'black'=>1500,'temp'=>10000);
        public $sets = array('white'=>10,'black'=>11,'temp'=>15);
        public $qsql = array(
            'white' => 'select ip from whitelist;',
            'black' => 'select ip from blacklist;',
            'temp'  => 'select ip from blocked where stime+durt>=UNIX_TIMESTAMP()',
        );
        public $output;     //输出的语句
        private $support;
       
        function __construct() {
            $this->step = config::$step;
            $this->ids['white'] = config::$white_start;
            $this->ids['black'] = config::$black_start;
            $this->ids['temp'] = config::$temp_start;
           
            $this->sets['white'] = config::$white_set;
            $this->sets['black'] = config::$black_set;
            $this->sets['temp'] = config::$temp_set;
           
            $this->dt = config::$dellogtime;
            $this->ipfw = config::$ipfw;
            $this->debug = false;
            $this->debug = config::$debug;
            $this->debug_out = config::$debug_out;
           
            $this->filter = implode('\\|',config::$filter);
            $this->ignore = implode('\\|',config::$ignore);
           
            $this->checksystem();
        }
       
        private function checksystem() {
            $res = array();
            $this->support = false;
            if (php_sapi_name()!='cli') return false;
            @exec('/usr/bin/uname',$res);    //检测uname
            if ($this->debug) {
                    $dd = var_export($res,true);
                    $this->append($this->debug_out,'/usr/bin/uname'."\r\n".$dd."\r\n");
            }
            if (count($res)>0) {
                $res = array();
                @exec('/usr/bin/whoami',$res);
                //var_dump($res);
            if ($this->debug) {
                    $dd = var_export($res,true);
                    $this->append($this->debug_out,'/usr/bin/whoami'."\r\n".$dd."\r\n");
            }
            if ($res[0]=='root')
                    $this->support = true;
            }
            return $this->support;
        }
       
        function execute() {
            /**
             * 主调用程序,分析相应日志,并生成防火墙命令语句并执行
             */
             if (! $this->support) die('Your system is unable to support this program!this program need command line shell and root execute!');
             //windows系统直接退出,不让玩!
             $this->loaddata();     //从日志文件中取得相应IP资料
             //echo "1";
             $this->delete_old_data();  //删除过期数据
             //echo "2";
             $t = $this->exec_filter();      //执行过滤语句
             //var_dump($t);
             //echo date('Y-m-d H:i:s');
             return date('Y-m-d H:i:s');
        }
       
        function init_DB() {
            $this->DB = new cls_mysql($this->vars['dbhost'],$this->vars['dbuser'],$this->vars['dbpass'],$this->vars['dbname']);
        }
       
        private function checklist($ip) {
            /**
             * 检查是否在黑白名单中,在的话返回True
             */
             $res = false;
             $sql = "select count(*) from ((select DISTINCT ip from whitelist) union (select DISTINCT ip from blacklist)) a where ip='{$ip}';";
             $rs = $this->DB->getOne($sql,MYSQL_NUM);
             if (intval($rs[0])>0) $res = true;
             return $res;
        }
       
        private function loaddata() {
            /**
             * 读取日志数据
             */
            
             foreach (config::$list as $v) {
                 $res = array();
                 $dstr = date('d/M/Y');
                 //$dstr = '19/Dec/2014';       //DEBUG专用
                 $p = '/^\s*(\d+)\s+([\d\.]+)$/';
                 $cmd = "/bin/cat {$v} | /usr/bin/grep -i '{$dstr}' | /usr/bin/grep -i '{$this->filter}' | /usr/bin/grep -vi '{$this->ignore}' | /usr/bin/awk '{print \$1}' | /usr/bin/sort | /usr/bin/uniq -c | /usr/bin/sort -nr ";
                 if ($this->debug) $this->append($this->debug_out,$cmd);
                 @exec($cmd,$res);
                       if ($this->debug) {
                               $dd = var_export($res,true);
                               $this->append($this->debug_out,"\r\n".$dd."\r\n");
                       }
                 foreach ($res as $v) {
                     preg_match($p,$v,$t);
                     $cc = 0;
                     if (intval($t[1])>=config::$blockvalue) {
                         //超过阀值,加入数据库,加入前先检查是否已有数据
                         //如果在黑名单中,应该就不会出现在这里了吧?
                         $ip = trim($t[2]);
                         $sql = "select count(id) from blocked where ip='{$ip}' and stime>UNIX_TIMESTAMP(DATE_FORMAT(now(),'%Y-%m-%d 0:0:0'))";
                         $rs = $this->DB->getOne($sql,MYSQL_NUM);
                         if ($rs[0]<=0) {
                             //当天没数据,加!
                             if (! $this->checklist($ip)) {
                                 //检测是否在黑白名单中
                                 $max = max(config::$temp_durt);
                                 $sql = "select count(id) from blocked where ip='{$ip}' and stime<UNIX_TIMESTAMP(DATE_FORMAT(now(),'%Y-%m-%d 0:0:0')) and stime>=UNIX_TIMESTAMP(DATE_FORMAT(DATE_SUB(now(),INTERVAL {$max} DAY),'%Y-%m-%d 0:0:0'))";      //N天内
                                 $rs = array();
                                 $rs = $this->DB->getOne($sql,MYSQL_NUM);
                                 if ($rs[0]>=0) {
                                     $cc = intval($rs[0]);
                                 }
                                
                                 $ary = array('ip'=>$ip,'stime'=>time());

                                 $ary['durt'] = config::$temp_durt[$cc];
                                 if ($ary['durt']==-1) {
                                     $this->DB->do_insert('blacklist',array('ip'=>$ip,'addtime'=>time()));
                                 }else{
                                         $ary['durt'] *= 24*60*60;
                                    $this->DB->do_insert('blocked',$ary);
                                 }
                             }
                         }
                     }
                 }
             }
        }
       
        private function delete_old_data() {
            /**
             * 删除过期数据 原设置为31天
             *
             * @var mixed
             */
            if ($this->dt >=0) {
                $sql = "delete from blocked where stime+durt+{$this->dt} < UNIX_TIMESTAMP();";
                //echo $sql;
                $this->DB->execute($sql);
            }
        }
       
        private function buildall() {
            /**
             * 从数据库取所有IP数据,返回IPFW语句
             *
             */
             $res = '';
             foreach ($this->type as $v) {
                 $res .= "{$this->ipfw} delete set {$this->sets[$v]};";
                 $res .= $this->buildrule($v);
             }
             $this->output = $res;
             return $res;
        }
       
        private function exec_filter($len=1024) {
            //echo 'here!';
            if ($this->output=='') $this->buildall();
            if ($this->output=='') return 'NULL';
            //var_dump($this->output);
            $str = $this->output;
            $res = array();
            while (strlen($str)>0) {
                if (strlen($str)<=$len) {
                    $res[] = $str;
                    @exec($str);
                    $str = '';
                }else{
                    $tmp = substr($str,0,$len);
                    $pos =strrpos($tmp,';');
                    $out = substr($tmp,0,$pos+1);
                    $res[] = $out;
                    @exec($out);
                    $str = substr($str,$pos+1);
                }
            }
            if ($this->debug) {
                     $dd = var_export($res,true);
                     $this->append($this->debug_out,"exec_filter\r\n".$dd."\r\n");
            }
            return $res;
        }
       
        private function buildrule($type) {
            /**
             * 生成相应ipfw语句
             *
             * Jamers 2015.1.5
             */
            $res = '';
            if (isset($this->qsql[$type])) {
                $sql = $this->qsql[$type];
                $rs = $this->DB->getAll($sql,MYSQL_NUM);
                if ($rs) {
                foreach ($rs as $v) {
                    $opt = strtolower($type)=='white'?'allow':'deny';
                    $res .= "{$this->ipfw} -q add {$this->ids[$type]} set {$this->sets[$type]} {$opt} ip from {$v[0]} to me;";
                    $this->ids[$type]+=$this->step;
                }
                return $res;
               }else{
                       return '';
               }
            }else{
                return '';
            }
        }
       
        private function append($file,$str) {
                $fp = fopen($file,'a');
                fwrite($fp,$str);
                fclose($fp);
        }
    }
?>

firewall.php 调用主程序

<?php
DEFINED('ROOT_DIR') || DEFINE('ROOT_DIR',dirname(__FILE__));

    include_once(ROOT_DIR.'/cls_firewall.php');
    $fw = new cls_firewall();   
   
    if (isset($INFO)) {
        $fw->vars = $INFO;
        $fw->init_DB();
    }else{
        die('system config error!');
    }
   
    echo $fw->execute();
?>

firewall.rar

Edited by Jamers
经过调试,修正部分问题。
Link to comment
Share on other sites

经过两天的测试,发现一些问题,正常shell内执行的时候一切正常,但是放到crontab中执行老是反应,后经过调试,发现由于未指定完全路径,导致文件无法找到,重新修正相应文件,将原帖内容和附件一同修改。

 

今后开发系统内直接执行的脚本时必须使用绝对路径。。谨记!

Link to comment
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...