Skip to content
This repository has been archived by the owner on Aug 31, 2021. It is now read-only.

Commit

Permalink
PHP-level DoS protection (resolves #81)
Browse files Browse the repository at this point in the history
  • Loading branch information
camilstaps committed Feb 13, 2017
1 parent b728e2c commit 8da7061
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 51 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fields:
* `151`: invalid request type (should use GET)
* `152`: no input (GET variable `str` should be set to the search string)
* `153`: the Clean backend timed out
* `154`: you have sent too many requests; try again later (DoS protection)

- `msg`

Expand Down
119 changes: 71 additions & 48 deletions frontend/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,81 @@
define('SERVER_PORT', 31215);
define('SERVER_TIMEOUT', 8);

define('E_CLOOGLEDOWN', 150);
define('E_ILLEGALMETHOD', 151);
define('E_ILLEGALREQUEST', 152);
define('E_TIMEOUT', 153);
if (file_exists('conf.php'))
require_once('conf.php');

$start_time = microtime(true);

function log_request($code) {
if (defined('CLOOGLE_KEEP_STATISTICS')) {
$db = new mysqli(
CLOOGLE_DB_HOST, CLOOGLE_DB_USER, CLOOGLE_DB_PASS, CLOOGLE_DB_NAME);
if (mysqli_connect_errno())
return;
if (defined('CLOOGLE_KEEP_STATISTICS')) {
$db = new mysqli(
CLOOGLE_DB_HOST, CLOOGLE_DB_USER, CLOOGLE_DB_PASS, CLOOGLE_DB_NAME);
if (mysqli_connect_errno())
$db = null;

$ua = $_SERVER['HTTP_USER_AGENT'];
$ua_hash = md5($ua);
$ua = $_SERVER['HTTP_USER_AGENT'];
$ua_hash = md5($ua);

$stmt = $db->prepare('SELECT `id` FROM `useragent` WHERE `ua_hash`=?');
$stmt->bind_param('s', $ua_hash);
$stmt->execute();
$stmt->bind_result($ua_id);
if ($stmt->fetch() !== true) {
$stmt->close();
$stmt = $db->prepare(
'INSERT INTO `useragent` (`useragent`,`ua_hash`) VALUES (?,?)');
$stmt->bind_param('ss', $ua, $ua_hash);
$stmt->execute();
$ua_id = $stmt->insert_id;
}
$stmt = $db->prepare('SELECT `id` FROM `useragent` WHERE `ua_hash`=?');
$stmt->bind_param('s', $ua_hash);
$stmt->execute();
$stmt->bind_result($ua_id);
if ($stmt->fetch() !== true) {
$stmt->close();

global $start_time;
$time = (int) ((microtime(true) - $start_time) * 1000);

$ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
$_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
$stmt = $db->prepare('INSERT INTO `log`
(`ip`,`useragent_id`,`query`,`responsecode`,`responsetime`)
VALUES (?,?,?,?,?)');
$stmt->bind_param('sisii',
$ip,
$ua_id,
$_GET['str'],
$code,
$time);
$stmt = $db->prepare(
'INSERT INTO `useragent` (`useragent`,`ua_hash`) VALUES (?,?)');
$stmt->bind_param('ss', $ua, $ua_hash);
$stmt->execute();
$stmt->close();

$db->close();
$ua_id = $stmt->insert_id;
}
$stmt->close();

$ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ?
$_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
}

function has_database() {
global $db;
return defined('CLOOGLE_KEEP_STATISTICS') && !is_null($db);
}

function dos_protect() {
global $db, $ua_id, $ip;

if (!has_database)
return false;

$stmt = $db->prepare('SELECT COUNT(*) FROM `log`
WHERE `useragent_id`=? AND `ip`=? AND `date`>=NOW() - INTERVAL 1 SECOND');
$stmt->bind_param('is', $ua_id, $ip);
$stmt->execute();
$stmt->bind_result($count);
$stmt->fetch();

return $count >= DOS_MAX_REQUESTS_PER_SECOND;
}

function log_request($code) {
global $db, $ua_id, $ip;

if (!has_database())
return;

global $start_time;
$time = (int) ((microtime(true) - $start_time) * 1000);

$stmt = $db->prepare('INSERT INTO `log`
(`ip`,`useragent_id`,`query`,`responsecode`,`responsetime`)
VALUES (?,?,?,?,?)');
$stmt->bind_param('sisii',
$ip,
$ua_id,
$_GET['str'],
$code,
$time);
$stmt->execute();
$stmt->close();

$db->close();
}

function respond($code, $msg, $data=[]) {
Expand All @@ -65,14 +90,12 @@ function respond($code, $msg, $data=[]) {
]);
}

if (file_exists('conf.php')) {
require_once('conf.php');
}

if($_SERVER['REQUEST_METHOD'] !== 'GET'){
if ($_SERVER['REQUEST_METHOD'] !== 'GET'){
respond(E_ILLEGALMETHOD, 'Can only be accessed by GET request');
} else if(!isset($_GET['str'])){
} else if (!isset($_GET['str'])){
respond(E_ILLEGALREQUEST, 'GET variable "str" must be set');
} else if (defined('CLOOGLE_KEEP_STATISTICS') && dos_protect()) {
respond(E_DOSPROTECT, "Yes, cloogle is great, but you don't need it so badly.");
} else {
$str = array_map('trim', explode('::', $_GET['str']));
$name = trim($str[0]);
Expand Down
8 changes: 8 additions & 0 deletions frontend/conf.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@
define('CLOOGLE_DB_PASS', 'cloogle');

define('CLOOGLE_KEEP_STATISTICS', true);

define('E_CLOOGLEDOWN', 150);
define('E_ILLEGALMETHOD', 151);
define('E_ILLEGALREQUEST', 152);
define('E_TIMEOUT', 153);
define('E_DOSPROTECT', 154);

define('DOS_MAX_REQUESTS_PER_SECOND', 3);
4 changes: 3 additions & 1 deletion frontend/stats/ajax/by-hour.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

$sql =
"SELECT dayofweek(`date`), hour(`date`), count(*) FROM `log`
WHERE `date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')
WHERE
`responsecode` <> " . E_DOSPROTECT . " AND
`date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')
GROUP BY dayofweek(`date`), hour(`date`)
ORDER BY dayofweek(`date`) asc, hour(`date`) ASC";

Expand Down
1 change: 1 addition & 0 deletions frontend/stats/ajax/oss.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
FROM `log`
INNER JOIN `useragent` ON `log`.`useragent_id` = `useragent`.`id`
WHERE
`responsecode` <> " . E_DOSPROTECT . " AND
`date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')
AND ";

Expand Down
4 changes: 3 additions & 1 deletion frontend/stats/ajax/over-time.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
count(case when `responsecode`>1 and `responsecode`<150 then 1 else null end),
count(distinct `ip`,`useragent_id`)
FROM `log`
WHERE `date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')
WHERE
`responsecode` <> " . E_DOSPROTECT . " AND
`date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')
GROUP BY $group
ORDER BY min(`date`) ASC";

Expand Down
4 changes: 3 additions & 1 deletion frontend/stats/ajax/types.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
sum(case when `query` LIKE 'type %' then 1 else null end),
sum(case when `query` LIKE 'class %' then 1 else null end)
FROM `log`
WHERE `date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')";
WHERE
`responsecode` <> " . E_DOSPROTECT . " AND
`date` BETWEEN timestamp('$startTime') AND timestamp('$endTime')";

$stmt = $db->stmt_init();
if (!$stmt->prepare($sql))
Expand Down

0 comments on commit 8da7061

Please sign in to comment.