| 知乎专栏 |
应用防火墙用于保护应用及服务不受恶意访问和攻击。
应用防火墙有别于网络防火墙,防火墙防火墙偏重对IP地址和端口端访问控制。
应用防火墙有有别于7层防火墙,7层防火墙虽然能实现拆包,根据协议,做出访问控制。
应用防火墙的核心功能除了局别7层防火墙的特点,颗粒度可以做到更细。
例如开发过程中我们有很多需求,直接在功能模块中实现。所谓应用防火墙就是将这些功能做成一个独立模块,实现共享和复用。
计数器的需求很常见,功能简单,就是记录访问数量,计数器也是水军主要战场。
计数器需求:
对于网防火墙可以通过IP访问策略进行封杀,但是我国由于IP地址有限,主要的上网方式是NAT(网络地址转换),例如一个公司的办公室内所有电脑都是通过一个IP地址出去的。封杀IP地址容易误伤。
使用应用防火墙就容易很多,可以使用用户+COOKIE+IP地址的方案。
应用防火墙我提供了一个思路,不便提供代码。
下面的代码是10年前写的,没有100%实现,因为该代码不会影响竞业,供大家参考。
<?php
/*
* =====================================
* Website: http://netkiller.github.com
* Author: neo <netkiller@msn.com>
* Email: netkiller@msn.com
* =====================================
*/
class Logging {
protected $file;
public function __construct($logfile = "/tmp/debug.log"){
$this->file = fopen($logfile,"a+");
}
public function __destruct() {
//fclose($this->file);
}
public function close() {
fclose($this->file);
}
private function write($msg){
fwrite($this->file,date('Y-m-d H:i:s').' '.$msg."\r\n");
}
public function info($msg){
$this->write(__FUNCTION__.' '.$msg);
}
public function warning($msg){
$this->write(__FUNCTION__.' '.$msg);
}
public function error($msg){
$this->write(__FUNCTION__.' '.$msg);
}
public function debug($msg){
$this->write(__FUNCTION__.' '.$msg);
}
}
class Permission{
protected $_PERMISSION = array();
public function __construct($login){
$test =
array(
'neo' => array(
'News'=> array(
'add' => 'Y',
'remove' => 'N',
'update' => 'Y'
),
'RSS'=> array(
'add' => 'Y',
'remove' => 'N',
'update' => 'Y'
)
),
'jam' => array(
'News'=> array(
'add' => 'Y',
'remove' => 'N',
'update' => 'Y'
),
'RSS'=> array(
'add' => 'Y',
'remove' => 'N',
'update' => 'Y'
)
)
);
//print_r($test);
$this->load($test[$login]);
}
public function load($arr){
$this->_PERMISSION = $arr;
}
public function is_allowed($class, $fun){
$class = trim($class);
$fun = trim($fun);
//echo $class, $fun;
//print_r($this->_PERMISSION);
if(array_key_exists($class,$this->_PERMISSION)){
if(array_key_exists($fun,$this->_PERMISSION[$class])){
if($this->_PERMISSION[$class][$fun] == 'Y') return true;
//return in_array("Y",$this->_PERMISSION[$class][$fun]);
}
}
return false;
}
public function is_denied($class, $fun){
return (!$this->is_allowed($class, $fun));
}
public function scan(){
return true;
}
}
class News extends Permission{
private $logging;
public function __construct(){
parent::__construct('neo');
$this->logging = new Logging('/tmp/news.log');
}
public function __destruct() {
$this->logging->debug('news->get permission denied!!!');
$this->logging->close();
}
public function add(){
if(!$this->is_allowed(__CLASS__,__FUNCTION__)) return;
print("Allowed!!! \r\n");
$this->logging->info('news->add ok');
}
public function get(){
if( $this->is_denied(__CLASS__,__FUNCTION__)) {
print("Denied!!! \r\n");
$this->logging->warning('news->get permission denied!!!');
}
}
}
$news = new News();
$news->add();
$news->get();
权限来自下面数组数据,这里仅仅提供一个例子,管理权限你可以单独实现一个class,实现供权限管理功能,最终后转化为下面的数据结构即可。例如你可以将权限写入数据库,最终拼装如下数字让Permission顺利load即可。
array( 'neo' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ), 'jam' => array( 'News'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ), 'RSS'=> array( 'add' => 'Y', 'remove' => 'N', 'update' => 'Y' ) ) );
public function is_allowed($class, $fun) 用户判断权限是否合法。
这里提供了一个 News 类,用于演示怎样控制每个function的权限。
同时还提供了一个简单的 Logging 类用于记录程序运行日志。
有了上面的例子就可以将News应用于SOAP一类Web Service上,用来控制每个方法的权限
上面仅仅对于方法控制权限,接下来我们为程序增加7层防火墙功能
<?php
/*
* =====================================
* Website: http://netkiller.github.com
* Author: neo <netkiller@msn.com>
* Email: netkiller@msn.com
* =====================================
*/
class Firewall{
protected $status;
protected $policy;
protected $chain;
protected $rule;
protected $match;
private $debug;
//$get,$post,$cookie,$server;
public function __construct() {
$this->name = "Firewall";
}
public function __destruct() {
//print "Destroying " . $this->name . "\n";
}
public function enable(){
$this->status = true;
}
public function disable(){
$this->status = false;
}
public function get(){
if($this->status){
$this->chain = $_GET;
return($this);
}else{
return($this->status);
}
}
public function post(){
if($this->status){
$this->chain = $_GET;
return($this);
}else{
return($this->status);
}
$this->chain = $_POST;
}
public function cookie() {
if($this->status){
$this->chain = $_COOKIE;
return($this);
}else{
return($this->status);
}
}
public function server(){
if($this->status){
$this->chain = $_SERVER;
return($this);
}else{
return($this->status);
}
}
public function match($key, $value){
if($this->debug) print_r($this->chain);
$this->match = false;
if(!array_key_exists($this->chain, $key)){
if($this->chain[$key] == $value){
$this->match = true;
}
}
return($this);
}
public function policy($p){
$this->policy = $p;
}
public function counter($tm, $cnt){
return($this);
}
public function allow($fun = null){
if($this->status && $this->match){
if($fun){
$fun();
}
}
$this->destroy();
return($this->status);
}
public function deny($fun = null){
if($this->status && $this->match){
if($fun){
$fun();
}
}
$this->destroy();
return($this->status);
}
public function debug($tmp){
$this->debug = $tmp;
}
public function ip($ipaddr){
return $this->server()->match('REMOTE_ADDR', $ipaddr);
}
public function destroy(){
$this->chain = array();
$this->match = false;
}
};
#include_once('firewall.php')
$fw = new Firewall();
$fw->debug(true);
$fw->debug(false);
$fw->enable();
//$fw->disable();
function test(){
echo 'OK';
};
function allow(){
echo 'allow';
};
function deny(){
echo 'deny';
};
//$fw->policy('blacklist');
$fw->ip('192.168.3.17')->allow('allow');
$fw->ip('192.168.3.17')->deny('deny');
$fw->counter('1m',5)->match('id','1000')->deny('test');
/*
$fw->ip('172.16.0.0/24')->allow();
$fw->ip('172.16.0.0','255.255.255.0')->allow();
$fw->header(array('User-Agent' => 'MSIE5'))->deny()
*/
$fw->get()->match('id','1000')->deny('test');
$fw->get()->match('name','chen')->allow('test');
//$fw->get()->match(array('id' => '1000'))->deny();
/*
$fw->post()->data(array('action'=>'/login.php'))->allow()
$fw->cookie()->data(array('userid'=>'test'))->deny()
*/
$fw->server()->match('HTTP_REFERER', 'http://www.mydomain.com/index.html')->allow('test');
$fw->server()->match('REQUEST_METHOD', 'GET')->deny('test');
$fw->disable();
//$fw->destroy();
这里仅仅给你一个思路,我并没有写完程序。例如控制IP请求次数可以如下实现,请自行改善程序
<?php
/*
* =====================================
* Website: http://netkiller.github.com
* Author: neo <netkiller@msn.com>
* Email: netkiller@msn.com
* =====================================
*/
require 'SharedConfigurations.php';
$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 0
);
$multiple_servers = array(
array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 15,
'alias' => 'first',
),
array(
'host' => '127.0.0.1',
'port' => 6380,
'database' => 15,
'alias' => 'second',
),
);
$client = new Predis\Client($single_server, array('prefix' => 'fw:'));
$key=$_SERVER['REMOTE_ADDR'];
if(!$client->exists($key)){
$client->setex($key, 20, 1);
}else{
$client->incrby($key,1);
}
$counter = $client->get($key);
if($counter > 10){
echo 'Deny';
}
print_r($client->get($key));
//var_dump($client->keys('*'));