Why does ""=" exploit this MySQL Query? - php

On a MySQL 5.6 database, I build this simple table and insert a row:
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
INSERT INTO users VALUES ('bob', 'pass');
Then I set up a query in PHP like so:
$query = "SELECT * from users where username=\"".$username."\" and password=\"".$password."\"";
When $username and $password are both equal to ""=", the resulting query is SELECT * from users where username="""="" and password="""="". When that's used to query the table set up before, the row in the table is returned.
The question is, how is MySQL evaluating that query such that it considers the query valid and that the WHERE statement is true? Assuming all double-quotes are matched with the nearest untaken adjacent double-quote, I would have expected the query to be interpreted something like this, which looks like it should be considered gibberish:
SELECT * from users where username=""
"="
" and password="
""
=
""
Here's an example of this behavior on a MySQL 5.6 DB: http://sqlfiddle.com/#!9/02e606/2

It's because MySQL allows "" as an alternative for \".
mysql> select '"foo"' = """foo""", '"foo"' = "\"foo\"", 'foo' = """foo""";
+---------------------+---------------------+-------------------+
| '"foo"' = """foo""" | '"foo"' = "\"foo\"" | 'foo' = """foo""" |
+---------------------+---------------------+-------------------+
| 1 | 1 | 0 |
+---------------------+---------------------+-------------------+
1 row in set (0.00 sec)
In your specific case:
SELECT * from users where username="""="" and password="""=""
would be the same as (if I'm parsing this correctly in my head):
SELECT * from users where (username='"="" and passsword="') = ""
A three-way equality test IS syntactically correct, but does not evaluate as expected
mysql> select 'a' = 'a' = 'a';
+-----------------+
| 'a' = 'a' = 'a' |
+-----------------+
| 0 |
+-----------------+
because that parses as (a=a)=a -> true=a -> false
--
comment follow up for #juan:
mysql> select 'a'='a'='a', 'a'='a'='b', 'a'='b'='a', 'b'='a'='a', 'b'='b'='a';
+-------------+-------------+-------------+-------------+-------------+
| 'a'='a'='a' | 'a'='a'='b' | 'a'='b'='a' | 'b'='a'='a' | 'b'='b'='a' |
+-------------+-------------+-------------+-------------+-------------+
| 0 | 0 | 1 | 1 | 0 |
+-------------+-------------+-------------+-------------+-------------+
It's non-intuitive, because
mysql> select 'a'=('a'='b'), ('a'='a')='b', true='b', 'a'=false;
+---------------+---------------+----------+-----------+
| 'a'=('a'='b') | ('a'='a')='b' | true='b' | 'a'=false |
+---------------+---------------+----------+-----------+
| 1 | 0 | 0 | 1 |
+---------------+---------------+----------+-----------+
--- followup to the followup: again, your original query:
SELECT * from users where username="""="" and password="""=""
will run as
SELECT * from users where (username='"="" and passsword="') = ""
SELECT * from users where (false) = ""
SELECT * from users where true
because false ="" in mysql evaluates to TRUE, therefore ALL rows get included, unless you have a user whose username is literally "="" and password=".

I guess you are doing something like this
Sql Demo
SELECT """="" and password="""="" -- this is equal to 0
from users
where (username = "anything") = false;

In MYSQL you can escape single and double quotes like this:
Instead of \" you can do this "" when useing " as field wrapper.
Same with \' becomes '' when using ' as field wrapper
And what in MYSQL also work is:
SELECT * FROM tbl WHERE name = 'ab' 'b c'
real query:
SELECT * FROM tbl WHERE name = 'abb c'
See Online MYSQL Documention

Related

PDO query binding string instead of integer returns results

I have a table with and auto increment id and was just testing a few scenarios when I stumbled across a problem whereby PDO or mysql seems to convert a string to an integer when in an array. Does anyone know why?
If my query is as follows:
$check = $db->prepare("SELECT * FROM tbl_test WHERE id=:id");
$check->execute(array(':id'=>1));
it retrieves 1 record - all fine, but if the query uses a string either by design or mistake as follows:
$check = $db->prepare("SELECT * FROM tbl_test WHERE id=:id");
$check->execute(array(':id'=>'1 OR id > 0'));
it still retrieves a record with id=1.
Surely nothing should be found? I appreciate I should never allow the 2nd scenario to happen but why is PDO / mysql converting the string to an integer and how is it doing it?
This is a MySQL bug/oversight in string to integer conversion. Instead of raising an error when given an incorrect integer literal, it simply issues a warning.
mysql> select '1'+0;
+-------+
| '1'+0 |
+-------+
| 1 |
+-------+
1 row in set (0,00 sec)
mysql> select '1 hello world'+0;
+-------------------+
| '1 hello world'+0 |
+-------------------+
| 1 |
+-------------------+
1 row in set, 1 warning (0,00 sec)
mysql> show warnings;
+---------+------+---------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '1 hello world' |
+---------+------+---------------------------------------------------+
1 row in set (0,00 sec)
For good or bad, that's how MySQL is designed to behave:
mysql> SELECT CASE
-> WHEN 123='123 pink elephants' THEN 'Equal'
-> ELSE 'Different' END
-> AS 'How are they?';
+---------------+
| How are they? |
+---------------+
| Equal |
+---------------+
1 row in set, 1 warning (0.00 sec)
As you can see, though, it triggers a warning:
mysql> SHOW WARNINGS;
+---------+------+--------------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '123 pink elephants' |
+---------+------+--------------------------------------------------------+
1 row in set (0.00 sec)
It's because execute create somthing like: SELECT * FROM tbl_test WHERE id='1 OR id > 0'
$check = $db->prepare("SELECT * FROM tbl_test WHERE id=:id OR id>:id2");
$check->execute(array(':id'=>'1', ':id2' => 0));
or just
$check = $db->prepare("SELECT * FROM tbl_test WHERE id>:id");
$check->execute(array(':id'=>'0'));
With prepared statements and placeholders, the database knows to expect a value that suits the column type. I would expect that it sees your numeric id column and casts the '1 or id > 0' to a number - so you just get the 1.

string comparisons in php (to create a sql query)

I'd like to know how I check whether one or more of the elements (numbers in this case) in a string, eg. '1,2,3,5' are in another ,eg. '3,4,5,6'
3 and 5 are common elements to each string in that example.
In this case it is to create a SQL query based on the string comparisons.
One column value in a db contains one number string, and needs to be compared to another. I need results that match values of each string.
$results = $db->query("SELECT * FROM db
WHERE comparisonString IN (".$idsString.")
")->fetchAll (PDO::FETCH_ASSOC);
But its not quite working... it could be a lateral or syntactic answer.
MORE SPECIFICALLY, I am only getting a result when the FIRST element in the comaprisonString matches the other string elements.
Ideally the solution will look something like this:
$results = $db->query("SELECT * FROM db
WHERE ELEMENTS IN comparisonString IN (".$idsString.")
")->fetchAll (PDO::FETCH_ASSOC);
"ELEMENTS IN" is made up syntax, but that's the sort of thing I'm after
First of all it smells like a bad schema design. Don't store delimited strings of values in your database. Normalize your data by creating a many-to-many table. It will pay off big time enabling you to normally maintain and query your data.
In the meantime if you're using MySQL and assuming that your table looks something like
CREATE TABLE Table1
(
id int not null auto_increment primary key,
column_name varchar(128)
);
and let's say you have sample data
| ID | COLUMN_NAME |
|----|-------------|
| 1 | 3,4,5,6 |
| 2 | 4,6,22 |
| 3 | 7,5,11 |
| 4 | 9,12,1,3 |
| 5 | 8,32,16 |
and you want to select all rows where column_name contains one or more values from a list 1,2,3,5 you can do either
SELECT *
FROM table1
WHERE FIND_IN_SET(1, column_name) > 0
OR FIND_IN_SET(2, column_name) > 0
OR FIND_IN_SET(3, column_name) > 0
OR FIND_IN_SET(5, column_name) > 0
or
SELECT *
FROM table1 t JOIN
(
SELECT id
FROM table1 t JOIN
(
SELECT 1 value UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 5
) s
ON FIND_IN_SET(s.value, t.column_name) > 0
GROUP BY id
) q
ON t.id = q.id
Output (in both cases):
| ID | COLUMN_NAME |
|----|-------------|
| 1 | 3,4,5,6 |
| 3 | 7,5,11 |
| 4 | 9,12,1,3 |
Here is SQLFiddle demo

SQL throws an error when two columns have the same value

I want to insert a record if it does not exist without using unique ID, and I found this answer here: MySQL: Insert record if not exists in table.
In my case I have a table:
+----+---------+---------+---------+---------+-----------+-------------+-----------+
| ID | dRelIns | vRelIns | dRelDel | vRelDel | cRelAktiv | iRelDateiId | iRelKatId |
+----+---------+---------+---------+---------+-----------+-------------+-----------+
| 1 | blabla | blabla | NULL | NULL | J | 3 | 5 |
+----+---------+---------+---------+---------+-----------+-------------+-----------+
| 2 | blabla | blabla | blabla | blabla | N | 3 | 1 |
+----+---------+---------+---------+---------+-----------+-------------+-----------+
| 3 | blabla | blabla | NULL | NULL | J | 3 | 2 |
+----+---------+---------+---------+---------+-----------+-------------+-----------+
I am getting an array ($_POST) with id iRelKatId and another on id iRelDateiId. I check if the id already exists and cRelAktiv = 'J', if not I want to insert a new one. If some entry exists but it's not in the list, I want to set cRelAktiv to 'N'.
My PHP script with the SQL queries:
$list=implode("','", $_POST["kat"]);
$sql="UPDATE tabRel_UDK SET dRelDel=NOW(),
vRelDel=USER(),
cRelAktiv='N'
WHERE iRelDateiId='$_POST[id]' AND cRelAktiv='J' AND iRelKatId NOT IN('$list')";
$result = mysql_query($sql) or die(Error (" . mysql_error() . ").");
foreach ($_POST["kat"] as $value) {
$sql="INSERT INTO tabRel_UDK (dRelIns, vRelIns, cRelAktiv, iRelDateiId, iRelKatId)
SELECT * FROM (SELECT NOW(), USER(), 'J', '$_POST[id]','$value') AS tmp
WHERE NOT EXISTS (
SELECT iRelDateiId,iRelKatId,cRelAktiv FROM tabRel_UDK
WHERE iRelDateiId = '$_POST[id]' AND iRelKatId='$value' AND cRelAktiv='J') LIMIT 1;";
$result = mysql_query($sql) or die("Error (" . mysql_error() . ").");
}
This script works for me, but when both ids have the same value(for example 5), it throws an error Duplicate column name '5' because of SELECT * FROM (SELECT NOW(), USER(), 'J', '$_POST[id]','$value')
Any ideas how to make it works, or should I make 2-3 SQL queries and check the ids manually in PHP?
I suspect $_POST[id] and $value have the same value, and so you appear to be selecting the same column twice. This should be suppressed in code, so you only select it once - or you should be giving each an alias so this does not happen.
I wouldn't recommend returning a resultset with column names starting with a number anyway - in some database systems that would not be permitted, unless it is quoted. Give them string prefixes as aliases, ending in _<number> if you must.
Thus, your subselect string might look like this:
"SELECT * FROM (
SELECT
NOW(),
USER(),
'J',
'{$_POST['id']}' AS val_1,
'{$value}' AS val_2
) WHERE ..."
More importantly, having $_POST[id] in your code will open you up to SQL injection vulnerabilities - always escape data before using it. Even better, switch to MySQL PDO and use parameterisation.
Lastly, the variable should be $_POST['id'] - PHP assumes that you meant a string index, but it will raise a warning if you skip the quotes. Turn on warnings so you can see mistakes like this.

MYSQL query magically grabs numeric value

I have the following query
SELECT * FROM (`user_profiles`) WHERE `user_id` = $user_id LIMIT 1
$user_id is a URI segment. For instance $user_id = 64 would produce
SELECT * FROM (`user_profiles`) WHERE `user_id` = '64' LIMIT 1
If I add alphabetical characters to the user id, e.g. http://www.mysite.com/profile/64kjdsg
I get:
SELECT * FROM (`user_profiles`) WHERE `user_id` = '64kjdsg' LIMIT 1
which still returns the correct data although there is no user id equal to 64kjdsg. The user id column in my table is int(11). The query seems to automatically grab the numeric value from 64kjdsg and match that in the db table. Is this a MYSQL function I'm not aware of?
How is this happening? I'm querying using the Codeigniter framework if that makes ant difference.
UPDATE: found a similar question MySQL integer comparison ignores trailing alpha characters
As you are comparing to a numeric column, MySQL casts your string to a number (so it removes everything from the occurance of the first non-number character). It's its default behavior:
mysql> select '23andthensome' + 4;
+---------------------+
| '23andthensome' + 4 |
+---------------------+
| 27 |
+---------------------+
1 row in set, 1 warning (0.02 sec)
mysql> show warnings;
+---------+------+---------------------------------------------------+
| Level | Code | Message |
+---------+------+---------------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: '23andthensome' |
+---------+------+---------------------------------------------------+
1 row in set (0.02 sec
So, make more proper queries, check beforehand wether something is a number (filter_var(FILTER_VALIDATE_INT,$id);), only use it when it is, and then: don't send it as a string to MySQL: if you want to compare numbers, send the number, which should not be quoted.
Alternatively, you can let MySQL do the work, but it seems a waste:
mysql> select 23 = '23andthensome';
+----------------------+
| 23 = '23andthensome' |
+----------------------+
| 1 |
+----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select cast(23 as CHAR) = '23andthensome';
+-------------------------------------+
| cast(23 as CHAR) = '23andthensome' |
+-------------------------------------+
| 0 |
+-------------------------------------+
1 row in set (0.02 sec)
Check in your script is urlsegment integer. You can use ctype_digit to do it. If isn't, don't touch your db. Say "No such user"
Check this out: http://ideone.com/khpEv, it is called type juggling. If you '64kjdsg' string will be converted to integer (64) because user_id is INT else it will produce syntax error.
PHP example:
<?php
echo (int) '64kjdsg'; // 64
?>
this is security hole
however, if
$user_id = 5;
result is
SELECT * FROM (`user_profiles`) WHERE `user_id` = 5 LIMIT 1
not
SELECT * FROM (`user_profiles`) WHERE `user_id` = `5` LIMIT 1
try to use intval()
$user_id = intval($user_id);

MySQL escaped strings problem

In some PHP code, I have an mysql_real_escape_string()'d term, like foo\'s. I search that in my database (where it is also stored as foo\'s) like so:
mysql_query("SELECT * FROM coupons WHERE retailerName LIKE '%" . $searchTerm . "%'");
The query should look like this without variables:
SELECT * FROM coupons WHERE retailerName LIKE '%foo\'s%'
If I search f, fo, or foo, then the search works. But if I search foo's then the search doesn't work (keep in mind that the actual query takes an escaped string, so everything should match up).
Perhaps the interface from you program to mysql (JDBC or similar) is adding extra escape characters to your string. If the same mechanism is not what put the data into the database, try doing an insert to see how the data gets stored.
Mysql can handle the query through it's own interface
mysql> describe test_table;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| col1 | varchar(20) | YES | | NULL | |
| col2 | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
mysql> insert into test_table (col1, col2) values ('col1value', 'foo\'s');
Query OK, 1 row affected (0.04 sec)
mysql> select * from test_table where col2 like '%foo\'s%';
+-----------+-------+
| col1 | col2 |
+-----------+-------+
| col1value | foo's |
+-----------+-------+
1 row in set (0.00 sec)
If it's stored as foo\'s in DB, then there are 2 options - either you are double-escaping (i.e., using mysql_real_escape_string() twice), or you are escaping values that "something" (e.g., magic quotes) has already slashed.
Check if you have magic_quotes_gpc enabled.
Here's PHP5.3 code for stripping "magic quotes" automatically (can be used in config file). For older PHP, callback function would look differently, but you should get the idea from this.
// remove slashes, if they are being automatically added
if ( get_magic_quotes_gpc () ) {
$stripslashes = function($value) use(&$stripslashes) {
if ( is_array($value) ) {
return array_map($stripslashes, $value);
}
return stripslashes($value);
};
$_GET = array_map($stripslashes, $_GET);
$_POST = array_map($stripslashes, $_POST);
$_COOKIE = array_map($stripslashes, $_COOKIE);
unset($stripslashes);
}

Categories