191 lines
5.4 KiB
PHP
191 lines
5.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
// Simple firewall to protect DDOS attack
|
||
|
|
// It's better to use
|
||
|
|
// https://github.com/terrylinooo/shieldon
|
||
|
|
// https://github.com/antonioribeiro/firewall
|
||
|
|
// https://packagist.org/packages/middlewares/firewall
|
||
|
|
|
||
|
|
|
||
|
|
namespace Hura8\System;
|
||
|
|
|
||
|
|
|
||
|
|
use Jaybizzle\CrawlerDetect\CrawlerDetect;
|
||
|
|
|
||
|
|
class Firewall
|
||
|
|
{
|
||
|
|
|
||
|
|
const MAX_CONCURRENT_VISIT_ALLOW = 5;
|
||
|
|
const WHITE_LIST_IP = ROOT_DIR . "/config/system/white_list_ip.txt";
|
||
|
|
const BAN_IP_LIST = ROOT_DIR . "/config/build/ban_ip_list.txt";
|
||
|
|
const TEMPORARY_BAN_DURATION = 60; //seconds
|
||
|
|
|
||
|
|
/* @var $instance Firewall */
|
||
|
|
protected static $instance;
|
||
|
|
protected $log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||
|
|
protected $ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||
|
|
private $user_ip = '';
|
||
|
|
protected $white_list = [
|
||
|
|
// google bot ips
|
||
|
|
// bing bots
|
||
|
|
// others..
|
||
|
|
];
|
||
|
|
|
||
|
|
protected function __construct() {
|
||
|
|
// todo: use Redis for performance instead of directory
|
||
|
|
/*$this->log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||
|
|
$this->ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||
|
|
if(!file_exists($this->log_dir)) {
|
||
|
|
mkdir($this->log_dir, 0755, true);
|
||
|
|
}
|
||
|
|
if(!file_exists($this->ban_dir)) {
|
||
|
|
mkdir($this->ban_dir, 0755, true);
|
||
|
|
}*/
|
||
|
|
$this->user_ip = getIpAddress();
|
||
|
|
|
||
|
|
if(file_exists(self::WHITE_LIST_IP)) {
|
||
|
|
$this->white_list = array_filter(explode("\n", file_get_contents(self::WHITE_LIST_IP)));
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
public static function getInstance() {
|
||
|
|
if(!static::$instance) {
|
||
|
|
static::$instance = new self();
|
||
|
|
}
|
||
|
|
|
||
|
|
return static::$instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
// check if current user agent is an crawler
|
||
|
|
public function isCrawler() : bool {
|
||
|
|
$objCrawlerDetect = new CrawlerDetect();
|
||
|
|
//echo $objCrawlerDetect->getUserAgent();
|
||
|
|
// https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers
|
||
|
|
return $objCrawlerDetect->isCrawler();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function monitor() {
|
||
|
|
// todo:
|
||
|
|
return false;
|
||
|
|
|
||
|
|
$total_concurrent_visit = $this->logIp($this->user_ip);
|
||
|
|
if($total_concurrent_visit > self::MAX_CONCURRENT_VISIT_ALLOW) {
|
||
|
|
// ban if not in whitelist
|
||
|
|
if(!$this->isWhiteListed()) {
|
||
|
|
$this->banTemporary($this->user_ip, self::TEMPORARY_BAN_DURATION);
|
||
|
|
}
|
||
|
|
|
||
|
|
die("System overload");
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function stopIfBanned() {
|
||
|
|
if($this->isIPBanned()) {
|
||
|
|
die("Perhaps, this website is not for you.");
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function isWhiteListed() {
|
||
|
|
$filter = new IPFilter($this->white_list);
|
||
|
|
return $filter->check($this->user_ip);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function isIPBanned() {
|
||
|
|
|
||
|
|
// check whitelist
|
||
|
|
if($this->isWhiteListed()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// check temporary ban
|
||
|
|
/*$file_id = $this->ban_dir . $this->user_ip.'.data';
|
||
|
|
if(file_exists($file_id)) {
|
||
|
|
$lift_time = file_get_contents($file_id);
|
||
|
|
// lift_time is not over yet
|
||
|
|
if($lift_time > time()) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}*/
|
||
|
|
|
||
|
|
// permanent ban
|
||
|
|
$config_file = static::BAN_IP_LIST;
|
||
|
|
if(@file_exists( $config_file )) {
|
||
|
|
$list_ips = @file_get_contents( $config_file );
|
||
|
|
$list_ip_arr = array_filter(explode("\n", $list_ips));
|
||
|
|
$filter = new IPFilter($list_ip_arr);
|
||
|
|
|
||
|
|
return $filter->check($this->user_ip);
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ban an IP temporarily
|
||
|
|
protected function banTemporary($ip, $time) {
|
||
|
|
$file_id = $this->ban_dir . $ip.'.data';
|
||
|
|
@file_put_contents($file_id, time() + $time);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// only allow 1 of these formats:
|
||
|
|
// - Full IP: 127.0.0.1
|
||
|
|
// - Wildcard: 172.0.0.*
|
||
|
|
// - Mask: 125.0.0.1/24
|
||
|
|
// - Range: 125.0.0.1-125.0.0.9
|
||
|
|
public static function validateIP($ip) {
|
||
|
|
// - Full IP: 127.0.0.1
|
||
|
|
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// - Wildcard: 172.0.0.*
|
||
|
|
if (strpos($ip, "*") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.(([0-9]+)|\*)\.(([0-9]+)|\*)$/i", $ip)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// - Mask: 125.0.0.1/24
|
||
|
|
if (strpos($ip, "/") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)$/i", $ip)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// - Range: 125.0.0.1-125.0.0.9
|
||
|
|
if (strpos($ip, "-") !== false ) {
|
||
|
|
list($begin, $end) = explode('-', $ip);
|
||
|
|
return filter_var($begin, FILTER_VALIDATE_IP) && filter_var($end, FILTER_VALIDATE_IP);
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private function logIp($ip) {
|
||
|
|
$file_id = $this->log_dir . $ip.'.data';
|
||
|
|
$time_track = CURRENT_TIME - CURRENT_TIME % 5; // track within 5 seconds,
|
||
|
|
$data = [];
|
||
|
|
if(file_exists($file_id)) {
|
||
|
|
$data = \json_decode(file_get_contents($file_id), true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(isset($data[$time_track])) {
|
||
|
|
$data[$time_track] = $data[$time_track] + 1;
|
||
|
|
}else{
|
||
|
|
$data[$time_track] = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// only log for current time to prevent large $data's size
|
||
|
|
// our purpose is only to detect if this IP is brutally forceing the system (too many visits per time)
|
||
|
|
|
||
|
|
@file_put_contents($file_id, \json_encode($data));
|
||
|
|
|
||
|
|
return $data[$time_track];
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|