API Security Check
Posted: Sun Nov 24, 2013 4:50 pm
Hello. Just finished my API and I would like a security check if possible. It's not completely done as of the distributing of the API key, and downloading of the data, but what I would really like feedback on is the handling of the data. I haven't installed SSL yet so there's no forcing it yet. It's actually in OOP but I remade it so mostly everyone should understand it. Edit: I just realized the lack of increment_api_request() calls. I'll be adding them later on.
api/index.php
[syntax=php]<?php
// Ignore these. Just for testing
error_reporting(E_ALL);
ini_set('display_errors', 1);
if($_SERVER['HTTP_USER_AGENT'] != "site Auto Updater")
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
if(!isset($_GET['action']))
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
// User is installing a script
if($_GET['action'] == "Create") {
// Not done yet
// User is updating a script
} else if($_GET['action'] == "Update") {
if(!isset($_GET['api_key']))
die(json_encode(array("status" => "Error", "message" => "Invalid API key specified.")));
if(!isset($_GET['script']))
die(json_encode(array("status" => "Error", "message" => "Invalid script specified.")));
require("functions.php");
// Check validity of the API key.
if(valid_api_key($_GET['api_key']) === false)
die(json_encode(array("status" => "Error", "message" => "Invalid API key specified.")));
// Check if the user has requested too many times today
if(max_requests($_GET['api_key']) === true)
die(json_encode(array("status" => "Error", "message" => "You have reached the limit of API requests today.")));
// Check if user has valid access to the script
if(valid_usage($_GET['api_key'], $_GET['script']) === false)
die(json_encode(array("status" => "Error", "message" => "You do not have permission to update this script.")));
// Get the script data
$parts = get_file($_GET['script']);
// If the requested script does not exist
if($parts === false) {
increment_api_requests($_GET['api_key']);
die(json_encode(array("status" => "Error", "message" => "The requested script is invalid.")));
// If we're updating the script or something
} else if($parts == "Unavailable") {
increment_api_requests($_GET['api_key']);
die(json_encode(array("status" => "Error", "message" => "The requested script is currently unavailable for update. Please try again later.")));
}
// Zip location - 0, Version - 1
$part = explode(",", $parts);
$num = 1;
// If a temporary directory exists, increment the directory number to prevent issues
while(is_dir("tmp/tmp_dir-{$num}"))
$num++;
$zip = new ZipArchive;
// Get the zipped script file
$res = $zip->open("../scripts/downloads/{$part['0']}");
if($res === true) {
// Extract the script into a temporary directory
@$zip->extractTo("tmp/tmp_dir-{$num}/"); // @ to suppress an error that occurs randomly. Google has no clue how to fix
$zip->close();
} else
die(json_encode(array("status" => "Error", "message" => "Sorry. There was an error preparing your update on our end. If this error persists, please contact us at support@site")));
// Create the tracking file for the cron job to access
$track = fopen("tmp/tmp_dir-{$num}/track_file.txt", "w");
fwrite($track, base64_encode(time()));
fclose($track);
// The script directory name afte the zip has been extracted
$absolute_script = str_replace(" ", "_", $_GET['script'])."_v{$part['1']}";
// Get the files in the extracted directory
$dir = @scandir("tmp/tmp_dir-{$num}/{$absolute_script}");
if($dir === false)
die(json_encode(array("status" => "Error", "message" => "There was an error producing your update.")));
unset($dir['0'], $dir['1']);
// Move the files out of the directory for convenience
foreach($dir as $src) {
rename("tmp/tmp_dir-{$num}/{$absolute_script}/{$src}", "tmp/tmp_dir-{$num}/{$src}");
}
// Remove the now empty directory
rmdir("tmp/tmp_dir-{$num}/{$absolute_script}");
// Create a new iterator
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator("tmp/tmp_dir-{$num}/"));
// Go through each file and encrypt the contents
foreach($iterator as $file) {
$extension = end(explode(".", $file));
$exceptions = array("gif", "png", "jpg", "jpeg");
// If the current file does not have an extension listed in the exceptions
if(!in_array($extension, $exceptions) && $file != "tmp/tmp_dir-{$num}/track_file.txt") {
$code = file_get_contents($file);
// Code - 0, Size - 1, Random Key - 2, Offset - 3
if(isset($random_key)) {
$returned_data = encrypt_data($code, $_GET['api_key'], $random_key);
$encoded = explode("&", $returned_data);
} else {
$returned_data = encrypt_data($code, $_GET['api_key']);
$encoded = explode("&", $returned_data);
$random_key = $encoded['2'];
$offset = $encoded['3'];
}
file_put_contents($file, $encoded['0'], LOCK_EX);
}
}
increment_api_requests($_GET['api_key']);
// Var1 - Tmp Directory
// Var2 - Size
// Var3 - Key
// Var4 - Offset
// Var5 - Version
die(json_encode(array("status" => "Success", "message" => "Successful API request!", "var1" => base64_encode($num), "var2" => base64_encode($encoded['1']), "var3" => base64_encode($random_key), "var4" => base64_encode($offset), "var5" => base64_encode($part['1']))));
} else
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
?>[/syntax]
api/functions.php
[syntax=php]<?php
// Just for testing. Normal will be 5 or 10 probably
define("MAX_REQUESTS", 500);
// Function to check validity of the API key
function valid_api_key($key) {
// Check for 64-digit hash format
if(strlen($key) != 64 || !preg_match("#^[0-9a-f]{64}$#i", $key))
return false;
$database = "---";
require("connections/select_connect.php");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `id`,`access` FROM `users` WHERE `api_key` = '{$key}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
$row = mysqli_fetch_array($sql);
if($row['access'] == 0)
return false;
else
return true;
} else
return false;
}
// Function to check if users have requested too much
function max_requests($key) {
$database = "---";
require("connections/select_connect.php");
$date = date("l, F j, Y");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `requests` FROM `api_requests` WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
$row = mysqli_fetch_array($sql);
if($row['requests'] >= MAX_REQUESTS)
return true;
else
return false;
} else
return false;
}
// Function to check if the user has valid permission to use the script
function valid_usage($key, $script) {
$database = "---";
require("connections/select_connect.php");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `valid_for` FROM `users` WHERE `api_key` = '{$key}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) != 0) {
$row = mysqli_fetch_array($sql);
$valid_for = explode(",", $row['valid_for']);
if(!in_array($script, $valid_for)) {
increment_api_requests($key);
return false;
} else
return true;
} else
return false;
}
// Function to get the file name of the requested script
function get_file($script) {
$database = "---";
require("connections/main/select_connect.php");
$name = mysqli_real_escape_string($connection, $script);
$sql = mysqli_query($connection, "SELECT `version`, `status`, `zip_location` FROM `downloads` WHERE `name` = '{$name}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) != 1)
return false;
else {
$row = mysqli_fetch_array($sql);
if($row['status'] == 0)
return "Unavailable";
return $row['zip_location'].",".$row['version'];
}
}
// Function to add on to the user's daily API requests
function increment_api_requests($key) {
$database = "---";
require("connections/select_connect.php");
$date = date("l, F j, Y");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `id` FROM `api_requests` WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
require("connections/select_update_connect.php");
$sql = mysqli_query($connection, "UPDATE `api_requests` SET `requests` = `requests` + 1 WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
} else {
require("connections/insert_connect.php");
$sql = mysqli_query($connection, "INSERT INTO `api_requests` (`api_key`,`date`,`requests`) VALUES ('{$key}', '{$date}', '1')") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
}
}
// Function to encrypt sensitive data
function encrypt_data($data, $api_key, $encoding_key = 0, $offset = 0) {
// Random key generated by a combination of time, api key, and IP
if(!isset($encoding_key) || $encoding_key == 0) {
$offset = rand(0,32);
$encoding_key = substr(hash("sha256", base64_encode($api_key.uniqid().$_SERVER['REMOTE_ADDR'])), $offset, 32);
}
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $encoding_key, $data, MCRYPT_MODE_CBC, $iv);
$encrypted = $iv . $encrypted;
$secured_data = base64_encode($encrypted);
return $secured_data."&".$iv_size."&".$encoding_key."&".$offset;
}
?>[/syntax]
test_api.php
[syntax=php]<?php
$parameters = http_build_query(array(
"action" => "Update",
"api_key" => "92bd30a6e35cc7a0666bfb7e1b23474c6b82d63fc2842d4b90876dd9e7487a54",
"script" => "JQuery Chat"
));
$curl = curl_init("http://site/api/?{$parameters}");
curl_setopt($curl, CURLOPT_USERAGENT, "site Auto Updater");
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // For maintenance
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
// Just an example, it output the following during one request
// {"status":"Success","message":"Successful API request!","var1":"Mg==","var2":"MTY=","var3":"MGMxNjdlOGE5N2JlMjgzNmYwYjBhODE1MTBmZTQ4NjI=","var4":"Nw==","var5":"MS4yLjE="}
?>[/syntax]
Any tips would be greatly appreciated!
api/index.php
[syntax=php]<?php
// Ignore these. Just for testing
error_reporting(E_ALL);
ini_set('display_errors', 1);
if($_SERVER['HTTP_USER_AGENT'] != "site Auto Updater")
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
if(!isset($_GET['action']))
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
// User is installing a script
if($_GET['action'] == "Create") {
// Not done yet
// User is updating a script
} else if($_GET['action'] == "Update") {
if(!isset($_GET['api_key']))
die(json_encode(array("status" => "Error", "message" => "Invalid API key specified.")));
if(!isset($_GET['script']))
die(json_encode(array("status" => "Error", "message" => "Invalid script specified.")));
require("functions.php");
// Check validity of the API key.
if(valid_api_key($_GET['api_key']) === false)
die(json_encode(array("status" => "Error", "message" => "Invalid API key specified.")));
// Check if the user has requested too many times today
if(max_requests($_GET['api_key']) === true)
die(json_encode(array("status" => "Error", "message" => "You have reached the limit of API requests today.")));
// Check if user has valid access to the script
if(valid_usage($_GET['api_key'], $_GET['script']) === false)
die(json_encode(array("status" => "Error", "message" => "You do not have permission to update this script.")));
// Get the script data
$parts = get_file($_GET['script']);
// If the requested script does not exist
if($parts === false) {
increment_api_requests($_GET['api_key']);
die(json_encode(array("status" => "Error", "message" => "The requested script is invalid.")));
// If we're updating the script or something
} else if($parts == "Unavailable") {
increment_api_requests($_GET['api_key']);
die(json_encode(array("status" => "Error", "message" => "The requested script is currently unavailable for update. Please try again later.")));
}
// Zip location - 0, Version - 1
$part = explode(",", $parts);
$num = 1;
// If a temporary directory exists, increment the directory number to prevent issues
while(is_dir("tmp/tmp_dir-{$num}"))
$num++;
$zip = new ZipArchive;
// Get the zipped script file
$res = $zip->open("../scripts/downloads/{$part['0']}");
if($res === true) {
// Extract the script into a temporary directory
@$zip->extractTo("tmp/tmp_dir-{$num}/"); // @ to suppress an error that occurs randomly. Google has no clue how to fix
$zip->close();
} else
die(json_encode(array("status" => "Error", "message" => "Sorry. There was an error preparing your update on our end. If this error persists, please contact us at support@site")));
// Create the tracking file for the cron job to access
$track = fopen("tmp/tmp_dir-{$num}/track_file.txt", "w");
fwrite($track, base64_encode(time()));
fclose($track);
// The script directory name afte the zip has been extracted
$absolute_script = str_replace(" ", "_", $_GET['script'])."_v{$part['1']}";
// Get the files in the extracted directory
$dir = @scandir("tmp/tmp_dir-{$num}/{$absolute_script}");
if($dir === false)
die(json_encode(array("status" => "Error", "message" => "There was an error producing your update.")));
unset($dir['0'], $dir['1']);
// Move the files out of the directory for convenience
foreach($dir as $src) {
rename("tmp/tmp_dir-{$num}/{$absolute_script}/{$src}", "tmp/tmp_dir-{$num}/{$src}");
}
// Remove the now empty directory
rmdir("tmp/tmp_dir-{$num}/{$absolute_script}");
// Create a new iterator
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator("tmp/tmp_dir-{$num}/"));
// Go through each file and encrypt the contents
foreach($iterator as $file) {
$extension = end(explode(".", $file));
$exceptions = array("gif", "png", "jpg", "jpeg");
// If the current file does not have an extension listed in the exceptions
if(!in_array($extension, $exceptions) && $file != "tmp/tmp_dir-{$num}/track_file.txt") {
$code = file_get_contents($file);
// Code - 0, Size - 1, Random Key - 2, Offset - 3
if(isset($random_key)) {
$returned_data = encrypt_data($code, $_GET['api_key'], $random_key);
$encoded = explode("&", $returned_data);
} else {
$returned_data = encrypt_data($code, $_GET['api_key']);
$encoded = explode("&", $returned_data);
$random_key = $encoded['2'];
$offset = $encoded['3'];
}
file_put_contents($file, $encoded['0'], LOCK_EX);
}
}
increment_api_requests($_GET['api_key']);
// Var1 - Tmp Directory
// Var2 - Size
// Var3 - Key
// Var4 - Offset
// Var5 - Version
die(json_encode(array("status" => "Success", "message" => "Successful API request!", "var1" => base64_encode($num), "var2" => base64_encode($encoded['1']), "var3" => base64_encode($random_key), "var4" => base64_encode($offset), "var5" => base64_encode($part['1']))));
} else
die(json_encode(array("status" => "Error", "message" => "Unauthorized API request.")));
?>[/syntax]
api/functions.php
[syntax=php]<?php
// Just for testing. Normal will be 5 or 10 probably
define("MAX_REQUESTS", 500);
// Function to check validity of the API key
function valid_api_key($key) {
// Check for 64-digit hash format
if(strlen($key) != 64 || !preg_match("#^[0-9a-f]{64}$#i", $key))
return false;
$database = "---";
require("connections/select_connect.php");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `id`,`access` FROM `users` WHERE `api_key` = '{$key}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
$row = mysqli_fetch_array($sql);
if($row['access'] == 0)
return false;
else
return true;
} else
return false;
}
// Function to check if users have requested too much
function max_requests($key) {
$database = "---";
require("connections/select_connect.php");
$date = date("l, F j, Y");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `requests` FROM `api_requests` WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
$row = mysqli_fetch_array($sql);
if($row['requests'] >= MAX_REQUESTS)
return true;
else
return false;
} else
return false;
}
// Function to check if the user has valid permission to use the script
function valid_usage($key, $script) {
$database = "---";
require("connections/select_connect.php");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `valid_for` FROM `users` WHERE `api_key` = '{$key}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) != 0) {
$row = mysqli_fetch_array($sql);
$valid_for = explode(",", $row['valid_for']);
if(!in_array($script, $valid_for)) {
increment_api_requests($key);
return false;
} else
return true;
} else
return false;
}
// Function to get the file name of the requested script
function get_file($script) {
$database = "---";
require("connections/main/select_connect.php");
$name = mysqli_real_escape_string($connection, $script);
$sql = mysqli_query($connection, "SELECT `version`, `status`, `zip_location` FROM `downloads` WHERE `name` = '{$name}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) != 1)
return false;
else {
$row = mysqli_fetch_array($sql);
if($row['status'] == 0)
return "Unavailable";
return $row['zip_location'].",".$row['version'];
}
}
// Function to add on to the user's daily API requests
function increment_api_requests($key) {
$database = "---";
require("connections/select_connect.php");
$date = date("l, F j, Y");
$key = mysqli_real_escape_string($connection, $key);
$sql = mysqli_query($connection, "SELECT `id` FROM `api_requests` WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
if(mysqli_num_rows($sql) == 1) {
require("connections/select_update_connect.php");
$sql = mysqli_query($connection, "UPDATE `api_requests` SET `requests` = `requests` + 1 WHERE `api_key` = '{$key}' AND `date` = '{$date}'") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
} else {
require("connections/insert_connect.php");
$sql = mysqli_query($connection, "INSERT INTO `api_requests` (`api_key`,`date`,`requests`) VALUES ('{$key}', '{$date}', '1')") or die(json_encode(array("status" => "Error", "message" => "Sorry. It seems there was a communication failure on our end. Please try again later. If the issue persists, please contact us at support@site")));
mysqli_close($connection);
}
}
// Function to encrypt sensitive data
function encrypt_data($data, $api_key, $encoding_key = 0, $offset = 0) {
// Random key generated by a combination of time, api key, and IP
if(!isset($encoding_key) || $encoding_key == 0) {
$offset = rand(0,32);
$encoding_key = substr(hash("sha256", base64_encode($api_key.uniqid().$_SERVER['REMOTE_ADDR'])), $offset, 32);
}
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $encoding_key, $data, MCRYPT_MODE_CBC, $iv);
$encrypted = $iv . $encrypted;
$secured_data = base64_encode($encrypted);
return $secured_data."&".$iv_size."&".$encoding_key."&".$offset;
}
?>[/syntax]
test_api.php
[syntax=php]<?php
$parameters = http_build_query(array(
"action" => "Update",
"api_key" => "92bd30a6e35cc7a0666bfb7e1b23474c6b82d63fc2842d4b90876dd9e7487a54",
"script" => "JQuery Chat"
));
$curl = curl_init("http://site/api/?{$parameters}");
curl_setopt($curl, CURLOPT_USERAGENT, "site Auto Updater");
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // For maintenance
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
// Just an example, it output the following during one request
// {"status":"Success","message":"Successful API request!","var1":"Mg==","var2":"MTY=","var3":"MGMxNjdlOGE5N2JlMjgzNmYwYjBhODE1MTBmZTQ4NjI=","var4":"Nw==","var5":"MS4yLjE="}
?>[/syntax]
Any tips would be greatly appreciated!