-
-
Save craigfrancis/901aa0479379fe9c261ccb2e33ebdcd7 to your computer and use it in GitHub Desktop.
Using is_literal() with table/field names
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
//-------------------------------------------------- | |
class db { | |
private $pdo; | |
private $protection_level = 2; // Probably should default to 1 at first. | |
function __construct() { | |
$this->pdo = new PDO('mysql:dbname=...;host=...', '...', '...', [PDO::ATTR_EMULATE_PREPARES => false]); | |
} | |
function literal_check($var) { | |
if (!function_exists('is_literal') || is_literal($var)) { | |
// Not supported, or is a programmer defined string. | |
} else if ($var instanceof unsafe_sql) { | |
// Not ideal, but at least you know this one is unsafe. | |
} else if ($this->protection_level === 0) { | |
// Programmer aware, and is choosing to bypass this check. | |
} else if ($this->protection_level === 1) { | |
trigger_error('Non-literal detected!', E_USER_WARNING); | |
} else { | |
throw new Exception('Non-literal detected!'); | |
} | |
} | |
function unsafe_disable_injection_protection() { | |
$this->protection_level = 0; // Not recommended, try unsafe_sql for special cases. | |
} | |
function executeQuery($sql, $parameters, $aliases = []) { | |
$this->literal_check($sql); | |
foreach ($aliases as $name => $value) { | |
if (!preg_match('/^[a-z0-9_]+$/', $name)) { | |
throw new Exception('Invalid alias name "' . $name . '"'); | |
} else if (!preg_match('/^[a-z0-9_]+$/', $value)) { | |
throw new Exception('Invalid alias value "' . $value . '"'); | |
} else { | |
$sql = str_replace('{' . $name . '}', '`' . $value . '`', $sql); | |
} | |
} | |
$statement = $this->pdo->prepare($sql); | |
$statement->execute($parameters); | |
return $statement->fetchAll(); | |
} | |
} | |
//-------------------------------------------------- | |
class unsafe_sql { | |
private $value = ''; | |
function __construct($unsafe_sql) { | |
$this->value = $unsafe_sql; | |
} | |
function __toString() { | |
return $this->value; | |
} | |
} | |
//-------------------------------------------------- | |
$parameters = []; | |
$aliases = [ // Using sprintf() so it's not marked as a literal | |
'with_1' => sprintf('w1'), // Not needed, just demoing a user defined alias. | |
'with_2' => sprintf('w2'), | |
'table_1' => sprintf('user'), | |
]; | |
$with_sql = '{with_1} AS (SELECT id, name, email, deleted FROM {table_1})'; | |
$sql = " | |
WITH | |
$with_sql | |
SELECT | |
t.name, | |
t.email | |
FROM | |
{with_1} AS t | |
WHERE | |
t.deleted IS NULL"; | |
$name = ($_GET['name'] ?? NULL); | |
if ($name) { | |
$sql .= ' AND t.name LIKE ?'; | |
$parameters[] = '%' . $name . '%'; | |
} | |
$ids = ($_GET['ids'] ?? '1,2,3'); | |
$ids = array_filter(explode(',', $ids)); | |
if (count($ids) > 0) { | |
$in_sql = '?'; | |
for ($k = count($ids); $k > 1; $k--) { | |
$in_sql .= ',?'; // Could also use literal_implode() | |
} | |
$sql .= ' AND t.id IN (' . $in_sql . ')'; | |
$parameters = array_merge($parameters, $ids); | |
} | |
$order_fields = ['name', 'email']; | |
$order_id = array_search(($_GET['sort'] ?? NULL), $order_fields); | |
$sql .= ' | |
ORDER BY ' . $order_fields[$order_id]; // When we can limit to known fields. | |
$db = new db(); | |
var_dump($sql, $parameters, $aliases); | |
var_dump($db->executeQuery($sql, $parameters, $aliases)); | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment