Last active
April 28, 2023 07:46
-
-
Save luckyshot/6077693 to your computer and use it in GitHub Desktop.
Throttle client requests to avoid DoS attack (scroll down for IP-based)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
ABUSE CHECK | |
Throttle client requests to avoid DoS attack | |
*/ | |
session_start(); | |
$usage = array(5,5,5,5,10,20,30,40,50,60,120,180,240); // seconds to wait after each request | |
if (isset($_SESSION['use_last'])) { | |
$nextin = $_SESSION['use_last']+$usage[$_SESSION['use_count']]; | |
if (time() < $nextin) { | |
echo 'Please wait '.($nextin-time()).' seconds…'; | |
die(); | |
}else{ | |
$_SESSION['use_count']++; | |
if ($_SESSION['use_count'] > sizeof($usage)-1) {$_SESSION['use_count']=sizeof($usage)-1;} | |
} | |
}else{ | |
$_SESSION['use_count'] = 0; | |
} | |
$_SESSION['use_last'] = time(); | |
// Execute code here |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Limit amount of requests to avoid server abuse (IP based) | |
require( dirname(__FILE__) . '/lib/throttlerequest.php' ); | |
$tr = new ThrottleRequest( $_SERVER['REMOTE_ADDR'], dirname(__FILE__) . '/lib/throttlerequest-db.php'); | |
*/ | |
class ThrottleRequest { | |
private $number_requests = 61; | |
private $time_interval = 60; | |
private $db_path = 'access.php'; | |
private $db = []; | |
public function __construct( $ip, $path = NULL ) | |
{ | |
// if path is set | |
if ( isset( $path ) ) | |
{ | |
$this->db_path = $path; | |
} | |
// Get DB | |
if ( file_exists( $this->db_path ) ) | |
{ | |
$this->db = include( $this->db_path ); | |
} | |
// Clean old IPs | |
foreach ( $this->db as $session_ip => $session ) | |
{ | |
if ( $session['expires'] <= time() ) | |
{ | |
unset( $this->db[ $session_ip ] ); | |
} | |
} | |
// Add request | |
if ( isset( $this->db[ $ip ] ) ) | |
{ | |
$this->db[ $ip ]['requests']++; | |
} | |
else | |
{ | |
$this->db[ $ip ] = [ | |
'requests' => 1, | |
'expires' => time() + $this->time_interval, | |
'user_agent' => $_SERVER['HTTP_USER_AGENT'], | |
]; | |
} | |
// Save DB | |
file_put_contents( $this->db_path, '<?php return ' . var_export( $this->db, true ) . ';' ); | |
// Check if needs to be blocked | |
if ( $this->db[ $ip ]['requests'] > $this->number_requests ) | |
{ | |
header('HTTP/1.0 503 Service Temporarily Unavailable'); | |
header('Status: 503 Service Temporarily Unavailable'); | |
header('Retry-After: ' . $this->time_interval ); // seconds | |
die( 'Too many requests. Please wait a few seconds.' ); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Limit amount of requests to avoid server abuse (IP based) | |
require( dirname(__FILE__) . '/libs/throttlerequest.php' ); | |
$tr = new ThrottleRequest( $config ); | |
MySQL | |
CREATE TABLE `throttlerequest` ( | |
`ip` varchar(45) NOT NULL DEFAULT '', | |
`requests` int(11) NOT NULL DEFAULT '1', | |
`expires` datetime NOT NULL, | |
`country` char(2) DEFAULT NULL, | |
`url` varchar(255) DEFAULT NULL, | |
`user_agent` varchar(255) DEFAULT NULL, | |
PRIMARY KEY (`ip`) | |
) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
*/ | |
class ThrottleRequest { | |
private $number_requests = 39; | |
private $time_interval = 20; // seconds | |
private $db = null; | |
private $config = null; | |
public function __construct( $config, $url = '' ){ | |
$this->config = $config; | |
$this->db = new DB( $this->config['db_user'], $this->config['db_pass'], $this->config['db_dbname'] ); | |
$ip = ( isset($_SERVER["HTTP_CF_CONNECTING_IP"]) ) ? $_SERVER["HTTP_CF_CONNECTING_IP"] : $_SERVER['REMOTE_ADDR']; | |
// Clean old IPs | |
$clean = $this->db->query("DELETE FROM throttlerequest WHERE expires <= :expires;") | |
->bind(':expires', date('Y-m-d H:i:s')) | |
->delete(); | |
$exists = $this->db->query("SELECT ip, requests FROM throttlerequest WHERE ip = :ip LIMIT 1;") | |
->bind(':ip', $ip ) | |
->single(); | |
if ( $exists ){ | |
$result = $this->db->query("UPDATE throttlerequest SET | |
requests = :requests, | |
url = :url, | |
user_agent = :user_agent | |
WHERE ip = :ip | |
LIMIT 1;") | |
->bind(':requests', $exists['requests'] + 1 ) | |
->bind(':url', $url ) | |
->bind(':user_agent', $_SERVER['HTTP_USER_AGENT'] ) | |
->bind(':ip', $ip ) | |
->update(); | |
}else{ | |
$result = $this->db->query("INSERT INTO `throttlerequest` (`ip`, `requests`, `expires`, `country`, `url`, `user_agent`) VALUES ( | |
:ip, | |
:requests, | |
:expires, | |
:country, | |
:url, | |
:user_agent | |
);") | |
->bind(':ip', $ip ) | |
->bind(':requests', 1 ) | |
->bind(':expires', date('Y-m-d H:i:s', time() + $this->time_interval ) ) | |
->bind(':country', @$_SERVER['HTTP_CF_IPCOUNTRY'] ) | |
->bind(':url', $url ) | |
->bind(':user_agent', $_SERVER['HTTP_USER_AGENT'] ) | |
->update(); | |
} | |
// Check if needs to be blocked | |
if ( $exists['requests'] > $this->number_requests ){ | |
http_response_code( 429 ); | |
header('Retry-After: ' . $this->time_interval ); | |
die( 'Too many requests, please wait a few seconds' ); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* THROTTLE REQUESTS | |
* | |
* Limit amount of requests to avoid server abuse | |
* For increased security, rely on IP addresses instead of Sessions | |
* | |
* $number_requests to be made on each interval | |
* $time_interval seconds each interval lasts | |
* | |
* throttlerequest( 10, 60 ); // 1 request every 6 seconds | |
*/ | |
function throttlerequest( $number_requests = 10, $time_interval = 60 ) | |
{ | |
if( !isset($_SESSION) ) { session_start(); } | |
// window not started or already finished | |
if ( !isset($_SESSION['window']) OR $_SESSION['window'] + $time_interval < time() ) | |
{ | |
$_SESSION['window'] = time(); | |
$_SESSION['count'] = 1; | |
} | |
// $_SESSION['window'] started | |
else if ( $_SESSION['window'] + $time_interval >= time() ) | |
{ | |
$_SESSION['count']++; | |
if ( $_SESSION['count'] > $number_requests ) | |
{ | |
die( 'Too many requests. Please wait a few seconds.' ); | |
} | |
} | |
} | |
throttlerequest( 10, 60 ); |
Thank you @Rotzbua for the valid observation but can one use the code below to validate IP addresses first, before using it in here
/**
- To validate if an IP address is both a valid and does not fall within
- a private network range.
- @param string $ip
*/
function isValidIpAddress($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 |
FILTER_FLAG_IPV6 |
FILTER_FLAG_NO_PRIV_RANGE |
FILTER_FLAG_NO_RES_RANGE) === false) {
return false;
}
return true;
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you @Rotzbua for the valid observation but can one use the code below to validate IP addresses first, before using it in here