-
-
Save luckyshot/6077693 to your computer and use it in GitHub Desktop.
<?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 |
<?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.' ); | |
} | |
} | |
} |
<?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' ); | |
} | |
} | |
} |
<?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 ); |
This is insecure if no cloudflare is used. PHP converts ever request header XYZ to
HTTP_XYZ
so an attacker can set any value for $ip.
Thank you @Rotzbua, do you know any solution to this? How do you get the real IP of the user or detect if Cloudflare is active? A list of Cloudflare IPs seems very unpractical...
Not sure if it is the best way, but I would add a "secret" header to the request by cloudflares workers so the php script knows that it is a request over cloudflare.
That's a great solution, I'd love to have something that doesn't need extra steps to set up but this is simple enough. Thank you mate!
Thank you @Rotzbua for the valid observation but can one use the code below to validate IP addresses first, before using it in here
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;
}
This is insecure if no cloudflare is used. PHP converts ever request header XYZ to
HTTP_XYZ
so an attacker can set any value for $ip.