<?php
/**
* @version $Id: defender.class.php 2403 2021-04-10 17:15:42Z IOS $
* @package Elxis
* @copyright Copyright (c) 2006-2021 Elxis CMS (https://www.elxis.org). All rights reserved.
* @license Elxis Public License ( https://www.elxis.org/elxis-public-license.html )
* @author Elxis Team ( https://www.elxis.org )
* @description Elxis CMS is free software. Read the license for copyright notices and details
*/
defined('_ELXIS_') or die ('Direct access to this location is not allowed.');
$defstart = microtime(true);
if (isset($_SERVER['QUERY_STRING'])) {
if (strpos(urldecode($_SERVER['QUERY_STRING']), chr(0)) !== false) {
exitPage::make('security', 'DEF-0001');
}
if (preg_match("#((\%0d)|(\%0a)|(\\\r)|(\\\n))#", $_SERVER['QUERY_STRING'])) {
exitPage::make('security', 'DEF-0002', 'Possible CRLF injection/HTTP response split.');
}
}
if (!empty($_COOKIE)) {
if (preg_match("#((\%0d)|(\%0a)|(\\\r)|(\\\n))#", serialize($_COOKIE))) {
exitPage::make('security', 'DEF-0003', 'Possible CRLF injection/HTTP response split.');
}
}
class elxisDefender {
private $cfg = NULL;
private $types = array();
private $repo_path = '';
private $query = '';
private $requesturi = '';
private $address = '';
private $host = '';
private $referer = '';
private $useragent = '';
private $rawpost = '';
private $bantimes = 3;
private $banmessage = '';
private $ipcheckafter = false;
private $triggered_rule = '';
private $banduration = 864000;//in seconds, by default 10 days
/*********************/
/* MAGIC CONSTRUCTOR */
/*********************/
public function __construct() {
elxisLoader::loadFile('configuration.php');
$this->cfg = new elxisConfig();
$this->repo_path = rtrim($this->cfg->get('REPO_PATH'), '/');
if ($this->repo_path == '') { $this->repo_path = ELXIS_PATH.'/repository'; }
if (!file_exists($this->repo_path.'/logs/') || !is_dir($this->repo_path.'/logs/')) {
die('Folder logs in Elxis repository does not exist!');
}
if ($this->cfg->get('SECURITY_LEVEL', 0) > 0) {
$this->ipcheckafter = false;
} else {
$this->ipcheckafter = ($this->cfg->get('DEFENDER_IPAFTER', 1) == 1) ? true : false;
}
$this->setTypes();
if (count($this->types) == 0) { return; }
if (isset($_SERVER['QUERY_STRING'])) {
$this->query = strtolower(urldecode($_SERVER['QUERY_STRING']));
$this->query = htmlspecialchars_decode($this->query, ENT_QUOTES);
}
$this->requesturi = strtolower(urldecode($this->getURI()));
$this->requesturi = htmlspecialchars_decode($this->requesturi, ENT_QUOTES);
$this->address = $this->getIP();
if ($this->address == '') {
$this->securityLogger(true, 'DEFB-0002', 'Empty IP address', 'IP');
exitPage::make('security', 'DEFB-0002', 'Your IP address could not be detected!');
}
if (strpos($this->address, ':') !== false) { //IPv6
if (filter_var($this->address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
$this->securityLogger(true, 'DEFB-0003', 'Invalid IP v6 address', 'IP');
exitPage::make('security', 'DEFB-0003', 'Your IP address is not valid IPv6!');
}
} else { //IPv4
if (filter_var($this->address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
$this->securityLogger(true, 'DEFB-0004', 'Invalid IP v4 address', 'IP');
exitPage::make('security', 'DEFB-0004', 'Your IP address is not valid IPv4!');
}
}
if (function_exists('gethostbyaddr') && is_callable('gethostbyaddr')) {
$this->host = strtolower(gethostbyaddr($this->address));
}
if (isset($_SERVER['HTTP_REFERER'])) {
$this->referer = strtolower($_SERVER['HTTP_REFERER']);
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$this->useragent = strtolower(urldecode($_SERVER['HTTP_USER_AGENT']));
$this->useragent = htmlspecialchars_decode($this->useragent, ENT_QUOTES);
}
if (isset($_SERVER['REQUEST_METHOD'])) {
if (($_SERVER['REQUEST_METHOD'] == 'PUT') || ($_SERVER['REQUEST_METHOD'] == 'DELETE') || ($_SERVER['REQUEST_METHOD'] == 'TRACE')) {
$msg = 'Request method '.$_SERVER['REQUEST_METHOD'].' is not allowed';
$this->securityLogger(true, 'DEFB-0005', $msg, 'OTHER');
exitPage::make('security', 'DEFB-0005', $msg);
}
}
if (!empty($_POST)) {
$this->rawpost = file_get_contents('php://input');
$this->rawpost = strtolower(substr($this->rawpost, 0, 4194304));
}
}
/***************************/
/* PERFORM SECURITY CHECKS */
/***************************/
public function check() {
if (count($this->types) == 0) { return; }
$this->checkBanned();
if ($this->cfg->get('DEFENDER_WHITELIST', '') != '') {
$parts = explode(',', $this->cfg->get('DEFENDER_WHITELIST', ''));
if (in_array($this->address, $parts)) { return; }
}
if ($this->cfg->get('SECURITY_LEVEL', 0) > 0) {
if (isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'POST')) {
if ($this->referer == '') {//If POST, then there must be a referer!
//Be careful: causes problems on third party services like Paypal IPN
$this->securityLogger(true, 'DEFB-0006', 'Empty HTTP REFERER on POST request', 'POST');
exitPage::make('security', 'DEFB-0006', 'Empty HTTP REFERER on POST request!');
}
}
if ($this->useragent == '') {
$this->securityLogger(true, 'DEFB-0007', 'Empty user agent!', 'AGENT');
exitPage::make('security', 'DEFB-0007', 'Empty user agent!');
}
}
if (!$this->ipcheckafter) {
if (in_array('R', $this->types)) {//IP RANGES
$block = $this->checkAutoIPRanges();
if ($block) {
$this->banIP('DEFR-AUTO');
$this->securityLogger(true, 'DEFR-AUTO', 'Blacklisted IP banned. Checked using IP ranges.', 'IP');
if ($this->cfg->get('DEFENDER_NOTIFY') == 2) {
$mailmsg = "IP address ".$this->address." belongs to a blacklisted IPs range!\r\n";
$mailmsg .= "The list of blacklisted IP ranges is updated automatically every 24 hours.\r\n";
$this->sendAlert('SEC-DEFR-AUTO', $mailmsg, 1);
}
exitPage::make('security', 'DEFR-AUTO', 'Your IP address is blacklisted!');
}
unset($block);
}
if (in_array('I', $this->types)) {//SPECIFIC IPs
$block = $this->checkAutoIP();
if ($block) {
$this->banIP('DEFI-AUTO');
$this->securityLogger(true, 'DEFI-AUTO', 'Blacklisted IP banned. Checked using IPs list.', 'IP');
if ($this->cfg->get('DEFENDER_NOTIFY') == 2) {
$mailmsg = "IP address ".$this->address." is blacklisted!\r\n";
$mailmsg .= "The list of blacklisted IPs is updated automatically every 12 hours.\r\n";
$this->sendAlert('SEC-DEFI-AUTO', $mailmsg, 1);
}
exitPage::make('security', 'DEFI-AUTO', 'Your IP address is blacklisted!');
}
unset($block);
}
}
//Check for ascii chars 0-31 and 127
$pat = '@[\x00-\x1F\x7F]@u';
//For POST and USER AGENT exclude Horizontal Tab, Line Feed and Carriage Return
$patpost = '@[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]@u';
$block = false;
$blockmsg = '';
$gvar = '';
if (!empty($_GET)) {
foreach ($_GET as $k => $v) {
if (is_array($v)) {
foreach ($v as $ka => $va) {
if (is_numeric($va)) { continue; }
if (is_array($va)) {
$testva = '';
array_walk_recursive($va, function($xva, $xk) use (&$testva) { $testva .= $xva.','; });
} else {
$testva = $va;
}
$v2 = preg_replace($pat, '', $testva);
if (strcmp($v2, $testva) <> 0) {
$block = true;
$gvar = 'GET';
$blockmsg = 'Unacceptable ASCII character in '.$gvar;
break 2;
}
}
} else {
if (is_numeric($v)) { continue; }
$v2 = preg_replace($pat, '', $v);
if (strcmp($v2, $v) <> 0) {
$block = true;
$gvar = 'GET';
$blockmsg = 'Unacceptable ASCII character in '.$gvar;
break;
}
}
}
}
if (!$block && !empty($_POST)) {
foreach ($_POST as $k => $v) {
if (is_array($v)) {
foreach ($v as $ka => $va) {
if (is_numeric($va)) { continue; }
if (is_array($va)) {
$testva = '';
array_walk_recursive($va, function($xva, $xk) use (&$testva) { $testva .= $xva.','; });
} else {
$testva = $va;
}
$v2 = preg_replace($patpost, '', $testva);
if (strcmp($v2, $testva) <> 0) {
$block = true;
$gvar = 'POST';
$blockmsg = 'Unacceptable ASCII character in '.$gvar;
break 2;
}
}
} else {
if (is_numeric($v)) { continue; }
$v2 = preg_replace($patpost, '', $v);
if (strcmp($v2, $v) <> 0) {
$block = true;
$gvar = 'POST';
$blockmsg = 'Unacceptable ASCII character in '.$gvar;
break;
}
}
}
}
if (!$block && ($this->useragent != '')) {
$v2 = preg_replace($patpost, '', $this->useragent);
if (strcmp($v2, $this->useragent) <> 0) {
$block = true;
$gvar = 'USER AGENT';
$blockmsg = 'Unacceptable ASCII character in '.$gvar;
}
}
if ($block) {
$this->banIP('DEFB-0008');
$this->securityLogger(true, 'DEFB-0008', $blockmsg, $gvar);
if ($this->cfg->get('DEFENDER_NOTIFY') > 0) {
$mailmsg = "Elxis Defender blocked an attack to your site!\r\n";
$mailmsg .= "Elxis Defender report\r\n";
$mailmsg .= $blockmsg;
$this->sendAlert('SEC-DEFB-0008', $mailmsg, 1);
}
exitPage::make('security', 'DEFB-0008', $blockmsg);
}
unset($pat, $patpost, $block, $gvar, $blockmsg);
foreach ($this->types as $type) {
switch($type) {
case 'G': $this->checkRules('general'); break;
case 'C': $this->checkRules('custom'); break;
case 'F': $this->checkFS(); break;
case 'I': break; //checked previously with checkIP
case 'R': break; //checked previously with checkIPRanges
default: break;
}
}
}
/***********************************************************/
/* RE-CHECK ONLY FOR AUTO IPS AFTER SESSION HAS INITIALIZE */
/***********************************************************/
public function reCheck() {
if (!class_exists('elxisSession', false)) { return; }
if (!$this->ipcheckafter) { return; }
if ($this->cfg->get('DEFENDER_WHITELIST', '') != '') {
$parts = explode(',', $this->cfg->get('DEFENDER_WHITELIST', ''));
if (in_array($this->address, $parts)) { return; }
}
$eSession = eFactory::getSession();
$checked = (int)$eSession->get('elxisdefips', '0');
if ($checked == 1) { return; }
$eSession->set('elxisdefips', '1');
if (in_array('R', $this->types)) {//IP RANGES
$block = $this->checkAutoIPRanges();
if ($block) {
$this->banIP('DEFR-AUTO');
$this->securityLogger(true, 'DEFR-AUTO', 'Blacklisted IP banned. Checked using IP ranges.', 'IP');
if ($this->cfg->get('DEFENDER_NOTIFY') == 2) {
$mailmsg = "IP address ".$this->address." belongs to a blacklisted IPs range!\r\n";
$mailmsg .= "The list of blacklisted IP ranges is updated automatically every 24 hours.\r\n";
$this->sendAlert('SEC-DEFR-AUTO', $mailmsg, 1);
}
exitPage::make('security', 'DEFR-AUTO', 'Your IP address is blacklisted!');
}
unset($block);
}
if (in_array('I', $this->types)) {//SPECIFIC IPs
$block = $this->checkAutoIP();
if ($block) {
$this->banIP('DEFI-AUTO');
$this->securityLogger(true, 'DEFI-AUTO', 'Blacklisted IP banned. Checked using IPs list.', 'IP');
if ($this->cfg->get('DEFENDER_NOTIFY') == 2) {
$mailmsg = "IP address ".$this->address." is blacklisted!\r\n";
$mailmsg .= "The list of blacklisted IPs is updated automatically every 12 hours.\r\n";
$this->sendAlert('SEC-DEFI-AUTO', $mailmsg, 1);
}
exitPage::make('security', 'DEFI-AUTO', 'Your IP address is blacklisted!');
}
unset($block);
}
}
/*******************/
/* SET CHECK TYPES */
/*******************/
private function setTypes() {
if (trim($this->cfg->get('DEFENDER')) != '') {
$this->types = str_split($this->cfg->get('DEFENDER'));
if (!in_array('C', $this->types)) { $this->types[] = 'C'; }
}
switch ($this->cfg->get('SECURITY_LEVEL')) {
case 1:
if (!in_array('G', $this->types)) { $this->types[] = 'G'; }
if (!in_array('C', $this->types)) { $this->types[] = 'C'; }
if (!in_array('F', $this->types)) { $this->types[] = 'F'; }
break;
case 2:
if (!in_array('G', $this->types)) { $this->types[] = 'G'; }
if (!in_array('C', $this->types)) { $this->types[] = 'C'; }
if (!in_array('I', $this->types)) { $this->types[] = 'I'; }
if (!in_array('R', $this->types)) { $this->types[] = 'R'; }
if (!in_array('F', $this->types)) { $this->types[] = 'F'; }
break;
case 0: default: break;
}
if (!$this->types) { return; }
if (in_array('R', $this->types)) {
$update_ips_db = true;
if (file_exists($this->repo_path.'/logs/defender_ip_ranges.php')) {
$ts = filemtime($this->repo_path.'/logs/defender_ip_ranges.php');
if ((time() - $ts) < 86400) { $update_ips_db = false; } //once per day
} else {
$fp = @fopen($this->repo_path.'/logs/defender_ip_ranges.php', 'w');
if ($fp) {
$ok = false;
if (flock($fp, LOCK_EX)) {
$ok = fwrite($fp, $txt);
flock($fp, LOCK_UN);
}
fclose($fp);
if ($ok) {
$this->securityLogger(false, '', 'Bad IP ranges database updated successfully', 'UPDATE');
} else {
$this->securityLogger(false, '', 'Bad IP ranges database update FAILED', 'UPDATE');
}
} else {
die('Could not create file logs/defender_ip_ranges.php in Elxis repository!');
}
}
if ($update_ips_db) { $this->updateIPDB(true); }
}
if (in_array('I', $this->types)) {
$update_ips_db = true;
if (file_exists($this->repo_path.'/logs/defender_ips.php')) {
$ts = filemtime($this->repo_path.'/logs/defender_ips.php');
if ((time() - $ts) < 43200) { $update_ips_db = false; } //twice per day
} else {
$fp = @fopen($this->repo_path.'/logs/defender_ips.php', 'w');
if ($fp) {
$ok = false;
if (flock($fp, LOCK_EX)) {
$ok = fwrite($fp, $txt);
flock($fp, LOCK_UN);
}
fclose($fp);
if ($ok) {
$this->securityLogger(false, '', 'Bad IPs list updated successfully', 'UPDATE');
} else {
$this->securityLogger(false, '', 'Bad IPs list update FAILED', 'UPDATE');
}
} else {
die('Could not create file logs/defender_ips.php in Elxis repository!');
}
}
if ($update_ips_db) { $this->updateIPDB(false); }
}
}
/******************************/
/* FILESYSTEM INTEGRITY CHECK */
/******************************/
private function checkFS() {
$files = $this->getLockFiles();
$hashfile = $this->repo_path.'/other/elxis_hashes_'.md5($this->cfg->get('ENCRYPT_KEY')).'.php';
if (!file_exists($hashfile)) {
$buffer = '<?php '._LEND._LEND;
$buffer .= '//Elxis Defender - Filesystem hash fingerprint generated on '.gmdate('Y-m-d H:i:s').' (UTC)'._LEND._LEND;
$buffer .= 'defined(\'_ELXIS_\') or die (\'Protected by Elxis Defender\');'._LEND._LEND;
$buffer .= '$hashes = array('._LEND;
foreach ($files as $file) {
$m = md5_file(ELXIS_PATH.'/'.$file);
$buffer.= "\t".'array(\''.$file.'\', \''.$m.'\'),'._LEND;
}
$buffer .= ');'._LEND._LEND;
$buffer .= '?>';
$ok = false;
if ($handler = @fopen($hashfile, 'w')) {
if (flock($handler, LOCK_EX)) {
$ok = @fwrite($handler, $buffer);
flock($handler, LOCK_UN);
}
fclose($handler);
}
if (!$ok) {
$msg = 'Elxis Defender could not save fingerprint (hashes) file! Please make sure Elxis repository is writable.';
exitPage::make('fatal', 'DEFF-0001', $msg);
}
return true;
}
include($hashfile);
if (!isset($hashes) || !is_array($hashes) || (count($hashes) == 0)) {
$msg = 'Elxis Defender detected an empty or invalid fingerprint (hashes) file! If you dont
know what to do consider Elxis documentation or visit Elxis forums for support.';
exitPage::make('fatal', 'DEFF-0002', $msg);
}
$i = 1;
foreach ($hashes as $hash) {
$f = $hash[0];
if (!in_array($f, $files)) {
$n = sprintf("%04d", $i);
$this->securityLogger(true, 'DEFF-'.$n, 'A protected file has been deleted! '.$f, 'FSDEL');
if ($this->cfg->get('DEFENDER_NOTIFY') > 0) {
$mailmsg = 'A protected file has been deleted!'."\r\n";
$mailmsg .= 'The deleted file is: '.$f."\r\n\r\n";
$mailmsg .= 'Actions to perform (pick one):'."\r\n";
$mailmsg .= '1. If the deletion made by an unauthorized person, or accidently by you, restore the original protected file.'."\r\n";
$mailmsg .= '2. If you accept this deletion then you must delete the Elxis hashes file in order for the Elxis Defender to regenarate it without the deleted file.'."\r\n\r\n";
$mailmsg .= "Elxis Defender hashes file: ".$hashfile."\r\n";
$mailmsg .= "The site wont come back online until you perform one of the above actions.\r\n";
$this->sendAlert('SEC-DEFF-'.$n, $mailmsg, 0);
}
$msg = 'A protected file has been deleted! If you dont know what
to do consider Elxis documentation or visit Elxis forums for support.';
exitPage::make('security', 'DEFF-'.$n, $msg);
}
$m = md5_file(ELXIS_PATH.'/'.$f);
if ($m != $hash[1]) {
$n = sprintf("%04d", $i);
$this->securityLogger(true, 'DEFF-'.$n, 'A protected file has been modified! '.$f, 'FSLOCK');
if ($this->cfg->get('DEFENDER_NOTIFY') > 0) {
$mailmsg = 'A protected file has been modified!'."\r\n";
$mailmsg .= 'The modified file is: '.$f."\r\n\r\n";
$mailmsg .= 'Actions to perform (pick one):'."\r\n";
$mailmsg .= '1. If the modification made by an unauthorized person, or accidently by you, restore the original protected file.'."\r\n";
$mailmsg .= '2. If you accept this modification then you must delete the Elxis hashes file in order for the Elxis Defender to regenarate it without the modified file.'."\r\n\r\n";
$mailmsg .= "Elxis Defender hashes file: ".$hashfile."\r\n";
$mailmsg .= "The site wont come back online until you perform one of the above actions.\r\n";
$this->sendAlert('SEC-DEFF-'.$n, $mailmsg, 0);
}
$msg = 'A protected file has been modified! If you dont know what
to do consider Elxis documentation or visit Elxis forums for support.';
exitPage::make('security', 'DEFF-'.$n, $msg);
}
$i++;
}
}
/*******************************************/
/* CHECK IF IP IS BANNED BY ELXIS DEFENDER */
/*******************************************/
private function checkBanned() {
if ($this->address == '') { return; }
$file = $this->repo_path.'/logs/defender_ban.php';
if (!file_exists($file)) { return; }
include($file);
if (!isset($ban) || !is_array($ban) || (count($ban) == 0)) { return; }
$ip = str_replace('.', 'x', $this->address);
$ip = str_replace(':', 'y', $ip);
if (isset($ban[$ip])) {
if ($ban[$ip]['times'] >= $this->bantimes) {
$msg = 'You have been banned! If you think this is wrong contact the site administrator.';
exitPage::make('security', 'DEFB-0001', $msg);
}
}
}
/************************/
/* CHECK BLOCKING RULES */
/************************/
private function checkRules($type) {
if (!file_exists(ELXIS_PATH.'/includes/libraries/elxis/defender/'.$type.'.rules.php')) { return; }
include(ELXIS_PATH.'/includes/libraries/elxis/defender/'.$type.'.rules.php');
if (!isset($rules) || !is_array($rules) || (count($rules) == 0)) { return; }
$this->triggered_rule = '';
foreach ($rules as $k => $rule) {
$methods = explode(',', $rule[0]);
foreach ($methods as $method) {
$func = 'check'.$method;
if ($this->$func($rule[1])) {
$char = strtoupper(substr($type, 0, 1));
$n = sprintf("%04d", $k);
$this->banIP('DEF'.$char.'-'.$n);
if ($this->triggered_rule != '') {
$logtxt = 'Request blocked, Method: '.$method.', Rule: '.$this->triggered_rule.', Reason: '.$rule[2];
} else {
$logtxt = 'Request blocked, Method: '.$method.', Reason: '.$rule[2];
}
$this->securityLogger(true, 'DEF'.$char.'-'.$n, $logtxt, $method);
$notify = false;
if ($this->cfg->get('DEFENDER_NOTIFY') == 2) {
$notify = true;
} elseif ($this->cfg->get('DEFENDER_NOTIFY') == 1) {
if (!in_array($method, array('AGENT', 'HOST', 'IP'))) {
$notify = true;
}
}
if ($notify) {
$mailmsg = "Rules: \t".$type."\r\n";
$mailmsg .= "Match where: \t".$method."\r\n";
$mailmsg .= "Regex match number: \t".($k+1)."\r\n";
if ($this->triggered_rule != '') {
$mailmsg .= "Match rule: \t".$this->triggered_rule."\r\n";
}
$mailmsg .= "Reason: \t".$rule[2]."\r\n";
if ($this->banmessage != '') { $mailmsg .= $this->banmessage."\r\n"; }
$this->sendAlert('SEC-DEF'.$char.'-'.$n, $mailmsg, 1);
}
exitPage::make('security', 'DEF'.$char.'-'.$n, $rule[2]);
}
}
}
}
/* CHECK POST DATA */
private function checkPOST($rule) {
if ($this->rawpost == '') { return false; }
if (preg_match('~'. $rule .'~', $this->rawpost)) {
preg_match('~'. $rule .'~', $this->rawpost, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
private function checkSESSION($rule) {
return false;//cannot check session before Elxis init
}
private function checkCOOKIE($rule) {
return false;//cannot check cookie
}
private function checkURI($rule) {
if ($this->requesturi == '') { return false; }
if (preg_match('~'. $rule .'~', $this->requesturi)) {
preg_match('~'. $rule .'~', $this->requesturi, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
private function checkREFERER($rule) {
if ($this->referer == '') { return false; }
if (preg_match('~'. $rule .'~', $this->referer)) {
preg_match('~'. $rule .'~', $this->referer, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
private function checkAGENT($rule) {
if ($this->useragent == '') { return false; }
if (preg_match('~'. $rule .'~', $this->useragent)) {
preg_match('~'. $rule .'~', $this->useragent, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
private function checkQUERY($rule) {
if ($this->query == '') { return false; }
if (preg_match('~'. $rule .'~', $this->query)) {
preg_match('~'. $rule .'~', $this->query, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
/***********************/
/* ALIAS OF checkQUERY */
/***********************/
private function checkGET($rule) {
return $this->checkQUERY($rule);
}
private function checkHOST($rule) {
if ($this->host == '') { return false; }
if (preg_match('~'. $rule .'~', $this->host)) {
preg_match('~'. $rule .'~', $this->host, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
private function checkIP($rule) {
if ($this->address == '') { return false; }
if (preg_match('~'. $rule .'~', $this->address)) {
preg_match('~'. $rule .'~', $this->address, $matches);
if (isset($matches[0]) && ($matches[0] != '')) { $this->triggered_rule = $matches[0]; }
return true;
}
return false;
}
/****************************************/
/* CHECK IP AGAINST DANGEROUS IP RANGES */
/****************************************/
private function checkAutoIPRanges() {
if (strpos($this->address, ':') !== false) { return; } //not supported IP v6
@include($this->repo_path.'/logs/defender_ip_ranges.php');
if (!isset($ips) || !is_array($ips) || (count($ips) == 0)) { return; }
$userip = sprintf('%u', ip2long($this->address));
$block = false;
foreach ($ips as $ip) {
if (($userip >= $ip[2]) && ($userip <= $ip[3])) {
$block = true;
break;
}
}
return $block;
}
/**********************************/
/* CHECK IP AGAINST DANGEROUS IPS */
/**********************************/
private function checkAutoIP() {
@include($this->repo_path.'/logs/defender_ips.php');
if (!isset($ips) || !is_array($ips) || (count($ips) == 0)) { return; }
$block = false;
foreach ($ips as $ip) {
if ($ip == $this->address) {
$block = true;
break;
}
}
return $block;
}
/*******************/
/* SEND MAIL ALERT */
/*******************/
private function sendAlert($code, $msg='', $attack=1) {
$file = $this->repo_path.'/logs/defender_notify.txt';
@clearstatcache();
if (!file_exists($file)) {
@touch($file);
$proceed = true;
} else {
$last = filemtime($file);
if ((time() - filemtime($file)) > 300) {
@touch($file);
$proceed = true;
} else {
$proceed = false;
}
}
if (!$proceed) { return; }
$uri = addslashes($this->requesturi);
$parsed = parse_url($this->cfg->get('URL'));
$host = preg_replace('#^(www\.)#i', '', $parsed['host']);
$subject = 'Message from Elxis Defender on '.$host;
if ($attack == 1) {
$text = "Elxis Defender blocked an attack to your site!\r\n";
} else {
$text = "Elxis Defender detected a change in site filesystem!\r\n";
}
$text .= 'Reference code: '.$code."\r\n";
if ($msg != '') { $text .= "\r\nElxis Defender report\r\n".$msg."\r\n"; }
$text .= "\r\n";
$text .= "Requested URI: \t".$uri."\r\n";
if ($this->address != '') { $text .= "IP address: \t".$this->address."\r\n"; }
if ($this->host != '') { $text .= "Hostname: \t".$this->host."\r\n"; }
if ($this->referer != '') { $text .= "HTTP Referrer: \t".$this->referer."\r\n"; }
if (isset($_SERVER['HTTP_USER_AGENT'])) { $text .= "User agent: \t".$_SERVER['HTTP_USER_AGENT']."\r\n"; }
if (!empty($_POST)) {
$text .= "\r\n";
$text .= '=== POST vars submitted ==='."\r\n";
foreach ($_POST as $k => $v) {
if (is_array($v)) {
$text .= addslashes($k).' = ARRAY'."\r\n";
} else if (strlen($v) > 200) {
$text .= addslashes($k).' = LONG STRING'."\r\n";
} else {
$text .= addslashes($k).' = '.addslashes($v)."\r\n";
}
}
$text .= "\r\n";
}
$text .= "Date (UTC): \t".gmdate('Y-m-d H:i:s')."\r\n";
$text .= "Site URL: \t".$this->cfg->get('URL')."\r\n\r\n\r\n\r\n";
$text .= "----------------------------------------------------------\r\n";
$text .= "Elxis Defender by Elxis Team\r\n";
$text .= 'Please do not reply to this message as it was generated automatically by the Elxis Defender ';
$text .= 'and it was sent for informational purposes. Elxis Defender will not send you an other notification for ';
$text .= 'the next 5 minutes even if more attacks occur.'."\r\n";
$text .= "----------------------------------------------------------\r\n";
require_once(ELXIS_PATH.'/includes/libraries/swift/swift_required.php');
$message = Swift_Message::newInstance();
$message->setCharset('UTF-8');
$message->setPriority(2);
$message->setSubject($subject);
$message->setBody($text, 'text/plain');
$message->addTo($this->cfg->get('MAIL_MANAGER_EMAIL'), $this->cfg->get('MAIL_MANAGER_NAME'));
if (($this->cfg->get('MAIL_METHOD') == 'smtp') && (strpos($this->cfg->get('MAIL_SMTP_HOST'), '.gmail.') !== false)) {
$message->setFrom(array($this->cfg->get('MAIL_FROM_EMAIL') => $this->cfg->get('MAIL_FROM_NAME')));
} else {
$message->setFrom(array('defender@'.$host => 'Elxis Defender'));
}
$headers = $message->getHeaders();
$headers->addTextHeader('X-Mailer', 'Elxis');
switch ($this->cfg->get('MAIL_METHOD')) {
case 'smtp':
$transport = Swift_SmtpTransport::newInstance(
$this->cfg->get('MAIL_SMTP_HOST'),
$this->cfg->get('MAIL_SMTP_PORT'),
$this->cfg->get('MAIL_SMTP_SECURE')
);
if ($this->cfg->get('MAIL_SMTP_AUTH') == 1) {
if ($this->cfg->get('MAIL_AUTH_METHOD') != '') {
$transport->setAuthMode($this->cfg->get('MAIL_AUTH_METHOD'));
}
$transport->setUsername($this->cfg->get('MAIL_SMTP_USER'));
$transport->setPassword($this->cfg->get('MAIL_SMTP_PASS'));
}
break;
case 'sendmail':
$transport = Swift_SendmailTransport::newInstance();
break;
case 'mail': default:
$transport = Swift_MailTransport::newInstance();
break;
}
$mailer = Swift_Mailer::newInstance($transport);
try {
$mailer->send($message);
} catch (\Swift_TransportException $Ste) {
}
}
/*********************/
/* BAN AN IP ADDRESS */
/*********************/
private function banIP($refcode) {
if ($this->address == '') { return; }
$file = $this->repo_path.'/logs/defender_ban.php';
$ip = str_replace('.', 'x', $this->address);
$ip = str_replace(':', 'y', $ip);
$unban_ts = time() - $this->banduration;
$unban_date = gmdate('Y-m-d H:i:s', $unban_ts);
$nowtimes = 1;
$buffer = '<?php '._LEND._LEND;
$buffer .= '//Elxis Defender - Banned IPs - Last updated on '.gmdate('Y-m-d H:i:s').' (UTC)'._LEND._LEND;
$buffer .= 'defined(\'_ELXIS_\') or die (\'Protected by Elxis Defender\');'._LEND._LEND;
$buffer .= '$ban = array('._LEND;
if (!file_exists($file)) {
$buffer .= '\''.$ip.'\' => array(\'times\' => '.$nowtimes.', \'refcode\' => \'SEC-'.$refcode.'\', \'date\' => \''.gmdate('Y-m-d H:i:s').'\'),'._LEND;
} else {
include($file);
$found = false;
if (isset($ban) && is_array($ban) && (count($ban) > 0)) {
foreach ($ban as $key => $row) {
if ($key == $ip) {
$found = true;
$nowtimes = $row['times'] + 1;
$buffer .= '\''.$ip.'\' => array(\'times\' => '.$nowtimes.', \'refcode\' => \'SEC-'.$refcode.'\', \'date\' => \''.gmdate('Y-m-d H:i:s').'\'),'._LEND;
} else {
if ($row['date'] > $unban_date) {//continue ban only IPs within the last 10 days (or whateven banduration value is)
$buffer .= '\''.$key.'\' => array(\'times\' => '.$row['times'].', \'refcode\' => \''.$row['refcode'].'\', \'date\' => \''.$row['date'].'\'),'._LEND;
}
}
}
}
unset($ban);
if (!$found) {
$buffer .= '\''.$ip.'\' => array(\'times\' => '.$nowtimes.', \'refcode\' => \'SEC-'.$refcode.'\', \'date\' => \''.gmdate('Y-m-d H:i:s').'\'),'._LEND;
}
}
$buffer .= ');'._LEND._LEND;
$buffer .= '?>';
if ($nowtimes >= $this->bantimes) {
$this->banmessage .= 'The guest has been BANNED as he was blocked by Elxis Defender '.$nowtimes.' times!'."\r\n";
}
if ($handler = @fopen($file, 'w')) {
$ok = false;
if (flock($handler, LOCK_EX)) {
$ok = @fwrite($handler, $buffer);
flock($handler, LOCK_UN);
}
fclose($handler);
if ($ok) {
@clearstatcache();
$fsize = intval(filesize($file) / 1024);
if ($fsize > 200) {
$this->banmessage .= 'Bans log file is '.$fsize.'KB! Please remove old bans to make it load faster.'."\r\n";
$this->banmessage .= 'Bans log file: '.$file;
}
}
}
}
/*******************/
/* SECURITY LOGGER */
/*******************/
private function securityLogger($is_attack=true, $refcode='', $message='', $method='') {
$dlog = (int)$this->cfg->get('DEFENDER_LOG');
if ($dlog < 1) { return; }
if ($dlog < 2) {
if (in_array($method, array('AGENT', 'USER AGENT', 'HOST', 'IP'))) {
return;
}
}
$file = $this->repo_path.'/logs/security.log';
if ($this->cfg->get('LOG_ROTATE') == 1) {
if (file_exists($file)) {
$modym = date('Ym', filemtime($file));
$creym = date('Ym');
if ($creym > $modym) {
$new_file = $this->repo_path.'/logs/security_'.$modym.'.log';
@copy($file, $new_file);
@unlink($file);
}
}
}
if ($is_attack) {
$txt = gmdate('Y-m-d H:i:s').' GMT ['.$this->address;
if ($this->host != '') { $txt .= ' - '.$this->host; }
$txt .= ']'._LEND;
if (isset($_SERVER['HTTP_USER_AGENT'])) { $txt .= addslashes($_SERVER['HTTP_USER_AGENT'])._LEND; }
if (isset($_SERVER['HTTP_REFERER'])) { $txt .= 'REFERER: '.addslashes($_SERVER['HTTP_REFERER'])._LEND; }
if (isset($_SERVER['REQUEST_METHOD'])) {
$txt .= $_SERVER['REQUEST_METHOD'].' ';
} else if (!empty($_POST)) {
$txt .= 'POST ';
} else {
$txt .= 'GET ';
}
$txt .= addslashes($this->getURI())._LEND;
$txt .= 'REFCODE: '.$refcode;
if ($message != '') { $txt .= ' '.$message; }
$txt .= _LEND._LEND;
} else {
$txt = gmdate('Y-m-d H:i:s').' GMT '.$message._LEND._LEND;
}
$ok = false;
if (!$fp = @fopen($file, 'a')) { return false; }
if (flock($fp, LOCK_EX)) {
$ok = fwrite($fp, $txt);
flock($fp, LOCK_UN);
}
fclose($fp);
return $ok;
}
/*********************/
/* GET REQUESTED URI */
/*********************/
private function getURI() {
if (isset($_SERVER['REQUEST_URI'])) { return $_SERVER['REQUEST_URI']; }
if (isset($_SERVER['QUERY_STRING'])) {
$query_str = $_SERVER['QUERY_STRING'];
} else if (@getenv('QUERY_STRING')) {
$query_str = getenv('QUERY_STRING');
} else {
$query_str = '';
}
if ($query_str != '') { $query_str = '?'.$query_str; }
if (isset($_SERVER['PATH_INFO'])) {
return $_SERVER['PATH_INFO'].$query_str;
} elseif (@getenv('PATH_INFO')) {
return getenv('PATH_INFO').$query_str;
}
if (isset($_SERVER['PHP_SELF'])) {
return $_SERVER['PHP_SELF'].$query_str;
} elseif (@getenv('PHP_SELF')) {
return getenv('PHP_SELF').$query_str;
} else {
return $query_str;
}
}
/*******************/
/* GET CLIENT'S IP */
/*******************/
private function getIP() {
if (isset($_SERVER['HTTP_CLIENT_IP']) && ($_SERVER['HTTP_CLIENT_IP'] != '')) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ($_SERVER['HTTP_X_FORWARDED_FOR'] != '')) {
$n = strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',');//Required in case we have multiple IPs
if ($n === false) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, $n);//get the first IP in the list
}
} elseif (isset($_SERVER['REMOTE_ADDR']) && ($_SERVER['REMOTE_ADDR'] != '')) {
$ip = $_SERVER['REMOTE_ADDR'];
} else {
$ip = '';
}
return $ip;
}
/***********************************/
/* GET ELXIS FILESYSTEM LOCK FILES */
/***********************************/
private function getLockFiles() {
$files = array('index.php', 'inner.php', 'includes/loader.php');
$libs = $this->listFiles('includes/libraries/elxis/');
if ($libs) {
foreach ($libs as $lib) { $files[] = $lib; }
}
unset($libs);
$tpls = $this->listFolders('templates/');
if ($tpls) {
foreach ($tpls as $tpl) {
if (file_exists(ELXIS_PATH.'/templates/'.$tpl.'/index.php')) {
$files[] = 'templates/'.$tpl.'/index.php';
}
if (file_exists(ELXIS_PATH.'/templates/'.$tpl.'/inner.php')) {
$files[] = 'templates/'.$tpl.'/inner.php';
}
}
}
unset($tpls);
$comps = $this->listFolders('components/');
if ($comps) {
foreach ($comps as $comp) {
$f = str_replace('com_', '', $comp);
if (file_exists(ELXIS_PATH.'/components/'.$comp.'/'.$f.'.php')) {
$files[] = 'components/'.$comp.'/'.$f.'.php';
}
}
}
unset($tpls);
return $files;
}
/*****************************/
/* LIST FILES IN A DIRECTORY */
/*****************************/
private function listFiles($dir, $onlyphp=true) {
$path = ELXIS_PATH.'/'.$dir;
if (!is_dir($path)) { return array(); }
$arr = array();
$handle = opendir($path);
while ($entry = readdir($handle)) {
if (($entry != '.') && ($entry != '..')) {
if ($onlyphp) {
if (preg_match('#(\.php)$#i', $entry)) { $arr[] = $dir.$entry; }
} else {
$arr[] = $dir.$entry;
}
}
}
closedir($handle);
asort($arr);
return $arr;
}
/*******************************/
/* LIST FOLDERS IN A DIRECTORY */
/*******************************/
private function listFolders($dir) {
$path = ELXIS_PATH.'/'.$dir;
if (!is_dir($path)) { return array(); }
$arr = array();
$handle = opendir($path);
while ($entry = readdir($handle)) {
if (($entry != '.') && ($entry != '..') && is_dir($path.$entry)) {
$arr[] = $entry;
}
}
closedir($handle);
asort($arr);
return $arr;
}
/************************************/
/* UPDATE IPs OR IP RANGES DATABASE */
/************************************/
private function updateIPDB($ranges=false) {
/*
Many thanks to Stop Forum Spam and blocklist.de teams for their work!
http://www.stopforumspam.com - http://www.blocklist.de
*/
if ($ranges) {
$url = 'https://www.stopforumspam.com/downloads/toxic_ip_range.txt';
} else {
$url = 'https://api.blocklist.de/getlast.php?time=28800';
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 8);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_REFERER, $this->cfg->get('URL'));
$data = curl_exec($ch);
if (curl_errno($ch) == 0) {
curl_close($ch);
} else {
curl_close($ch);
return false;
}
if (trim($data) == '') { return false; }
$lines = preg_split("@((\r?\n)|(\r\n?))@", $data);
if (!$lines) { return false; }
$txt = '<?php '._LEND;
if ($ranges) {
$txt .= '//Elxis Defender - Blocked IP ranges - Last update '.gmdate('Y-m-d H:i:s').' GMT'._LEND._LEND;
} else {
if (strpos($lines[0], '<') !== false) { return false; } //nothing found
$txt .= '//Elxis Defender - Blocked IPs - Last update '.gmdate('Y-m-d H:i:s').' GMT'._LEND._LEND;
}
$i = 0;
$txt .= '$ips = array('._LEND;
foreach($lines as $line) {
$line = trim($line);
if ($line == '') { continue; }
if ($ranges) {
$parts = explode('-', $line);
if (!isset($parts[1])) { continue; }
$first = ip2long($parts[0]);
$last = ip2long($parts[1]);
if (($first == -1) || ($first === false) || ($last == -1) || ($last === false)) { continue; }
//I. Sannos note: we use "%u" to get the unsigned ip address because on 32bit systems ip2long might return negative number
$txt .= "\t".'array(\''.$parts[0].'\', \''.$parts[1].'\', \''.sprintf('%u', $first).'\', \''.sprintf('%u', $last).'\'),'._LEND;
} else {
if ($i > 4000) { break; }
$txt .= '\''.$line.'\','._LEND;
$i++;
}
}
$txt .= ');'._LEND._LEND;
$txt .= '?>';
unset($lines);
$file = ($ranges) ? $this->repo_path.'/logs/defender_ip_ranges.php' : $this->repo_path.'/logs/defender_ips.php';
$fp = @fopen($file, 'w');
if ($fp) {
if (flock($fp, LOCK_EX)) {
fwrite($fp, $txt);
flock($fp, LOCK_UN);
}
fclose($fp);
}
return true;
}
}
?>