Final update 5/11/2018:
Ok, here's my final answer to my own question, meticulously constructed over several weeks and tested for two months on my active e-commerce website. It works marvelously.
This also thwarts other types of non-SQL-injection attacks, such as attempts to inject bogus blog posts and other attempts to inject global variables with urls and other detritus.
//************ Foil SQL injection attacks early by validating the URL and parameters ********
//*** Rejecting a bunch of special characters - make sure to change in both places if you change this!
function VerifyURL()
{
global $URL_LOG;
global $BOGUSURL_LOG;
$RejectChars = "'\"%;\\/()[]"; //* If you change this, also change the Javascript below
$SleepTime = rand(2, 15); //* Seconds to sleep to slow down the hackers (random to confuse them)
//* REQUEST_URI breaks down to: SCRIPT_NAME, PATH_INFO, QUERY_STRING
$UrlParms = $_SERVER['REQUEST_URI'];
$ScriptName = $_SERVER['SCRIPT_NAME'];
$PathInfo = $_SERVER['PATH_INFO'];
$QueryString = $_SERVER['QUERY_STRING'];
$ClientIPAddress = str_pad($_SERVER['REMOTE_ADDR'], 15);
//* We don't use PATH_INFO (extra "/something/" after the SCRIPT_NAME) so this is always BOGUS
if (isset($PathInfo))
{
LogToFileOnly($ClientIPAddress." *BOGUS PATH_INFO: ".$UrlParms, $URL_LOG);
LogToFileOnly($ClientIPAddress." *BOGUS PATH_INFO: ".$UrlParms, $BOGUSURL_LOG);
if (!empty($_POST)) //* Log any POST data, too, to help analyze the intent of this attack
{
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $URL_LOG);
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $BOGUSURL_LOG);
}
sleep($SleepTime); //* Slow down the hackers
die();
}
//* Look for unusual characters that should not be in the URL and abort
$FoundAnomalies = (strpbrk($QueryString, $RejectChars));
$Exception = strpos($ScriptName, "/srch.php"); //* if this is the search page, let it through
$Exception |= strpos($ScriptName, "/amn2/"); //* if this is an admin page, let it through
if ($FoundAnomalies && !$Exception)
{
LogToFileOnly($ClientIPAddress." *BOGUS URL: ".$UrlParms, $URL_LOG);
LogToFileOnly($ClientIPAddress." *BOGUS URL: ".$UrlParms, $BOGUSURL_LOG);
if (!empty($_POST)) //* Log any POST data, too, to help analyze the intent of this attack
{
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $URL_LOG);
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $BOGUSURL_LOG);
}
sleep($SleepTime); //* Slow down the hackers
die();
}
else
LogToFileOnly($ClientIPAddress." Valid URL: ".$UrlParms, $URL_LOG);
//* Now look for unusual characters injected into a POST, if a POST was sent
if (!empty($_POST) && !$Exception)
{
foreach ($_POST as $key => $value)
{
//* Validate both keys and values
$FoundAnomalies = (strpbrk($key, $RejectChars) || strpbrk($value, $RejectChars));
//$Exception = ($key == "AmnMsg"); //* if this is the admin emailing message page, let it through - we want to email urls and stuff
if ($FoundAnomalies && !$Exception)
{
//* Found an anomaly, report it and die
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $URL_LOG);
LogToFileOnly($ClientIPAddress." Valid URL: ".$UrlParms, $BOGUSURL_LOG);
LogArrayToFileOnly($ClientIPAddress." *BOGUS POST Array:", $_POST, $BOGUSURL_LOG);
sleep($SleepTime); //* Slow down the hackers
die();
}
}
LogArrayToFileOnly($ClientIPAddress." Valid POST Array:", $_POST, $URL_LOG);
}
}
//*** This code is added near the bottom of the page, and is for javascript filtering, but could be for other purposes
function FinalHTMLCode()
{
//* Attach a filter to every text input field to filter prohibited characters using javascript
echo '
<script>
//* Set the key filtering for every text type input field in the entire page
var node = document.querySelectorAll("input[type=text],input[type=password],textarea");
for (var i = 0, len = node.length; i < len; i++)
{
node[i].addEventListener("keyup", OnKeyUpEvent); //* Filter later on keypresses (and paste)
FilterInputChars(node[i]); //* Do the filtering immediatly, too
}
function OnKeyUpEvent()
{
FilterInputChars(this);
}
function FilterInputChars(textfield)
{
//* Remove prohibited characters globally in this field - also works when user pastes text!
textfield.value=textfield.value.replace(/[\\\'\"%\;\\\\/()\[\]]/g,\'\');
}
</script>
'; //* End echo
}
The number one benefit to this approach is that it detects hacking attempts early in the processing, and just die()s, therefore causing no additional cpu load of opening databases, etc., and no further information to the hacker about why the attempt failed. I also added code to confuse an active hacker by delaying the response randomly (see sleep()), so he thinks he's accomplishing something.
A legitimate user must not enter these prohibited characters into forms, so I have set up some Javascript that automatically filters all of the special characters. You can place this single line of code near the bottom of your HTML and it will automatically attach the Javascript to every text field and filter the characters:
<?php FinalHTMLCode(); //* Block prohibited characters from all text fields ?>
Note that the code also includes exceptions for pages where I know there might be a special character entered by a legitimate user but where there is no chance of a SQL injection attack succeeding.
Other improvements I made during this journey included the creation of a SQL web user account that did not have admin privileges, but just SELECT, INSERT, and UPDATE privileges for the database. This keeps a malicious attacker from DROPing tables or DELETEing rows or other potentially malicious activity.