当前位置: 首页 > news >正文

济源市住房和城乡建设局网站公示广州互帮物流哪家公司做的网站

济源市住房和城乡建设局网站公示,广州互帮物流哪家公司做的网站,ui界面设计英文,dnf制裁做任务网站前言之前我一个搞网络安全的朋友问了我一个的问题#xff0c;为啥用 PreparedStatement 预编译的 SQL 就不会有被 SQL 注入的风险#xff1f;第一时间我联想到的是八股文中关于 Mybatis 的脚本 ${} 和 #{} 的问题#xff0c;不过再想想#xff0c;为啥 ${} 会有 SQL 注入的…前言之前我一个搞网络安全的朋友问了我一个的问题为啥用 PreparedStatement 预编译的 SQL 就不会有被 SQL 注入的风险第一时间我联想到的是八股文中关于 Mybatis 的脚本 ${} 和 #{} 的问题不过再想想为啥 ${} 会有 SQL 注入的风险而 #{} 就没有是因为到 PreparedStatement 做了什么处理吗不知道。然后我又想了想预编译到底是个什么概念预编译或者不预编译的 SQL 对数据库来说有什么区别吗PreparedStatement 又在这个过程中扮演了怎样的角色不知道。好吧我发现我确实对这个问题一无所知看来需要亲自研究一下了。一、数据库预编译当我们说到关于持久层框架的功能必然需要先想想这个功能的源头到底是不是直接通过数据库提供的。实际上和事务一样SQL 预编译的功能也是需要数据库提供底层支持的。1、预编译SQL的用法以 MySQL 为例在 MySQL 中所谓预编译其实是指先提交带占位符的 SQL 模板然后为其指定一个 keyMySQL 先将其编译好然后用户再拿着 key 和占位符对应的参数让 MySQL 去执行用法有点像 python 中的 format 函数。一个标准的预编译 SQL 的用法如下prepare prepare_query fromselect * from s_user where username ? # 提交带有占位符的参数化 SQL也可以理解为 SQL 模板 setname%王五; # 指定一个参数 execute prepare_query usingname; # 指定参数化 SQL 的 key 和参数让 MySQL 自己去拼接执行先通过 prepare 设置一个 SQL 模板然后通过 execute 提交参数MySQL 会自行根据参数替换占位符到最后执行的 SQL 就是select*from s_user where username %王五2、预编译的原理这里有个有意思问题按网上的说法prepare 执行的时候实际上 SQL 已经编译完了所以可以防止注入因为后续不管塞什么参数都不可能在调整语法树了换个角度想这是不是说明如果我们一开始就让 prepare 执行的 SQL 模板的关键字变成占位符是不是应该在这个时候就编译不通过比如可以把查询的表名改成占位符prepare prepare_query fromselect * from ? where username ?# 1064- You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to # use near ? where username ?at line 1实际上也确实不行因为编译时必须确定主表因此在 from 后面加占位符会导致预编译不通过。那么只在查询字段里面套一个嵌套查询呢prepare prepare_query fromselect ? from s_user; SETc(select * from s_user) as q; EXECUTE prepare_query usingc;# 查询结果 # (select*from s_user) as q # (select*from s_user) as q # (select*from s_user) as q # ......查询成功了不过得到的结果的固定的 (select * from s_user) 这个字符串我们检查一下 MySQL 的执行日志看看最终执行的 SQL 变成什么样了Prepare select ? from s_user Query SETc(select * from s_user) as q Query EXECUTE prepare_query usingcExecute select(select * from s_user) as qfrom s_user # 最终执行的SQL显然(select * from s_user) 参数本身被直接转义为了一串普通的字符串我们试图“注入”的 SQL 片段完全不会生效。换而言之对于预编译 SQL 来说我们作为模板的参数化 SQL 已经完成的编译过程这段 SQL 包含几条有效语句查哪张表查哪些字段作为条件的字段有哪些......这些在 prepare 语句执行完后都是固定的此后我们再通过 execute 语句塞进去的任何参数都会进行转义不会再作为 SQL 的一部分。这就是为什么说预编译 SQL 可以防止注入的原因。二、JDBC的预编译现在我们知道了预编译在数据库中是个怎样的功能那么 JDBC 又是如何把这个功能提供给开发者使用的呢1、PreparedStatement从最开始学 JDBC 时我们就知道通过 JDBC 连接数据库一般是这样写的Class.forName(JDBC_DRIVER); // 加载驱动Connectionconnection DriverManager.getConnection(URL, USERNAME, PASSWORD); // 获取连接PreparedStatementpreparedStatement connection.prepareStatement(sql); // 获取sqlStatement preparedStatement.setString(1, foo); // 设置参数ResultSetresultSet preparedStatement.executeQuery(); // 执行SQL这里有一个关键角色 PreparedStatement相比起它的父接口 Statement它最大的变化是多了各种格式为 setXXX 的、用于设置与占位符对应的参数的方法显然它正对应着上文我们提到的预编译 SQL。2、虚假的“预编译”不过事情显然没有这么简单我们依然以 MySQL 为例默认情况下 MySQL 驱动包提供的 PreparedStatement 实现类 ClientPreparedStatement 也能起到防止 SQL 注入的功能但是方式跟我们想的不太一样。假设现有如下代码我们尝试模拟进行一次 SQL 注入Stringsqlselect * from s_user where username ?; PreparedStatementpreparedStatement connection.prepareStatement(sql); preparedStatement.setString(1, 王五 union select * from s_user); ResultSetresultSet preparedStatement.executeQuery();运行上述代码并正常的请求数据库然后我们去数据库执行日志中查看对应的执行的 SQL 如下会发现只有这么一行Query select*from s_user where username 王五 union select * from s_user显然跟我们上文说到的先 prepare 再 execute 流程不同带有占位符的原始 SQL 模板并没有在日志中出现但是代码中的 王五 确实也被转义为了 王五。数据库到底收到了哪些数据那么数据库到底拿到的就是这条 SQL还是原始的 SQL 模板 参数呢为了了解这一点我们打断点跟踪 ClientPreparedStatement.executeQuery 方法一路找到它组装请求数据库的参数的那一行代码MessagesendPacket ((PreparedQuery?) this.query).fillSendPacket();最后我们会进入 AbstractPreparedQuery.fillSendPacket 这个方法这里主要干的事是把我们带占位符的原始 SQL 模板和参数合并为最终要执行的 SQL 并封装到 NativePacketPayload 对象用于在后续发起 TCP 请求时把 SQL 参数转为二进制数据包。为了验证这一点我们先拿到 sendPacket 对象再获取里面的字节数组最后转为字符串可以看到内容就是已经格式化完的 SQLselect*from s_user where username 王五 union select * from s_user现在答案就很明显了转义在 preparedStatement.setString 方法调用的时候完成而 PreparedStatement 在发起请求前就把转义后的参数和 SQL 模板进行了格式化最后发送到 MySQL 的时候就是一条普通的 SQL。鉴于此我们可以说 MySQL 提供的 PreparedStatement 在默认情况下是假的“预编译”它只不过在设置参数的时候帮我们对参数做了一下转义但是最后发送到数据库的依然是普通的 SQL而不是按预编译 SQL 的方式去执行。3、真正的预编译好吧那既然 MySQL 提供了这个预编译的功能那通过 JDBC 肯定也还是有办法用上真正的预编译功能的实际上要做到这点也很简单就是直接在驱动的 url 上配上 useServerPrepStmtstrue 这样就会真正的启用 MySQL 的预编译功能。依然以上文的代码为例Stringsqlselect * from s_user where username ?; PreparedStatementpreparedStatement connection.prepareStatement(sql); preparedStatement.setString(1, 王五 union select * from s_user); ResultSetresultSet preparedStatement.executeQuery();设置了 useServerPrepStmtstrue 后再执行代码去数据库查看执行日志有Executeselect*from s_user where username 王五\unionselect*from s_user Prepare select * from s_user where username ?此时 MySQL 的预编译功能就真正的生效了。我们回到 ClientPreparedStatement.executeQuery 创建 sendPacket 地方看此时通过 ((PreparedQuery?) this.query).fillSendPacket(); 拿到的 Message 对象是 null然后进一步追踪到最后向 MySQL 发送请求的地方 NativeSession.execSQLpublic T extendsResultset T execSQL(Query callingQuery, String query, int maxRows, NativePacketPayload packet, boolean streamResults,ProtocolEntityFactoryT, NativePacketPayload resultSetFactory, ColumnDefinition cachedMetadata, boolean isBatch) {// ... ...try {// 如果 sendPacket 为 null则调用 sendQueryString 方法把原始 sql 和参数序列化为二进制数据包returnpacket null? ((NativeProtocol) this.protocol).sendQueryString(callingQuery, query, this.characterEncoding.getValue(), maxRows, streamResults, cachedMetadata, resultSetFactory)// 否则调用 sendQueryPacket 方法直接发送数据包: ((NativeProtocol) this.protocol).sendQueryPacket(callingQuery, packet, maxRows, streamResults, cachedMetadata, resultSetFactory);}// ... ...}更具体的实现就不看了基本都是关于序列化请求参数的逻辑。三、Myabtis占位符与预编译至此问题真相大白了不过还是顺带扯一下八股文常提到的 Mybatis 占位符 #{} 与 ${} 是如何影响 SQL 注入问题的。当然看完上面的内容其实就已经很好猜到原因了#{} 对应的内容会作为 SQL 参数的一部分通过 PreparedStatement.setXXX 装入请求${} 对应的内容会直接作为 SQL 模板的一部分而不会视为独立的请求参数在 Mybatis 中用于解析占位符的类为 GenericTokenParser 根据它我们很容易在源码中找到占位符的处理方法从而验证我们的猜想其中#{} 占位符在 SqlSourceBuilder.ParameterMappingTokenHandler.handleToken 方法中处理public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return?; }可见 #{} 占位符会被解析为 ? 占位符而对于的数据会被添加到 parameterMappings 用于后续塞到 PreparedStatement。而 ${} 占位符在 PropertyParser.VariableTokenHandler.handleToken 方法中被处理public String handleToken(String content) {if (variables ! null) {Stringkey content;if (enableDefaultValue) {finalintseparatorIndex content.indexOf(defaultValueSeparator);StringdefaultValuenull;if (separatorIndex 0) {key content.substring(0, separatorIndex);defaultValue content.substring(separatorIndex defaultValueSeparator.length());}if (defaultValue ! null) {return variables.getProperty(key, defaultValue);}}if (variables.containsKey(key)) {return variables.getProperty(key);}}return${ content }; }若占位符符合规范则占会根据占位符中的内容去用户给定的参数中取值并且让值直接替换掉原本 SQL 脚本中的 ${} 占位符。这就是“ Mybatis 用 #{} 而不是 ${} 可以防止 SQL 注入的真相。总结回顾一下全文当我们说“预编译”的时候其实这个功能来自于数据库的支持它的原理是先编译带有占位符的 SQL 模板然后在传入参数让数据库自动替换 SQL 中占位符并执行在这个过程中由于预编译好的 SQL 模板本身语法已经定死因此后续所有参数都会被视为不可执行的非 SQL 片段被转义因此能够防止 SQL 注入。当我们通过 JDBC 使用 PreparedStatement 执行预编译 SQL 的时候此处的预编译实际上是假的预编译至少 MySQL 是如此不过其他数据库仍待确认PreparedStatement 只是在设置参数的时候自动做了一层转义最终提交给数据库执行的 SQL 仍然是单条的非预编译 SQL。而当我们通过在驱动 url 上开启 useServerPrepStmts 配置后预编译就会真正的生效驱动包发往数据库的请求就会分成带占位符的 SQL 模板和参数到了数据库再由数据库完成格式化并执行。此外八股文常提到的“Mybatis 的 #{} 相比 ${} 可以防止 SQL 注入”这一点本质上是因为 #{} 占位符会被解析为 SQL 模板中的 ? 占位符而 ${} 占位符会被直接解析为 SQL 模板的一部分导致的。最后脑补一下由于 useServerPrepStmts 不开启时 PreparedStatement 的预编译实际上是假的预编译所以理论上使用 #{} 也并非绝对安全如果有办法绕过 PreparedStatement 的检查那么数据库拿到被注入过的 SQL 直接执行依然有暴毙的风险。
http://www.w-s-a.com/news/358708/

