From 6399c0a451de3f68e48f212330b66513db5846b2 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Fri, 16 Jun 2017 18:40:12 -0600 Subject: [PATCH] Fix issue #1 (add rate limiting to sensitive actions) --- action.php | 4 +- api.php | 18 +++--- database.mwb | Bin 17873 -> 18382 bytes index.php | 3 + lib/login.php | 19 +----- mobile/index.php | 2 + required.php | 149 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 28 deletions(-) diff --git a/action.php b/action.php index d0eec8a..a6c0268 100644 --- a/action.php +++ b/action.php @@ -3,8 +3,6 @@ /** * Make things happen when buttons are pressed and forms submitted. */ -use LdapTools\LdapManager; -use LdapTools\Object\LdapObjectType; require_once __DIR__ . "/required.php"; @@ -18,6 +16,8 @@ if ($VARS['action'] == 'signout' && $_SESSION['loggedin'] != true) { dieifnotloggedin(); +engageRateLimit(); + require_once __DIR__ . "/lib/login.php"; function returnToSender($msg, $arg = "") { diff --git a/api.php b/api.php index 8e776c7..0c40984 100644 --- a/api.php +++ b/api.php @@ -14,7 +14,8 @@ header("Content-Type: application/json"); //try { $key = $VARS['key']; if ($database->has('apikeys', ['key' => $key]) !== TRUE) { - header("HTTP/1.1 403 Unauthorized"); + engageRateLimit(); + http_response_code(403); insertAuthLog(14, null, "Key: " . $key); die("\"403 Unauthorized\""); } @@ -82,7 +83,7 @@ switch ($VARS['action']) { exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)])); } } else { - header("HTTP/1.1 400 Bad Request"); + http_response_code(400); die("\"400 Bad Request\""); } break; @@ -118,6 +119,7 @@ switch ($VARS['action']) { case "acctstatus": exit(json_encode(["status" => "OK", "account" => get_account_status($VARS['username'])])); case "login": + engageRateLimit(); // simulate a login, checking account status and alerts $errmsg = ""; if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) { @@ -195,7 +197,7 @@ switch ($VARS['action']) { exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)])); } } else { - header("HTTP/1.1 400 Bad Request"); + http_response_code(400); die("\"400 Bad Request\""); } $managed = $database->select('managers', 'employeeid', ['managerid' => $managerid]); @@ -215,7 +217,7 @@ switch ($VARS['action']) { exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)])); } } else { - header("HTTP/1.1 400 Bad Request"); + http_response_code(400); die("\"400 Bad Request\""); } $managers = $database->select('managers', 'managerid', ['employeeid' => $empid]); @@ -230,7 +232,7 @@ switch ($VARS['action']) { break; case "permission": if (is_empty($VARS['code'])) { - header("HTTP/1.1 400 Bad Request"); + http_response_code(400); die("\"400 Bad Request\""); } $perm = $VARS['code']; @@ -247,15 +249,15 @@ switch ($VARS['action']) { exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false)])); } } else { - header("HTTP/1.1 400 Bad Request"); + http_response_code(400); die("\"400 Bad Request\""); } $hasperm = account_has_permission($user, $perm); exit(json_encode(["status" => "OK", "has_permission" => $hasperm])); break; default: - header("HTTP/1.1 400 Bad Request"); - die("\"400 Bad Request\""); + http_response_code(404); + die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."])); } /* } catch (Exception $e) { header("HTTP/1.1 500 Internal Server Error"); diff --git a/database.mwb b/database.mwb index e7161c36037b04313048e83f2ad4de6139217cf6..2ab560fe86dc2fb8788e70fe72a7b11af6bfcc71 100644 GIT binary patch literal 18382 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 vqU@y9yyOf8-~5!+9EHT9insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]); } diff --git a/mobile/index.php b/mobile/index.php index d8bc1ad..54c8174 100644 --- a/mobile/index.php +++ b/mobile/index.php @@ -28,6 +28,7 @@ if (is_empty($VARS['username']) || is_empty($VARS['key'])) { // Make sure the username and key are actually legit $user_key_valid = $database->has('mobile_codes', ['[>]accounts' => ['uid' => 'uid']], ["AND" => ['mobile_codes.code' => $VARS['key'], 'accounts.username' => $VARS['username']]]); if ($user_key_valid !== TRUE) { + engageRateLimit(); http_response_code(401); insertAuthLog(21, null, "Username: " . $VARS['username'] . ", Key: " . $VARS['key']); die(json_encode(["status" => "ERROR", "msg" => "Invalid username and/or access key."])); @@ -40,6 +41,7 @@ switch ($VARS['action']) { // If we get this far, it is, so return success. exit(json_encode(["status" => "OK"])); case "check_password": + engageRateLimit(); if (get_account_status($VARS['username']) != "NORMAL") { insertAuthLog(20, null, "Username: " . $VARS['username'] . ", Key: " . $VARS['key']); exit(json_encode(["status" => "ERROR", "msg" => lang("login failed try on web", false)])); diff --git a/required.php b/required.php index 9d7944f..bb542b8 100644 --- a/required.php +++ b/required.php @@ -241,3 +241,152 @@ function redirectIfNotLoggedIn() { die(); } } + +/** + * Check if a given ipv4 address is in a given cidr + * @param string $ip IP to check in IPV4 format eg. 127.0.0.1 + * @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed + * @return boolean true if the ip is in this range / false if not. + * @author Thorsten Ott + */ +function ip4_in_cidr($ip, $cidr) { + if (strpos($cidr, '/') == false) { + $cidr .= '/32'; + } + // $range is in IP/CIDR format eg 127.0.0.1/24 + list( $cidr, $netmask ) = explode('/', $cidr, 2); + $range_decimal = ip2long($cidr); + $ip_decimal = ip2long($ip); + $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1; + $netmask_decimal = ~ $wildcard_decimal; + return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); +} + +/** + * Check if a given ipv6 address is in a given cidr + * @param string $ip IP to check in IPV6 format + * @param string $cidr CIDR netmask + * @return boolean true if the IP is in this range, false otherwise. + * @author MW. + */ +function ip6_in_cidr($ip, $cidr) { + $address = inet_pton($ip); + $subnetAddress = inet_pton(explode("/", $cidr)[0]); + $subnetMask = explode("/", $cidr)[1]; + + $addr = str_repeat("f", $subnetMask / 4); + switch ($subnetMask % 4) { + case 0: + break; + case 1: + $addr .= "8"; + break; + case 2: + $addr .= "c"; + break; + case 3: + $addr .= "e"; + break; + } + $addr = str_pad($addr, 32, '0'); + $addr = pack("H*", $addr); + + $binMask = $addr; + return ($address & $binMask) == $subnetAddress; +} + +/** + * Check if the REMOTE_ADDR is on Cloudflare's network. + * @return boolean true if it is, otherwise false + */ +function validateCloudflare() { + if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + // Using IPv6 + $cloudflare_ips_v6 = [ + "2400:cb00::/32", + "2405:8100::/32", + "2405:b500::/32", + "2606:4700::/32", + "2803:f800::/32", + "2c0f:f248::/32", + "2a06:98c0::/29" + ]; + $valid = false; + foreach ($cloudflare_ips_v6 as $cidr) { + if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } else { + // Using IPv4 + $cloudflare_ips_v4 = [ + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "104.16.0.0/12", + "108.162.192.0/18", + "131.0.72.0/22", + "141.101.64.0/18", + "162.158.0.0/15", + "172.64.0.0/13", + "173.245.48.0/20", + "188.114.96.0/20", + "190.93.240.0/20", + "197.234.240.0/22", + "198.41.128.0/17" + ]; + $valid = false; + foreach ($cloudflare_ips_v4 as $cidr) { + if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) { + $valid = true; + break; + } + } + } + return $valid; +} + +/** + * Makes a good guess at the client's real IP address. + * + * @return string Client IP or `0.0.0.0` if we can't find anything + */ +function getClientIP() { + // If CloudFlare is in the mix, we should use it. + // Check if the request is actually from CloudFlare before trusting it. + if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { + if (validateCloudflare()) { + return $_SERVER["HTTP_CF_CONNECTING_IP"]; + } + } + + if (isset($_SERVER["REMOTE_ADDR"])) { + return $_SERVER["REMOTE_ADDR"]; + } + + return "0.0.0.0"; // This will not happen unless we aren't a web server +} + +/** + * Check if the client's IP has been doing too many brute-force-friendly + * requests lately. + * Kills the script with a "friendly" error and response code 429 + * (Too Many Requests) if the last access time in the DB is too near. + * + * Also updates the rate_limit table with the latest data and purges old rows. + * @global type $database + */ +function engageRateLimit() { + global $database; + $delay = date("Y-m-d H:i:s", strtotime("-2 seconds")); + $database->delete('rate_limit', ["lastaction[<]" => $delay]); + if ($database->has('rate_limit', ["AND" => ["ipaddr" => getClientIP()]])) { + http_response_code(429); + // JSONify it so API clients don't scream too loud + die(json_encode(["status" => "ERROR", "msg" => "You're going too fast. Slow down, mkay?"])); + } else { + // Add a record for the IP address + $database->insert('rate_limit', ["ipaddr" => getClientIP(), "lastaction" => date("Y-m-d H:i:s")]); + } +}