342 lines
7.7 KiB
PHP
Executable File
342 lines
7.7 KiB
PHP
Executable File
#!/usr/bin/php -q
|
|
<?php
|
|
/**
|
|
* banbrutes.php
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2011-2023 Null Team
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
/* Brute force attack detect and ban for the Yate PHP interface
|
|
Add in extmodule.conf
|
|
|
|
[scripts]
|
|
banbrutes.php=
|
|
or
|
|
banbrutes.php=NNN
|
|
where NNN >= 2 is the number of failures causing a ban
|
|
|
|
If you are using SIP proxies or clients with multiple subscriptions you will need to
|
|
allow more failures for each since each separate transaction will fail once
|
|
|
|
This script requires Yate to run as root or have permissions to run iptables / ip6tables
|
|
*/
|
|
|
|
// How many failures in a row cause a ban
|
|
$ban_failures = 10;
|
|
// In how many seconds to clear a gray host
|
|
$clear_gray = 10;
|
|
// In how many seconds to clear a blacklisted host
|
|
$clear_black = 600;
|
|
// Path prefix for commands, if needed
|
|
$cmd_path = ""; // "/usr/sbin/";
|
|
// Command to ban an IPv4 address
|
|
$cmd_ban4 = "${cmd_path}iptables -I INPUT -s \$addr -j DROP";
|
|
// Command to unban an IPv4 address
|
|
$cmd_unban4 = "${cmd_path}iptables -D INPUT -s \$addr -j DROP";
|
|
// Command to ban an IPv6 address
|
|
$cmd_ban6 = "${cmd_path}ip6tables -I INPUT -s \$addr -j DROP";
|
|
// Command to unban an IPv6 address
|
|
$cmd_unban6 = "${cmd_path}ip6tables -D INPUT -s \$addr -j DROP";
|
|
|
|
|
|
require_once("libyate.php");
|
|
|
|
$banHelp = " banbrutes [list|unban address|debug on/off|failures NN]\r\n";
|
|
|
|
$hosts = array();
|
|
|
|
class Host
|
|
{
|
|
var $fail;
|
|
var $when;
|
|
|
|
function __construct()
|
|
{
|
|
global $clear_gray;
|
|
$this->fail = 1;
|
|
$this->when = time() + $clear_gray;
|
|
}
|
|
|
|
function success()
|
|
{
|
|
if ($this->fail > 0) {
|
|
$this->fail = 0;
|
|
$this->when = time() + 2;
|
|
}
|
|
}
|
|
|
|
function failed()
|
|
{
|
|
global $ban_failures;
|
|
global $clear_gray;
|
|
global $clear_black;
|
|
if ($this->fail < 0)
|
|
return false;
|
|
$this->fail++;
|
|
if ($this->fail >= $ban_failures) {
|
|
$this->fail = -1;
|
|
$this->when = time() + $clear_black;
|
|
return true;
|
|
}
|
|
$this->when = time() + $clear_gray;
|
|
return false;
|
|
}
|
|
|
|
function banned()
|
|
{
|
|
return $this->fail < 0;
|
|
}
|
|
|
|
function timer($now)
|
|
{
|
|
return $now >= $this->when;
|
|
}
|
|
}
|
|
|
|
function updateAuth($addr,$ok)
|
|
{
|
|
global $hosts;
|
|
global $cmd_ban4, $cmd_ban6;
|
|
switch ($addr) {
|
|
case null:
|
|
case "":
|
|
case "127.0.0.1":
|
|
case "::1":
|
|
return;
|
|
}
|
|
if ($ok) {
|
|
if (isset($hosts[$addr]))
|
|
$hosts[$addr]->success();
|
|
return;
|
|
}
|
|
if (isset($hosts[$addr])) {
|
|
if ($hosts[$addr]->failed()) {
|
|
$cmd = strstr($addr,":") ? $cmd_ban6 : $cmd_ban4;
|
|
$cmd = eval('return "' . $cmd . '";');
|
|
Yate::Output("banbrutes: $cmd");
|
|
shell_exec($cmd);
|
|
}
|
|
}
|
|
else {
|
|
Yate::Debug("New gray host: $addr");
|
|
$hosts[$addr] = new Host();
|
|
}
|
|
}
|
|
|
|
function onTimer()
|
|
{
|
|
global $hosts;
|
|
global $cmd_unban4, $cmd_unban6;
|
|
$now = time();
|
|
foreach ($hosts as $addr => &$host) {
|
|
if ($host->timer($now)) {
|
|
if ($host->banned()) {
|
|
$cmd = strstr($addr,":") ? $cmd_unban6 : $cmd_unban4;
|
|
$cmd = eval('return "' . $cmd . '";');
|
|
Yate::Output("banbrutes: $cmd");
|
|
shell_exec($cmd);
|
|
}
|
|
else
|
|
Yate::Debug("Expired host: $addr");
|
|
unset($hosts[$addr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onCommand($l,&$retval)
|
|
{
|
|
global $hosts;
|
|
global $ban_failures;
|
|
global $cmd_unban4, $cmd_unban6;
|
|
if ($l == "banbrutes") {
|
|
$gray = 0;
|
|
$banned = 0;
|
|
foreach ($hosts as &$host) {
|
|
if ($host->banned())
|
|
$banned++;
|
|
else
|
|
$gray++;
|
|
}
|
|
$retval = "failures=${ban_failures},banned=${banned},gray=${gray}\r\n";
|
|
return true;
|
|
}
|
|
else if ($l == "banbrutes list") {
|
|
$retval = "";
|
|
$now = time();
|
|
foreach ($hosts as $addr => &$host) {
|
|
if ($retval != "")
|
|
$retval .= ",";
|
|
if ($host->banned()) {
|
|
$t = $host->when - $now;
|
|
$retval .= "$addr=banned:${t}s";
|
|
}
|
|
else
|
|
$retval .= "$addr=gray:".$host->fail;
|
|
}
|
|
$retval .= "\r\n";
|
|
return true;
|
|
}
|
|
else if (strpos($l,"banbrutes unban ") === 0) {
|
|
$addr = substr($l,16);
|
|
if (isset($hosts[$addr])) {
|
|
if ($hosts[$addr]->banned()) {
|
|
$cmd = strstr($addr,":") ? $cmd_unban6 : $cmd_unban4;
|
|
$cmd = eval('return "' . $cmd . '";');
|
|
Yate::Output("banbrutes: $cmd");
|
|
shell_exec($cmd);
|
|
unset($hosts[$addr]);
|
|
$retval = "Unbanned: $addr\r\n";
|
|
}
|
|
else {
|
|
unset($hosts[$addr]);
|
|
$retval = "Removed from gray list: $addr\r\n";
|
|
}
|
|
}
|
|
else
|
|
$retval = "Not banned: $addr\r\n";
|
|
return true;
|
|
}
|
|
else if (strpos($l,"banbrutes failures ") === 0) {
|
|
$fail = (int) substr($l,19);
|
|
if ($fail > 1 && $fail <= 1000) {
|
|
$ban_failures = $fail;
|
|
return true;
|
|
}
|
|
}
|
|
else if (strpos($l,"banbrutes debug ") === 0) {
|
|
$dbg = substr($l,16);
|
|
switch ($dbg) {
|
|
case "true":
|
|
case "yes":
|
|
case "on":
|
|
Yate::Debug(true);
|
|
return true;
|
|
case "false":
|
|
case "no":
|
|
case "off":
|
|
Yate::Debug(false);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function oneCompletion(&$ret,$str,$part)
|
|
{
|
|
if (($part != "") && (strpos($str,$part) !== 0))
|
|
return;
|
|
if ($ret != "")
|
|
$ret .= "\t";
|
|
$ret .= $str;
|
|
}
|
|
|
|
function onComplete(&$ev,$l,$w)
|
|
{
|
|
global $hosts;
|
|
if ($l == "")
|
|
oneCompletion($ev->retval,"banbrutes",$w);
|
|
else if ($l == "help")
|
|
oneCompletion($ev->retval,"banbrutes",$w);
|
|
else if ($l == "banbrutes") {
|
|
oneCompletion($ev->retval,"list",$w);
|
|
oneCompletion($ev->retval,"unban",$w);
|
|
oneCompletion($ev->retval,"debug",$w);
|
|
oneCompletion($ev->retval,"failures",$w);
|
|
}
|
|
else if ($l == "banbrutes unban") {
|
|
foreach ($hosts as $addr => &$host) {
|
|
if ($host->banned())
|
|
oneCompletion($ev->retval,$addr,$w);
|
|
}
|
|
}
|
|
else if ($l == "banbrutes debug") {
|
|
oneCompletion($ev->retval,"on",$w);
|
|
oneCompletion($ev->retval,"off",$w);
|
|
}
|
|
}
|
|
|
|
function onHelp($l,&$retval)
|
|
{
|
|
global $banHelp;
|
|
if ($l) {
|
|
if ($l == "banbrutes") {
|
|
$retval = "${banHelp}Automatically block brute force attackers\r\n";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
$retval .= $banHelp;
|
|
return false;
|
|
}
|
|
|
|
Yate::Init();
|
|
// Comment the next line to get output only in logs, not in rmanager
|
|
Yate::Output(true);
|
|
// Uncomment the next line to get debugging details by default
|
|
//Yate::Debug(true);
|
|
|
|
$n = round( (float)Yate::Arg());
|
|
if ($n >= 2)
|
|
$ban_failures = $n;
|
|
|
|
Yate::SetLocal("trackparam","banbrutes");
|
|
Yate::Watch("user.auth");
|
|
Yate::Watch("user.authfail");
|
|
Yate::Watch("engine.timer");
|
|
Yate::Install("engine.command",120);
|
|
Yate::Install("engine.help",150);
|
|
Yate::SetLocal("restart",true);
|
|
|
|
for (;;) {
|
|
$ev=Yate::GetEvent();
|
|
if ($ev === false)
|
|
break;
|
|
if ($ev === true)
|
|
continue;
|
|
if ($ev->type == "incoming") {
|
|
switch ($ev->name) {
|
|
case "engine.command":
|
|
if ($ev->GetValue("line"))
|
|
$ev->handled = onCommand($ev->GetValue("line"),$ev->retval);
|
|
else
|
|
onComplete($ev,$ev->GetValue("partline"),$ev->GetValue("partword"));
|
|
break;
|
|
case "engine.help":
|
|
$ev->handled = onHelp($ev->GetValue("line"),$ev->retval);
|
|
break;
|
|
}
|
|
$ev->Acknowledge();
|
|
}
|
|
if ($ev->type == "answer") {
|
|
switch ($ev->name) {
|
|
case "user.auth":
|
|
updateAuth($ev->GetValue("ip_host"),$ev->handled && ($ev->retval != "-"));
|
|
break;
|
|
case "user.authfail":
|
|
updateAuth($ev->GetValue("ip_host"),false);
|
|
break;
|
|
case "engine.timer":
|
|
onTimer();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Yate::Output("banbrutes: bye!");
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|
|
?>
|