12.2 Type Conversion in Expression Evalutation

当操作符用来操作两个不同类型的操作数时,会发生类型转换使操作数相兼容。一些转换是隐式发生的。例如,MySQL自动的将数值转化为字符如果必要,反之亦然

mysql> select concat(2, 'test');
+-------------------+
| concat(2, 'test') |
+-------------------+
| 2test             |
+-------------------+
1 row in set (0.00 sec)

也可以将数字转换为字符显示的使用CAST()函数。转换隐匿的发生当使用CONCAT()函数因为他期待string参数

mysql> SELECT 38.8, cast(38.8 as char);
+------+--------------------+
| 38.8 | cast(38.8 as char) |
+------+--------------------+
| 38.8 | 38.8               |
+------+--------------------+

mysql> select 38.8, concat(38.8);
+------+--------------+
| 38.8 | concat(38.8) |
+------+--------------+
| 38.8 | 38.8         |
+------+--------------+
1 row in set (0.00 sec)

参阅此节关于隐式将数值转换为字符的转换的字符集和modified rules that apply to CREATE TABLE ... SELECT 语句。

下面的规则描述了比较操作的转换发生的规则:

  • 如果一个或两个参数都是NULL,比较返回的结果是NULL,除了NULL自身的<=>相等性比较操作。对于NULL<=>NULL,结果是true。不需要转换。

  • 如果两个参数都是字符,则作为字符串比较。

  • 如果两个参数都是整数,则他们作为整数比较。

  • 十六进制的值将被看成二进制字符如果没有与数值比较
  • 如果一个参数是TIMESTAMP或DATATIME列,其他的参数是常量,常量被转换为timestamp在比较执行之前。这比ODBC作的更友好。注意这在IN()的参数中是不会这样做的!为了安全,永远使用完全的datetime, date, 或time字符串在做比较时。例如,在用BETWEEN的date或time参数时为了达到更好的效果,使用CAST()显示的将值转换为渴望的值。
  • 一个single-row从一个表或多个表返回的不被认为是常量。例如,如果一个子查询返回一个整数来合DATETIME值作比较,比较会把他们都作为整数比较。整数不会被转换为时间值。要将操作数作为DATETIME值比较,使用CAST()来显示的将子查询的值转换为DATETIME
  • 如果一个参数是decimal值,比较取决于另一个参数。如果另一个参数是一个decimal或integer。如果另一个参数是浮点类型,则会作为浮点类型比较
  • 剩下的其他情况,参数都作为浮点类型比较

总结:

SQL Schema:

DROP TABLE IF EXISTS `test`;

CREATE TABLE IF NOT EXISTS `test`(
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(30),
  password VARCHAR(30)
)ENGINE InnoDB CHARSET UTF8;

INSERT INTO `test` (`name`, `password`) VALUES ("test1", "password1"), ("test2", "password2");

安全问题:假如password类型为字符串,查询条件为int 0 则会匹配上。

mysql> SELECT * FROM `test`;
+----+-------+-----------+
| id | name  | password  |
+----+-------+-----------+
|  1 | test1 | password1 |
|  2 | test2 | password2 |
+----+-------+-----------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM `test` WHERE `name` = 'test1' AND `password` = 0;
+----+-------+-----------+
| id | name  | password  |
+----+-------+-----------+
|  1 | test1 | password1 |
+----+-------+-----------+
1 row in set, 1 warning (0.00 sec)

mysql> show warnings;
+---------+------+-----------------------------------------------+
| Level   | Code | Message                                       |
+---------+------+-----------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'password1' |
+---------+------+-----------------------------------------------+
1 row in set (0.00 sec)

相信上面的例子,一些机灵的同学可以发现其实上面的例子也可以做sql注入。

假设网站的登录那块做的比较挫,使用下面的方式:

SELECT * FROM users WHERE username= '$_POST["username"]' AND password = '$_POST["password"]'

如果username输入的是a' OR 1 = '1,那么password随便输入,这样就生成了下面的查询:

SELET * FROM users WHERE username = 'a' OR 1 = '1'AND password = 'anyvalue'

就有可能登录系统。其实如果攻击者看过了这篇文章,那么就可以利用隐a工转化来进行登录了。如下:

INSERT INTO `test` (`name`, `password`) VALUES ('aaa', 'aaaa'), ('55aaa', '55aaaa');

mysql> SELECT * FROM `test`;
+----+-------+-----------+
| id | name  | password  |
+----+-------+-----------+
|  1 | test1 | password1 |
|  2 | test2 | password2 |
|  3 | aaa   | aaaa      |
|  4 | 55aaa | 55aaaa    |
+----+-------+-----------+
4 rows in set (0.00 sec)

mysql> SELECT * FROM `test` WHERE name = 'a' + '55';
+----+-------+----------+
| id | name  | password |
+----+-------+----------+
|  4 | 55aaa | 55aaaa   |
+----+-------+----------+
1 row in set, 5 warnings (0.00 sec)

