-
-
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_XYZso 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_XYZso an attacker can set any value for $ip.