开发者社区> 问答> 正文

postgres 子查询中写递归导致SQL注入异常

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:

Error querying database. Cause: 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

The error may exist in cn/net/data/mapper/ExternalMapper.xml

The error may involve cn.net.data.mapper.ExternalMapper.queryDeptAndPeopleNumByDeptId

The error occurred while executing a query

SQL: 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

Cause: 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

; 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

展开
收起
山海行 2023-07-05 19:22:03 110 0
4 条回答
写回答
取消 提交回答
  • 北京阿里云ACE会长

    这是因为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

    2023-07-30 18:35:54
    赞同 展开评论 打赏
  • 尝试使用statement标记来告诉Druid忽略对该语句的SQL注入检查。将递归查询放在子查询内似乎触发了Druid的SQL注入检测,导致异常被抛出。

    2023-07-11 09:22:17
    赞同 展开评论 打赏
  • 问题已修复,请用新版本

    https://github.com/alibaba/druid/releases/tag/1.2.5

    原回答者GitHub用户wenshao

    2023-07-06 10:52:23
    赞同 展开评论 打赏
  • 使用子查询和递归来构建复杂的SQL查询是很常见和有用的。然而,如果不小心编写这样的查询,可能会导致SQL注入漏洞。

    为了防止SQL注入异常,以下是一些建议:

    1. 使用参数化查询:在构建动态SQL语句时,应该使用参数化查询来代替直接将用户输入拼接到查询中。参数化查询能够对输入进行正确转义,从而预防SQL注入攻击。

    2. 验证和限制输入:在接收用户输入之前,进行验证和过滤。确保输入符合预期的格式和类型,并将其限制在允许的范围内。

    3. 不信任外部输入:不要信任外部输入数据的安全性。即使看起来是可信的来源,也应该对其进行适当的验证和转义。

    4. 严格控制权限:确保数据库用户只拥有执行必要操作的最低权限。这样即使存在SQL注入攻击,攻击者也无法执行敏感操作。

    5. 定期更新和维护数据库:保持数据库引擎和相关软件的最新版本,以获取最新的安全修复和增强功能。

    请注意,这些都是预防SQL注入的基本原则,但并不能确保完全防止所有情况。如果你的代码仍然存在SQL注入风险,建议在安全专家的指导下进行审查和修复。

    2023-07-05 19:27:40
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
SQL Server在电子商务中的应用与实践 立即下载
GeoMesa on Spark SQL 立即下载
原生SQL on Hadoop引擎- Apache HAWQ 2.x最新技术解密malili 立即下载