之所以出现上述的原因是因为:

mysql> SELECT '55aaaa' = 55;
+---------------+
| '55aaaa' = 55 |
+---------------+
|             1 |
+---------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT 'a' + '55';
+------------+
| 'a' + '55' |
+------------+
|         55 |
+------------+
1 row in set, 1 warning (0.00 sec)

下面通过一些例子来复习一下上面的转换规则:

mysql> SELECT 1 + 1;
+-------+
| 1 + 1 |
+-------+
|     2 |
+-------+
1 row in set (0.00 sec)

mysql> SELECT 'aa' + 1;
+----------+
| 'aa' + 1 |
+----------+
|        1 |
+----------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+----------------------------------------+
| Level   | Code | Message                                |
+---------+------+----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'aa' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)

把字符中 "aa"和1进行求和,得到1,因为"aa"和数字1的类型不同,MySQL官方文档告诉我们:

When an operator is used with operands of different types, type conversion occurs to make the operands compatible.

查看warnings可以看到隐式转换把字符串转换为了double类型。但是因为字符串是非数字型的,所以就会被转换为0,因此最终计算的是0 + 1 = 1

上面的例子是类型不同,所以出现了隐式转化,那么如果我们使用相同类型的值进行运算呢?

mysql> SELECT 'a' + 'b';
+-----------+
| 'a' + 'b' |
+-----------+
|         0 |
+-----------+
1 row in set, 2 warnings (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level   | Code | Message                               |
+---------+------+---------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
+---------+------+---------------------------------------+
2 rows in set (0.00 sec)

是不是有点郁闷呢? 之所以出现这种情况,是在为+为算术操作符(arithmetic operator)这样就可以解释为什么a和b都转换为double了。因为转换之后其实就是: 0 + 0 = 0

在看一个例子:

mysql> SELECT 'a' + 'b' = 'c';
+-----------------+
| 'a' + 'b' = 'c' |
+-----------------+
|               1 |
+-----------------+
1 row in set, 3 warnings (0.00 sec)

mysql> SHOW WARNINGS;
+---------+------+---------------------------------------+
| Level   | Code | Message                               |
+---------+------+---------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'a' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'b' |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'c' |
+---------+------+---------------------------------------+
3 rows in set (0.00 sec)

现在就看也很好的理解上面的例子了吧。a+b=c结果为1,1在MySQL中可以理解为TRUE, 因为'a' + 'b'的结果为0,c也会隐式转换为0,因此比较其实是:0=0也就是true,也就是1.

第二个需要注意点就是防止多查询或者删除数据

mysql> INSERT INTO `test` (`name`, `password`) VALUES  ('1212', 'aaa'),('1212a', 'aaa');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM `test`;
+----+-------+-----------+
| id | name  | password  |
+----+-------+-----------+
|  1 | test1 | password1 |
|  2 | test2 | password2 |
|  3 | aaa   | aaaa      |
|  4 | 55aaa | 55aaaa    |
|  5 | 1212  | aaa       |
|  6 | 1212a | aaa       |
+----+-------+-----------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM `test` WHERE name = 1212;
+----+-------+----------+
| id | name  | password |
+----+-------+----------+
|  5 | 1212  | aaa      |
|  6 | 1212a | aaa      |
+----+-------+----------+
2 rows in set, 5 warnings (0.00 sec)

mysql> SELECT * FROM `test` WHERE `name`='1212';
+----+------+----------+
| id | name | password |
+----+------+----------+
|  5 | 1212 | aaa      |
+----+------+----------+
1 row in set (0.00 sec)

上面的例子本意是查询id为5的那一条记录,结果把id为6的那一条也查询来了。 我想说明什么情况呢?有时候我们的数据库表中的一些列是varchar类型,但是存储的值为'1123'这种的纯数字的字符串值,一些同学写sql的时候又不习惯加引号。这样当进行SELECT, UPDATE 或者 DELETE的时候就可能会多操作一些数据。所以应该加引号的地方别忘记了。

关于字符串转数字的一些说明

mysql> SELECT 'a' = 0;
+---------+
| 'a' = 0 |
+---------+
|       1 |
+---------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT '1a' = 1;
+----------+
| '1a' = 1 |
+----------+
|        1 |
+----------+
1 row in set, 1 warning (0.01 sec)

mysql> SELECT '1a1b' = 1;
+------------+
| '1a1b' = 1 |
+------------+
|          1 |
+------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT 'a1a1b' = 0;
+-------------+
| 'a1a1b' = 0 |
+-------------+
|           1 |
+-------------+
1 row in set, 1 warning (0.00 sec)

从上面的例子可以看出,当把字符串转换数的时候,其实是从左边处理 。

  • 如果字符串的第一个字符就是非数字的字符,那么转换数字就是0
  • 如果字符串中都是数字,那么转换为数字就是整个字符串对应的数字
  • 如果字符串中存在非数字,那么转换为的数字就是开头的那些数字对应的值

results matching ""

    No results matching ""