Skip to content

Instantly share code, notes, and snippets.

@craigfrancis
Last active May 17, 2021 14:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save craigfrancis/901aa0479379fe9c261ccb2e33ebdcd7 to your computer and use it in GitHub Desktop.
Save craigfrancis/901aa0479379fe9c261ccb2e33ebdcd7 to your computer and use it in GitHub Desktop.
Using is_literal() with table/field names
<?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