druid版本:1.1.23 postgres版本:10.1 nvicat版本:12.0.18
最近工作中涉及到大量对树形结构的操作,于是用到了递归,经测试发现,只要将递归查询放在子查询内就会导致违反sql注入异常。
语句在Navicat中正常执行
以下两种方式都会导致违反sql注入
-- 第一种 select u.id, ( WITH RECURSIVE users AS ( select id from t_user limit 1 ) select id from users ) from t_user u limit 1;
-- 第二种 select 1 from t_user u join ( WITH RECURSIVE users AS ( select id from t_user limit 1 ) select id from users ) t on u.id = t.id;
错误信息:
org.springframework.jdbc.UncategorizedSQLException:
WITH RECURSIVE users AS (
SELECT department code, COUNT(id) num
FROM t_user
WHERE visiable = 1
GROUP BY department
) select 1 from users
limit 1
) from t_user u
limit 10
WITH RECURSIVE users AS (
SELECT department code, COUNT(id) num
FROM t_user
WHERE visiable = 1
GROUP BY department
) select 1 from users
limit 1
) from t_user u
limit 10
; uncategorized SQLException for SQL []; SQL state [null]; error code [0]; sql injection violation, syntax error: syntax error, expect RPAREN, actual RECURSIVE pos 32, line 2, column 9, token RECURSIVE : select u.id, ( WITH RECURSIVE users AS ( SELECT department code, COUNT(id) num FROM t_user WHERE visiable = 1 GROUP BY department ) select 1 from users limit 1
) from t_user u
limit 10; nested exception is java.sql.SQLException: sql injection violation, syntax error: syntax error, expect RPAREN, actual RECURSIVE pos 32, line 2, column 9, token RECURSIVE : select u.id, (
WITH RECURSIVE users AS (
SELECT department code, COUNT(id) num
FROM t_user
WHERE visiable = 1
GROUP BY department
) select 1 from users
limit 1
) from t_user u
limit 10
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
at com.sun.proxy.$Proxy103.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy130.queryDeptAndPeopleNumByDeptId(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy131.queryDeptAndPeopleNumByDeptId(Unknown Source)
at cn.net.data.service.imple.ExternalServiceImpl.queryDeptAndPeopleNumByDeptId(ExternalServiceImpl.java:97)
at cn.net.data.controller.ExternalController.queryDeptAndPeopleNumByDeptId(ExternalController.java:288)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at cn.net.data.filter.SessionFilter.doFilterInternal(SessionFilter.java:79)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at cn.net.data.filter.CorsFilter.doFilter(CorsFilter.java:19)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:208)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.sql.SQLException: sql injection violation, syntax error: syntax error, expect RPAREN, actual RECURSIVE pos 32, line 2, column 9, token RECURSIVE : select u.id, ( WITH RECURSIVE users AS ( SELECT department code, COUNT(id) num FROM t_user WHERE visiable = 1 GROUP BY department ) select 1 from users limit 1
) from t_user u
limit 10
at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:805)
at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:258)
at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:568)
at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:341)
at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:350)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.logging.jdbc.ConnectionLogger.invoke(ConnectionLogger.java:55)
at com.sun.proxy.$Proxy183.prepareStatement(Unknown Source)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:87)
at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:88)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)
at cn.net.plugin.PagePlugin.intercept(PagePlugin.java:119)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
at com.sun.proxy.$Proxy182.prepare(Unknown Source)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:83)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
... 89 more
Caused by: com.alibaba.druid.sql.parser.ParserException: syntax error, expect RPAREN, actual RECURSIVE pos 32, line 2, column 9, token RECURSIVE at com.alibaba.druid.sql.parser.SQLExprParser.accept(SQLExprParser.java:2856) at com.alibaba.druid.sql.parser.SQLExprParser.primary(SQLExprParser.java:369) at com.alibaba.druid.sql.dialect.postgresql.parser.PGExprParser.primary(PGExprParser.java:154) at com.alibaba.druid.sql.parser.SQLExprParser.expr(SQLExprParser.java:157) at com.alibaba.druid.sql.parser.SQLExprParser.parseSelectItem(SQLExprParser.java:3481) at com.alibaba.druid.sql.parser.SQLSelectParser.parseSelectList(SQLSelectParser.java:937) at com.alibaba.druid.sql.dialect.postgresql.parser.PGSelectParser.query(PGSelectParser.java:103) at com.alibaba.druid.sql.parser.SQLSelectParser.query(SQLSelectParser.java:362) at com.alibaba.druid.sql.parser.SQLSelectParser.select(SQLSelectParser.java:61) at com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser.parseSelect(PGSQLStatementParser.java:348) at com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser.parseSelect(PGSQLStatementParser.java:57) at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:248) at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:182) at com.alibaba.druid.wall.WallProvider.checkInternal(WallProvider.java:624) at com.alibaba.druid.wall.WallProvider.check(WallProvider.java:578) at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:792) ... 123 more
原提问者GitHub用户chaizp
这是因为Druid使用的JsqlParser尚不支持递归子查询。
Druid在解析该SQL时,JsqlParser无法识别WITH RECURSIVE语句,从而产生SQL注入异常。这个问题在Druid 1.1.9及以前的版本中存在。
解决方案有两种:
升级到Druid 1.1.10或更高版本
1.1.10及之后的版本修改了JsqlParser,可以支持Postgres中的递归子查询。
自定义SQL解析器
针对Postgres的特殊SQL(如递归子查询),使用Postgres驱动自定义SQL解析器。
总的来说,Postgres递归子查询引起Druid报SQL注入异常主要是:
JsqlParser不支持WITH RECURSIVE语法。
两种解决方案:
升级Druid版本以支持循环子查询
自定义SQL解析器专门处理Postgres特殊SQL
尝试使用statement标记来告诉Druid忽略对该语句的SQL注入检查。将递归查询放在子查询内似乎触发了Druid的SQL注入检测,导致异常被抛出。
问题已修复,请用新版本
https://github.com/alibaba/druid/releases/tag/1.2.5
原回答者GitHub用户wenshao
使用子查询和递归来构建复杂的SQL查询是很常见和有用的。然而,如果不小心编写这样的查询,可能会导致SQL注入漏洞。
为了防止SQL注入异常,以下是一些建议:
使用参数化查询:在构建动态SQL语句时,应该使用参数化查询来代替直接将用户输入拼接到查询中。参数化查询能够对输入进行正确转义,从而预防SQL注入攻击。
验证和限制输入:在接收用户输入之前,进行验证和过滤。确保输入符合预期的格式和类型,并将其限制在允许的范围内。
不信任外部输入:不要信任外部输入数据的安全性。即使看起来是可信的来源,也应该对其进行适当的验证和转义。
严格控制权限:确保数据库用户只拥有执行必要操作的最低权限。这样即使存在SQL注入攻击,攻击者也无法执行敏感操作。
定期更新和维护数据库:保持数据库引擎和相关软件的最新版本,以获取最新的安全修复和增强功能。
请注意,这些都是预防SQL注入的基本原则,但并不能确保完全防止所有情况。如果你的代码仍然存在SQL注入风险,建议在安全专家的指导下进行审查和修复。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。