I've just noticed that if I do a MySQL request like this one:
SELECT 1 FROM myTable WHERE id = 'asdf'
Then the string 'asdf' is casted to 0.
It means that I have a record with id 0 this will match.
The format of the id field is int(8).
What is the best way to proceed:
I need to check (by PHP for example) that my value is numerical only?
There is a MySQL way to do that?
I must remove my record with id 0? (bad)
Just write your queries so that they don't use numeric fields as if they were textual ones.
If id is a numeric field, then your where clause can never be useful. Yes, it would be good if MySQL actively complained about it - but fundamentally you shouldn't be writing code which runs bad queries to start with.
How did that query enter your system? Is the 'asdf' part direct user input? Can you use parameterized SQL instead?
If you're genuinely intending to query a numeric field, you should make sure that your input is numeric first. Convert the text to an integer in your calling code, not in the database.
You must first sanitize your inputs via PHP.
$id = 'asdf';
if(is_numeric($id)){
$query("SELECT 1 FROM myTable WHERE id = $id");
}else{
die("ID is not numeric");
}
Or you can do:
SELECT 1 FROM myTable WHERE id = 'asdf' AND 'asdf' REGEXP '^-?[0-9]+$'
This would cause the regex to = false, causing no rows to return.
Since pdo prepared statements binding with correct types will not raise any error (except if mysql strict mode is enabled), your only choice is to ensure and control the types of your variables within your php to "correct" the permissivity of these languages.
[thanks to commentators]
Related
I try to query a mysql column which has the name "5". This outputs the wrong column which has the name "22".
This is my php code, $pid is the variable I am getting from the android app and is always a number. When I search with $pid = 5, instead of getting the column "5" with artist1, it is getting the column "22" with eternal1.
Basically it confuses the Column Name with # in the first print screen. If the # doesn't exist, then it searches correctly; so if I search with 16 I get the column 16. How do I fix this?
$pid = $_GET["pid"];
$result=mysql_query("SELECT * FROM TableComments WHERE `$pid` IS NOT NULL ");
http://imgur.com/WFsfEtB
http://imgur.com/rZA27XC
This is by design in the SQL dialects I can think of offhand. I know several people who, out of habit, add order by 0 desc or order by 1 to ad hoc queries, the first to pick what typically is the ID column and the second what is often a "Name" column or similar. They're querying based on the ordinal position of the field in the query (or the schema, in the case of *)
In order to get a column named 5, you need to use the appropriate SQL quoting mechanism for your dialect and configuration. As an example, Microsoft Sql and Access would typically use select * from tablecomments where [5]=5; in Postgres and Oracle you'd use select * from tablecomments where "5"=5, and in Mysql, Quoted Identifiers are quoted with a backtick select * from tablecomments where `5`=5. In Microsoft SQL you can also make things more like Oracle and Postgres if your session has SET QUOTED_IDENTIFIER ON, in which case you'd use quotes instead of square brackets.
As an aside, but a very important one, you should not take user input and directly embed it in SQL. If someone were to intercept the HTTP transmission between your Android app and your PHP app (trivial with a proxy like Charles or Fiddler), they'd be able to replay the http request with arbitrary SQL injected. As other commenters have noted, please use a parameterized query instead.
Since you're trying to modify the query itself rather than the parameters, you may need to consider whitelisting the allowed field names (or compare the string you're sent against the fields represented in the schema).
Wrap the column-name into backticks:
SELECT * FROM TableComments WHERE `$pid` IS NOT NULL
Start using PDO instead of old, unsafe and deprecated mysql_*
Here's an example with a function inside a model in my Codeigniter application.
function is_subscriber($thread_id) {
$this->db->query("SELECT user_id FROM subscriptions WHERE thread_id = $thread_id");
}
Where and how should I escape $thread_id? Since the database field thread_id is an integer $thread_id should not be surrounded by single quotes in the query. So I'm wondering how I should escape numbers in Codeigniter? Should I by my self check if $thread_id is a number, or is there a way to do it with Codeigniter?
I found escape_like_str() which escapes without adding the quotes, as opposed to escape() that adds quotes. Should I also use escape_like_str() for numbers?
Secondly, should I "validate" $thread_id in the model or in the controller?
The codeigniter database adapter has built-in query parameterization; it's as simple as replacing each parameter with a question mark, and passing all your variables in an array as the second argument.
$this->db->query("SELECT user_id FROM subscriptions WHERE thread_id = ?", array($thread_id));
When you set it up this way, there's no possibility of sql injection and you don't have to worry about escaping your data (at least not for the SQL portion; XSS and html escaping is still a concern)
Sam's way is the proper Codeigniter way to do this. For more of a standard PHP way:
$thread_id = intval($thread_id);
$this->db->query("SELECT user_id FROM subscriptions WHERE thread_id = ". $thread_id);
Although you have to use a query from Sam Dufel's answer, just to answer your numerous questions and assumptions
Where and how should I escape $thread_id?
A general rule is - as close to the query building as possible.
Thus, the best method would be to use a placeholder, as it will guarantee that the data will be properly formatted right before insertion into query.
Since the database field thread_id is an integer $thread_id should not be surrounded by single quotes in the query.
This is not quite true.
Only if you run mysql in strict mode, you can't use quotes.
but in general you can use quotes all the way. So, formatting numbers as strings is quite possible solution.
So I'm wondering how I should escape numbers...
The only way to escape a number is to cast it. intval() is okay
... in Codeigniter?
if it lets you to use a placeholder - go for it.
Should I by my self check if $thread_id is a number, or is there a way to do it with Codeigniter?
In gneral, there is no need to check. At the database level, only proper formatting needed.
Should I also use escape_like_str() for numbers?
It will make absolutely, definitely no sense.
Should you use a influenza vaccine to protect your money from robbery? The same here. It's different matters.
should I "validate" $thread_id in the model or in the controller?
First of all you have to distinguish input validation and database-specific formatting.
Second one is obligatory and unconditional, while first one is optional.
As long as this "validation" has nothing to do with DB formatting, you can do it anywhere.
Personally I'd do it in controller, and throw 404 on fail. Because it's controller to do HTTP specific actions, while model have to be environment-independent.
But again, such a validation has nothing to do with database-specific formatting, which have to be applied always.
when using a standard query you may want to use $this->db->escape_like_str() like so
$sql = 'SELECT name FROM users WHERE id = '.$this->db->escape_like_str($id).'AND user_id ='.$this->db->escape_like_str($user_id).'LIMIT 1';
Or:
$sql = 'SELECT name FROM users WHERE id = ? AND user_id = ? LIMIT 1'
$query = $this->db->query(array($id,$user_id));
This methods does not validate numbers just escapes them for security , if you want to validate a number you can use PHP native function is_numeric or ctype_digit or is_int() you can read the PHP manual or discover your self on what you need to use . .
Example:
$user_input = $_POST['input'];
'SELECT '.$user_input.' FROM table_name'
So it's selecting a column in a database based on a secure (this example isn't secure obviously) value. Is this practical/allowable code?
In SQL you simply send a string to the DBMS (like MySQL). Because of this you can build any string you want and submit it to the DBMS.
You just have to make sure the resulting SQL query is valid. That means that e.g. the columns exist and that no invalid symbol appears.
On it's face, this code is valid, assuming that $user_input must be a valid column name, which means that it must exist and must not contain any special characters, reserved words, etc. (unless they're escaped).
As you said, however, this code isn't secure, but as long as you plan to build the query securely and use PDO or MySQLi (no deprecated mysql_* functions...), you should be fine. If you need an example that doesn't use deprecated functions (including mysql_real_escape_string, which is also being deprecated) I'll provide one.
I know you stated that you know this code isn't secure, but here's another example if you're curious. As was discussed in the comments and this question, this input:
$user_input = '; DELETE FROM table_name ; *';
'SELECT '.$user_input.' FROM table_name'
will delete the entire contents of the table table_name. Even though this code raises a syntax error, MySQL will continue to execute it, thus effectively truncating table_name.
I have a MySQL table with ID as a primary key and other for this matter non-important fields.
What I would like to do is delete multiple records by sending a list of IDs for deletion as a parameter to stored procedure.
I know how to do this manually (building a query directly in PHP) but I would like to avoid that and do all my SQL directly in the DB.
Tried searching SO but couldn't find any related questions. Sorry if this is a duplicate.
Thanks
In accordance to http://dev.mysql.com/doc/refman/5.1/en/faqs-stored-procs.html#qandaitem-B-4-1-17 you can't do it directly.
But I think you can try to the following trick:
Create string of you ids in php like 'id1,id2,id3'.
Use prepared statement for binding this sting on fly.
Maybe it helps.
You could try something like
DELETE FROM sometable WHERE FIND_IN_SET(idfield, #param)
no idea if this'd work (and don't have access to a mysql instance right now to test on). Basically the problem is that if you pass in a comma-separated value list into a paramter, it'll just be a string inside the sproc, and doing a WHERE id IN ('1,2,3') would fail, since that's just a simple string and not at all the same as WHERE id IN (1,2,3). The find_in_set() function should take care of that.
I gave +1 to #Marc B for clever use of FIND_IN_SET(). It won't be able to use an index, so the performance won't be good, but it should work.
Another solution that can work (but will be slow as well, because it can't use an index):
DELETE FROM sometable
WHERE CONCAT(',', param, ',') LIKE CONCAT('%,', idfield, ',%')
The solution that #Andrej L describes isn't really parameter binding, it's interpolation of a stored procedure argument into a dynamic SQL string prior to preparing it.
SET sql = CONCAT('DELETE FROM sometable WHERE idfield IN (', param, ')');
PREPARE stmt FROM sql;
EXECUTE stmt;
You can't parameterize a list of values with a single parameter, even if the parameter's value looks like a comma-separated list of integers.
Interpolation can work, and it will benefit from an index, but be careful to filter the string so it contains only numeric digits and commas. Otherwise you introduce a significant risk of SQL injection (debunking the claim that some people make that stored procedures are inherently more secure).
I used to use the standard mysql_connect(), mysql_query(), etc statements for doing MySQL stuff from PHP. Lately I've been switching over to using the wonderful MDB2 class. Along with it, I'm using prepared statements, so I don't have to worry about escaping my input and SQL injection attacks.
However, there's one problem I'm running into. I have a table with a few VARCHAR columns, that are specified as not-null (that is, do not allow NULL values). Using the old MySQL PHP commands, I could do things like this without any problem:
INSERT INTO mytable SET somevarchar = '';
Now, however, if I have a query like:
INSERT INTO mytable SET somevarchar = ?;
And then in PHP I have:
$value = "";
$prepared = $db->prepare($query, array('text'));
$result = $prepared->execute($value);
This will throw the error "null value violates not-null constraint"
As a temporary workaround, I check if $value is empty, and change it to " " (a single space), but that's a horrible hack and might cause other issues.
How am I supposed to insert empty strings with prepared statements, without it trying to instead insert a NULL?
EDIT: It's too big of a project to go through my entire codebase, find everywhere that uses an empty string "" and change it to use NULL instead. What I need to know is why standard MySQL queries treat "" and NULL as two separate things (as I think is correct), but prepared statements converts "" into NULL.
Note that "" and NULL are not the same thing. For Example, SELECT NULL = ""; returns NULL instead of 1 as you'd expect.
Thanks to some of the answers, I realized that the problem may be in the MDB2 API, and not in the PHP or MYSQL commands themselves. Sure enough, I found this in the MDB2 FAQ:
Why do empty strings end up as NULL in the database? Why do I get an NULL
not allowed in NOT NULL text fields
eventhough the default value is ""?
The problem is that for some RDBMS (most noteably Oracle) an empty
string is NULL. Therefore MDB2
provides a portability option to
enforce the same behaviour on all
RDBMS.
Since all portability options are enabled by default you will have
to disable the feature if you dont
want to have this behaviour:
$mdb2->setOption('portability',
MDB2_PORTABILITY_ALL ^
MDB2_PORTABILITY_EMPTY_TO_NULL);
Thanks to everyone who provided thoughtful answers.
This sounds like a problem with the MDB2 API fumbling PHP's duck typing semantics. Because the empty string in PHP is equivalent to NULL, MDB2 is probably mis-treating it as such. The ideal solution would be to find a workaround for it within it's API, but I'm not overly familiar with it.
One thing that you should consider, though, is that an empty string in SQL is not a NULL value. You can insert them into rows declared 'NOT NULL' just fine:
mysql> CREATE TABLE tbl( row CHAR(128) NOT NULL );
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO tbl VALUES( 'not empty' ), ( '' );
Query OK, 2 rows affected (0.02 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT row, row IS NULL FROM tbl;
+-----------+-------------+
| row | row IS NULL |
+-----------+-------------+
| not empty | 0 |
| | 0 |
+-----------+-------------+
2 rows in set (0.00 sec)
mysql> INSERT INTO tbl VALUES( NULL );
ERROR 1048 (23000): Column 'row' cannot be null
If you're unable to find (or implement) a workaround in the MDB2 API, one hackish solution (though slightly better than the one you're currently using) might be to define a user variable for the empty string --
SET #EMPTY_STRING = "";
UPDATE tbl SET row=#EMPTY_STRING;
Finally, if you need to use the empty string in an INSERT statement but find yourself unable to, the default value for string types in MySQL is an empty string. So you could simply omit the column from INSERT statement and it would automatically get set to the empty string (provided the column has a NOT NULL constraint).
I realize this question is pretty much answered and retired, but I found it while looking for answers to a similar situation and I can't resist throwing my hat in the ring.
Without knowing what the NULL/"" column relates to, I can't know how the true significance of an empty string. Does empty string mean something unto itself (like, if I convinced a judge to let me change my name to simply nothing, I would be really irritated if my name showed up on my Driver's License as NULL. My name would be !
However, the empty string (or blank, or the nothingness that is SOMETHING, not simply the lack of anything (like NULL)) could also simply just mean "NOT NULL" or "Nothing, but still not Null". You could even go the other direction and suggest that the absence of the value NULL makes it even LESS something than Null, cuz at least Null has a name you can say aloud!
My point is, that if the empty string is a direct representation of some data (like a name, or what I prefer be inserted between the numbers in my phone number, etc), then your options are either to argue until you're sore for the legitimate use of empty string or to use something that represents an empty string that isn't NULL (Like an ASCII control character or some unicode equivalent, a regex value of some kind, or, even worse, an arbitrary yet totally unused token, like: ◘
If the empty cell really just means NOT NULL, then you could think of some other way of expressing it. One silly and obvious way is the phrase "Not NULL". But I have a hunch that NULL means something like "Not part of this group at all" while the empty string means something like "this guy is cool, he just hasn't gotten his gang tattoos yet". In which case I would come up with a term/name/idea for this situation, like "default" or "rookie" or "Pending".
Now, if by some crazy chance you actually want empty string to represent that which is not even worthy of NULL, again, come up with a more significant symbol for that, such as "-1" or "SUPERNULL" or "UGLIES".
In the Indian Caste System, the lowest Caste are Shudra: Farmers and Laborers. Beneath this caste are the Dalit: "The Untouchables". They are not considered a lower caste, because setting them as the lowest caste would be considered a contamination of the entire system.
So don't call me crazy for thinking empty strings may be WORSE than NULL!
'Til next time.
I found the solution!
MDB2 converts empty strings to NULL because portability option MDB2_PORTABILITY_EMPTY_TO_NULL is on by default (thanks to Oracle which considers empty strings to be null).
Switch this options off when you connect to the database:
$options = array(
'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL
);
$res= & MDB2::connect("mysql://user:password#server/dbase", $options);
While 0 and empty strings are variables NULL is the absence of data. And trust me, it's a lot easier to write a query to
SELECT * from table where mything IS NULL than to try to query for empty strings :/
Doesn't an empty set of quotes, "" do that?
I'm confused. It looks like you're using mysqli OO (from the tags and style), but the syntax is different than the manual on php.net, which says to do this instead:
$query = "INSERT INTO mytable SET somevarchar = ?";
$value = "";
$prepared = $db->prepare($query);
$prepared->bind_param("s", $value);
$result = $prepared->execute();