相关文章:

  • 地方门户类网站产品推广惠州市中国建设银行网站
  • 网站建设公司推荐5788移动版wordpress
  • 产品类型 速成网站淘宝怎么建立自己的网站
  • 南京优化网站建设公司的网站怎么建设
  • 做网站开发能挣钱月嫂云商城网站建设
  • 包装网站模板新手入门网站建设
  • 做网站的天津哪个公司做网站
  • 网站建设摊销时间是多久微信官网免费下载安装
  • 网站解析是做a记录吗群晖 wordpress 阿里云
  • 涉县移动网站建设公司常州做网站的公司有哪些
  • 网站批量创建程序中国十大人力资源公司
  • 菏泽网站建设 梧桐树二次开发创造作用
  • 维护网站费用长沙广告设计公司排名
  • 模仿别人网站侵权wordpress 修改链接失效
  • wordpress文章设置受密码保护南宁网站优化公司哪家好
  • 网站开发工程师介绍设计类的网站
  • 嘉兴seo网站推广中山建网站多少钱
  • 高端汽车网站建设帮别人做网站自己为什么会被抓
  • 网站开发实验室建设方案wordpress 主题丢失
  • 珠宝网站建设平台分析报告郑州最新发布
  • 世界杯最新排名泉州seo网站关键词优
  • 广州公司网站提供如何推广新品
  • 网站建设如何描述沈阳网站建设推广平台
  • 用dw制作个介绍家乡网站学生个人简历
  • 建设银行企业网站访问不了wordpress搬到谷歌服务器
  • 网站建设与网站优化销售别墅庭院园林景观设计公司
  • 沈阳红方城网站建设专业的微网站哪家好
  • 医院网站asp东营信息发布平台
  • 网站全站建设开题报告范文南京本地网站
  • 网站漏洞扫描工具wampserver集成环境搭建了一个织梦cms网站