MySQL的预编译语句的转义探究
2017-04-24 00:12
281 查看
0x00 什么是预编译语句
SQL语句,说到底也是一种类似于编程语言的东西,目的是让程序员更友好的操纵数据库,既然这样,它肯定也存在一个SQL语句的编译过程,将其转化为数据库所能执行的命令。顾名思义,预编译语句就是将需要执行SQL语句预先进行编译后缓存起来,下次使用的时候直接越过了编译这步,理论上是会比普通的SQL查询拥有更好的性能。我这里的重点不是关注它的性能,而是大家都说,预编译可以防止SQL注入,事实是怎样的呢?我们平时说的参数化查询,又是怎么一回事?0x01 搭建环境
我们需要了解应用程序和数据库交互时,一些支持预编译的模块(如PreparedStatement)进行了怎样的处理,数据库收到的究竟是怎样的数据。我们通过两种方式来观察传递结果:开启MySQL的日志记录,用wireshark抓包。首先,打开MySQL的日志记录:
这里遇到了一点小坑,寻找
my.ini配置文件的时候,以为位置是在默认安装根目录下(C:\Program Files\MySQL\MySQL Server 5.7),结果发现并没有。查看文档,说依照以下顺序搜索配置文件:
%PROGRAMDATA%\MySQL\MySQL Server 5.7\my.ini %PROGRAMDATA%\MySQL\MySQL Server 5.7\my.cnf %WINDIR%\my.ini %WINDIR%\my.cnf C:\my.ini C:\my.cnf ......
我在cmd里 echo %PROGRAMDATA%,结果输出的是:
接下来找到
my.ini,在
[mysqld]中修改以下两个参数:
... general-log=1 # 默认是0,1代表开启 general_log_file="E:/mysql/logs/mysql_general.log" ...
接下来我们新建一个简单的Java程序,来测试一下:
try { Class.forName(name);//指定连接类型 conn = DriverManager.getConnection(url, user, password);//获取连接 pst = conn.prepareStatement("SELECT * FROM users WHERE `name`=?");//准备执行语句 pst.setString(1,"9ian1i"); rs = pst.executeQuery(); while (rs.next()){ String name = rs.getString("name"); System.out.println(name); }
其中,users表中的数据如下:
0x02 预编译的开启
如果按照上面的代码执行,通过wireshark抓包,你会发现,其实根本没有所谓的“预先编译”这一过程:Java程序直接将完整的SQL语句传给了数据库,问题出在哪儿?网上有些人此时就说,MySQL不支持预编译。其实是默认不开启,你需要显式的声明,即在连接数据库的时候,加上:
useServerPrepStmts=true,接下来我们再试一次:
预编译模式打开了!
我们同时看一下数据库的日志记录:
同样验证了我们的想法。
0x03 参数化查询
还有一种说法,说是参数化查询的时候MySQL的连接驱动进行了转义处理,所谓的防止SQL注入其实也是在代码层完成的。记得在写SQL语句的时候,我们用占位符(?)替换了用户可控的参数,这里其实就是叫参数化查询,那当我们输入带有定界符(’)的字符串时,它会怎么处理?
我们首先直接看一下MySQL连接驱动的源代码:
public void setString(int parameterIndex, String x) throws SQLException { synchronized(this.checkClosed().getConnectionMutex()) { if(x == null) { this.setNull(parameterIndex, 1); } else { this.checkClosed(); ...... ...... String parameterAsString = x; boolean needsQuoted = true; if(this.isLoadDataQuery || this.isEscapeNeededForString(x, stringLength)) { needsQuoted = false; buf = new StringBuilder((int)((double)x.length() * 1.1D)); buf.append('\''); for(int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch(c) { case '\u0000': buf.append('\\'); buf.append('0'); break; case '\n': ...... ...... case '"': if(this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '¥': case '₩': ...... ......
看似在做转义,但认真看代码你就会发现,竟然没有包括单引号!!!
为了验证我们的想法,依旧通过抓包和日志记录来观察,将查询参数改为
9ian'1i:
再看看日志记录:
事实证明,参数化查询的转义工作是在MySQL数据库那一端完成的,而不是在代码层。
最开始我其实也认同在代码层这种看法,特别是这篇文章竟然还有好几个赞:占位符,SQL注入? ,所以说还是不能光看别人的东西,要自己动手去实践才能明白。
0x04 PHP和Python
目前主流的后端语言除了Java还有PHP和Python,我们都一并测试一下。首先是PHP:
抓包:
执行日志:
发现PHP也是这样。
然后是Python:
Python这里有点麻烦,首先我们尝试利用%占位符:
#!/usr/bin/env python # coding=UTF-8 import MySQLdb con = MySQLdb.connect(host='127.0.0.1', db='test', user='root', passwd='123456') cur = con.cursor() cur.execute("SELECT * FROM users WHERE `name`=%s", "9i'an") print cur.fetchone()
发现只是单纯的在代码层进行了转义。
尝试用?占位符,但是报错了。 我在Python核心编程中也没有查到预编译相关的东西。
怎么办?有个第三方库,叫做 oursql (要翻墙),网上说支持原生预编译指令,我们首先安装它:
安装方法大家应该看明白了,左边是ubuntu,右边是windows。
我们写个简单的查询:
#!/usr/bin/python # -*- coding: utf-8 -*- import oursql con = oursql.connect(host='127.0.0.1', user='root', passwd='root',db='test', port=3306) cur = con.cursor() cur.execute('SELECT * FROM users WHERE `name`=?', ("9ia'n1i",)) print cur.fetchone()
看一下日志:
的确支持预编译。
0x05 小结
MySQL(我用的5.7)是支持预编译的,且转义是在数据库中进行的,并不是在代码层完成。实践是检验真理的唯一标准。
主流语言都支持预编译,Python的比较麻烦,需要使用第三方模块。PHP还有个PDO,预编译其实差不多,就没有再贴。
参考文档就不贴了,太杂了,参差不齐。
推荐Python第三方库 oursql : https://pythonhosted.org/oursql/
中间的坑挺多,MySQL的常规日志在5.7中名字叫
general_log,并不是
log,linux下配置文件也不在
/etc/my.cnf,而是在
/etc/mysql/my.cnf,且里面啥都没有,需要自己添加
[mysqld]标签。
如有错误还望大家能不吝指出。
相关文章推荐
- (转载)MySQL中执行sql语句反斜杠需要进行转义否则会被吃掉
- mysql语句的转义字符
- php中转义mysql语句的实现代码
- php中转义mysql语句的实现代码
- mysql中的预编译语句
- Mysql 语句中对关键字进行转义的方式
- 探究mysql预编译
- PHP中对mysql预编译查询语句的一个封装
- mysql和postgresql转义字符探究
- mysql的sql语句中如何转义系统关键字
- mysql执行语句中的转义字符`
- 测试mysql的sql语句预编译效果
- 预编译语句(Prepared Statements)介绍,以MySQL为例
- MySQL中无GROUP BY情况下直接使用HAVING语句的问题探究
- MySQL语句中的转义字符----引号
- 关于mysql语句的转义
- mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符,可使用本函数来预防数据库攻击。
- 预编译语句(Prepared Statements)介绍,以MySQL为例
- MySQL中执行sql语句反斜杠需要进行转义
- mysql需要注意的几个sql语句