Escaping column names in PDO statements - php

I am currently building a query where both the field/column and value parts possibly consist of user inputted data.
The problem is escaping the fieldnames.
I'm using prepared statements in order to properly escape and quote the values but when escaping the fieldnames i run into trouble.
mysql_real_escape_string requires a mysql connection resource in order to us so that is ruled out
PDO::quote adds quotes around the fieldnames which renders them useless in a query too
addslashes works but isn't really safe
Anyone has an idea on what the best way is to properly insert the fieldnames into the query before passing it to PDO::prepare?

The ANSI standard way of doing a delimited identifier is:
SELECT "field1" ...
and if there's a " in the name, double it:
SELECT "some""thing" ...
Unfortunately this doesn't work in MySQL with the default settings, because MySQL prefers to think double quotes are an alternative to single quotes for string literals. In this case you have to use backticks (as outlined by Björn) and backslash-escaping.
To do backslash escaping correctly, you would need mysql_real_escape_string, because it's character-set-dependent. But the point is moot, because neither mysql_real_escape_string nor addslashes escape the backquote character. If you can be sure there will never be non-ASCII characters in the column names you can get away with just manually backslash-escaping the ` and \ characters.
Either way, this isn't compatible with other databases. You can tell MySQL to allow the ANSI syntax by setting the config option ANSI_QUOTES. Similarly, SQL Server also chokes on double quotes by default; it uses yet another syntax, namely square brackets. Again, you can configure it to support the ANSI syntax with the ‘quoted_identifier’ option.
Summary: if you only need MySQL compatibility:
a. use backquotes and disallow the backquote, backslash and nul character in names because escaping them is unreliable
If you need cross-DBMS compatibility, either:
b. use double quotes and require MySQL/SQL-Server users to change the configuration appropriately. Disallow double-quote characters in the name (as Oracle can't handle them even escaped). Or,
c. have a setting for MySQL vs SQL Server vs Others, and produce either the backquote, square bracket, or double-quote syntax depending on that. Disallow both double-quotes and backslash/backquote/nul.
This is something you'd hope the data access layer would have a function for, but PDO doesn't.
Summary of the summary: arbitrary column names are a problem, best avoided if you can help it.
Summary of the summary of the summary: gnnnnnnnnnnnh.

The correct answer, is str_replace("`", "``", $fieldname)
Wrong:
mysql> SELECT `col\"umn` FROM user;
ERROR 1054 (42S22): Unknown column 'col\"umn' in 'field list'
Right:
mysql> SELECT `kid``s` FROM user;
ERROR 1054 (42S22): Unknown column 'kid`s' in 'field list'
mysql> SELECT ```column``name``` FROM user;
ERROR 1054 (42S22): Unknown column '`column`name`' in 'field list'
(Note that in last example, the column name has 3 (three) extra back-ticks in it, just to show an extreme case)

This may affect performance, but it should be secure.
First run a DESCRIBE table query to get a list of allowed field names, then match these agaisnt the user submitted data.
If there's a match then you can use the user-submitted data without the need for any escaping.
If there's not match then it's a typo or a hack - either way it's an 'error' in the inputted data and the query should not be run.
The same could be done for 'dynamic' table names by running a SHOW TABLES query and matching from that result set.
In one of my applications I have an 'install' script; part of this queries the database and table field names and then writes a php file that is always referred back to so I'm not constantly running DESCRIBE queries agains the database, eg
$db_allowed_names['tableName1']['id'] = 1;
$db_allowed_names['tableName1']['field1'] = 1;
$db_allowed_names['tableName1']['field2'] = 1;
$db_allowed_names['tableName2']['id'] = 1;
$db_allowed_names['tableName2']['field1'] = 1;
$db_allowed_names['tableName2']['field2'] = 1;
$db_allowed_names['tableName2']['field3'] = 1;
if($db_allowed_names['tableName1'][$_POST['field']]) {
//ok
}
I use array keys like this as the if statement is a little faster than an in_array lookup

How about something like this?
function filter_identifier($str, $extra='') {
return preg_replace('/[^a-zA-Z0-9_'.$extra.']/', '', $str);
}
try {
$res = $db->query('SELECT '.filter_identifier($_GET['column'], '\*').' FROM '.filter_identifier($_GET['table']).' WHERE id = ?', $id);
} catch (PDOException $e) {
die('error querying database');
}
This is a simple white-list based character list. Any characters not in the list will get removed. Fortunately for me, I was able to make the database and tables, so I know there will never be any characters outside "a-zA-Z0-9_" (note: no space). You can add extra characters to the list via the $extra arg. If someone were to try and put "(SELECT * FROM users);--" in 'column', it would filter down to "SELECT*FROMusers", which would thrown an exception :)
I try to avoid doing extra queries if at all possible (I'm very performance sensitive). So things like doing a DESCRIBE beforehand or hard-coding an array of tables/columns to check against is something I'd rather not do.

Wierd design of a project, but for your problem: Surround your field names with ` and use addslashes for the name as well.
select `field1`, `field2` from table where `field3`=:value

Related

Vulnerability to SQL injection even when SQLite3::escapeString() is used and no user input is asked?

I am referring to this answer of mine to another question, which another user criticized because vulnerable to SQL injection, even if no user input is requested and escape procedure is called.
The following code is used to create a .sql dump of an SQLite database, using only PHP code with no call to sqlite3 tool (which was the original request of the author of the question).
<?php
$db = new SQLite3(dirname(__FILE__)."/your/db.sqlite");
$db->busyTimeout(5000);
$sql="";
$tables=$db->query("SELECT name FROM sqlite_master WHERE type ='table' AND name NOT LIKE 'sqlite_%';");
while ($table=$tables->fetchArray(SQLITE3_NUM)) {
$sql.=$db->querySingle("SELECT sql FROM sqlite_master WHERE name = '{$table[0]}'").";\n\n";
$rows=$db->query("SELECT * FROM {$table[0]}");
$sql.="INSERT INTO {$table[0]} (";
$columns=$db->query("PRAGMA table_info({$table[0]})");
$fieldnames=array();
while ($column=$columns->fetchArray(SQLITE3_ASSOC)) {
$fieldnames[]=$column["name"];
}
$sql.=implode(",",$fieldnames).") VALUES";
while ($row=$rows->fetchArray(SQLITE3_ASSOC)) {
foreach ($row as $k=>$v) {
$row[$k]="'".SQLite3::escapeString($v)."'";
}
$sql.="\n(".implode(",",$row)."),";
}
$sql=rtrim($sql,",").";\n\n";
}
file_put_contents("sqlitedump.sql",$sql);
In the comments to this answer, user #Dharman insisted this code is vulnerable, and after asking to provide a full example of a case of how it could lead to problems, he told me to just open a question regarding the matter.
I personally feel there is no way this code could "explode" because of the contents already present inside the database to be dumped, but I'm no authority.
So I ask you instead.
It's pretty reasonable to name a table Order in an e-commerce application, but this causes a syntax error if you run a query like:
SELECT * FROM Order
Why? Because Order is a reserved keyword in SQLite. It introduces an ORDER BY clause. Using a table named Order in this way just creates a syntax error.
SQLite allows you to name tables after reserved words by delimiting the table name.
This is in the documentation I linked to about reserved words:
'keyword' A keyword in single quotes is a string literal.
"keyword" A keyword in double-quotes is an identifier.
[keyword] A keyword enclosed in square brackets is an identifier. This is not standard SQL. This quoting mechanism is used by MS Access and SQL Server and is included in SQLite for compatibility.
`keyword` A keyword enclosed in grave accents (ASCII code 96) is an identifier. This is not standard SQL. This quoting mechanism is used by MySQL and is included in SQLite for compatibility.
So you can use a table named Order without error like this:
SELECT * FROM "Order"
You shouldn't use SQLite3::escapeString($table[0]) because that's for string literals. Remember from the list above, single-quoted strings are string literals. The escapeString() function only escapes ' characters, so you can put that string inside single-quotes as the delimiter, and thus you can use strings like 'O\'Reilly'.
But escapeString() doesn't do anything for the double-quotes used for delimiting table names. There isn't a function provided for that. So you either have to be sure you have no table names that contain a " character, or else use other PHP functions to escape them.
$safeTable = str_replace('"', '\\"', $table[0]);
$rows = $db->query("SELECT * FROM \"{$safeTable}\"");
You should also read 8. Double-quoted String Literals Are Accepted on this page: https://www.sqlite.org/quirks.html

Special characters in PHP MySQL statement

I have some special characters stored in a MySQL database. Specifically ®. I want to SELECT all entries with these characters. I am currently using this statement:
mysql_query("SELECT * FROM table WHERE description LIKE '%®%' OR name
LIKE '%®%'");
It returns 0 entries. When i try something with %a% it returns a lot so I know everything is right, it is just some kind of a charset problem or something.
When I echo "®" it returns "å¨".
Also when I do the exact same query in phpmyadmin it works properly. Please help!
Read this its very help full to you
Just simply add those symbols to your text, and execute it as SQL query:
INSERT INTO tbl_name VALUES ("Here's my text: ©®");
When you want to display it one the website don't do anything with these symbols (but remember to escape at least <, >, & (using htmlspecialchars()) cause those has special meaning in XML/SGML (HTML) documents)
PS. Also remember to escape text passed to SQL query using mysql_real_escape_string() to avoid any SQL Injection problems. If your server has magic_quotes_gpc enabled disable it or at least filter your GET/POST/COOKIE data to its raw value. You should always consciously escape values.
EDIT:
According to your comment... I don't remember whether magic_quotes_gpc are enabled by default but you can easily undone magic quotes effect. Just on the very beginning of your PHP code add something like this:
if (get_magic_quotes_gpc()) {
array_walk_recursive($_GET, 'stripslashes');
array_walk_recursive($_POST, 'stripslashes');
array_walk_recursive($_COOKIE, 'stripslashes');
}
Now each GPC value should be always raw - without quotes - so you have to escape it manually before passing any variable into query.

Displaying fields from MySQL table but no field with quotes being printed

I am fetching data from a MySQL table to display it on a page. The script is displaying the information, but in my table normal quotes we're inserted as another type of quote characters such as. ( ’ ) and ( “ ” ) which are automatically formatted this way when something is typed in Microsoft Word 2010, which was used to type most of the entries in the table. So my guess are those are special characters. But whenever i test out displaying a field with actual single quotes ( ' ) and ( " " ) i receive a mysql_fetch_row expects parameter 1 to be a resource, boolean given error. This is the code i use:
$result = mysql_query("SELECT `question` FROM {$db_table_alt}");
while($field = mysql_fetch_row($result)) {
foreach($field as $fields) {
//build a unique section ID based on the ID that the Question belongs to
$uid = mysql_query("SELECT `id` FROM `questions` WHERE `question` LIKE '%$fields%'");
while($uidfield = mysql_fetch_row($uid)) {
But whenever i use this line
$fields = mysql_real_escape_string(stripslashes($fields));
The field with real quotes will display, but with forward slashes before the quote.
Can somebody help me find a solution to this please?
If mysql_fetch_row() is complaining about a boolean that means mysql_query either returned no rows or the SQL had an error (mysql_error() will tell you which).
If you're getting backslashes before the quotes in your returned data, then they are getting put in the database. That sounds like magic_quotes are enabled. You really want to turn that off as it's an obsolete and broken solution to a problem.
Also, I think you're going to have to learn about character encodings. A default MySQL install will be not be UTF8, I'm afraid, it will probably be ISO-8859-15. Word used to like writing text in Windows-1252 which is not the same. And then it gets more complicated with whatever browser, website and other things that talk to the database use. I believe PhpMyAdmin tries to run in UTF8, so data will get converted into your tables if they're not UTF8. This will also affect your queries looking for the "smart quotes".
You seem to have two distinct problems here.
One where $result is evaluating to a boolean, which means there is an error in the query generated (SELECT question FROM {$db_table_alt}). Try echoing that query out and manually running it. It may be that the table/view named by {$db_table_alt} does not exist.
The second is a string escaping problem. I expect the quotes are escaped in the database - using mysql_real_escape_string on the query will not alter whether the returned results are escaped or not.
Also, if your data in the database is escaped by slashes, you should read up on magic quotes and what to do about them: PHP docs on magic quotes. You should not have to do any string escaping when pulling data out of the DB.

Why is my query finding field entries that are numbers, but not number/letter combinations

Newbie here. The following function works fine when $color refers to an entry in the "style" field that is numberic e.g. "5000". But if the entry is "5000B" or letters entirely, it can't find it. Is this an indexing problem?
function get_shirt_colors_by_style($color)
{
db_connect();
$query = "SELECT style,sanmar_mainframe_color,unique_key,color_square_image
FROM sanmar_products WHERE style=$color
GROUP BY style ORDER BY style";
$result = mysql_query($query);
$data = mysql_fetch_array($result);
return $data;
}
It is failing to find alphanumeric comnbinations because the string is not quoted:
$query = "SELECT style,sanmar_mainframe_color,unique_key,color_square_image FROM sanmar_products WHERE style='$color' GROUP BY style ORDER BY style";
//-----------------------------------------------------------------------------------------------------------^^^^^^^^^
Numeric values need not be quoted in a MySQL query, but string values must always be surrounded in single quotes like '5000B'.
We assume the value of $color has already been escaped against SQL injection:
// Hopefully this happened already.
// If not, do it before running mysql_query()
$color = mysql_real_escape_string($color);
Is this an indexing problem?
No its a really bad coding problem.
Non-numeric values should be enclosed in quotes - actually literal values should always be enclosed in quotes when the underlying column type is numeric - otherwise you're likely to run into performance issues. You've also got a problem with managing errors in your code - if a query containing "WHERE style=5000B" is sent to the database it will always return an error - that you have no visibility of this error means that you've got a lot of important functionality missing from your site. Indeed, since content representations whould always be validated / changed at the point where the value leaves PHP, that also implies that your code is probably wide open to SQL injection attacks.
...and then there's the database design issues evident here: 'style' implies a non-unique identifier yet you are returning a single row from your query without any explicit ordering other than on the field you've selected on (i.e. even if it were unique, its very innefficient).
try using single quotes here:
WHERE style='$color'

Php/SQL/DB2 special characters in where clause

I am trying to SQL a DB2 database (on an iSeries) using PHP and "DB2_exec"- not mysql.
I have these characters in my WHERE clause (variable $EncSSN) which cause the SQL statement to stop: ðIn*Éæng “"Ò×ÑRÈ•`
The SQL is constructed as:
select EENUM, EESSN
from EEMAST
where EESSN = '$EncSSN'
The field in the table EESSN contains encrypted values.
- I get no errors and no log entries. The html renders a blank page.
- I have tried replacing (str_replace) quotes, single quotes, period, etc with escape character '\'
- I can't use mysql_real_escape_string because I am loading the db2_connect resource.
If I change the SQL statement above's where to select a value from a different field, my html is rendered properly.
Can you think of anyway I can accomplish this?
Steven
Prepare the SQL and set the parameter for where clause using the array approach. Never ever attempt to build SQL queries by string functions.
try the addslashes() function http://php.net/manual/en/function.addslashes.php
or heredoc or nowdoc syntax
http://php.net/manual/en/language.types.string.php
you could also put the sql in a stored proc, but you may have the same issues for the parameter value and need to try one of the above.

Categories