thymeleaf / thymeleaf-spring

Thymeleaf integration module for Spring
http://www.thymeleaf.org
Apache License 2.0
435 stars 157 forks source link

Markup Selectors function could cause denial of service #296

Closed wbxlll closed 2 years ago

wbxlll commented 2 years ago

Hi,

Thymeleaf & thymeleaf-spring5 : 3.0.15.RELEASE Spring boot version : 2.5.13 Java : 8

I found that once a "Markup Selectors" can be injected with odd number of single quote, a denial of service would be cause.

Example:

Controller

@Controller
public class InjectionController {

    @GetMapping("/tryInjection")
    public String tryInjection(String fragment) {
        return  "index::" + fragment;
    }

}

Once I visit this path with parameter that have odd number of single quote, for example:

http://localhost:8080/tryInjection?fragment=a'b'c'

a thread will enter an endless loop. After several times, the app will denial all service.

The thread dump was like this:

"http-nio-9080-exec-1@4851" daemon prio=5 tid=0x13 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
      at org.attoparser.select.MarkupSelectorItems.parseAttributeCondition(MarkupSelectorItems.java:549)
      at org.attoparser.select.MarkupSelectorItems.parseAttributeCondition(MarkupSelectorItems.java:579)
      at org.attoparser.select.MarkupSelectorItems.parseAttributeCondition(MarkupSelectorItems.java:579)
      at org.attoparser.select.MarkupSelectorItems.parseSelector(MarkupSelectorItems.java:371)
      at org.attoparser.select.MarkupSelectorItems.parseSelector(MarkupSelectorItems.java:453)
      at org.attoparser.select.MarkupSelectorItems.parseSelector(MarkupSelectorItems.java:111)
      at org.attoparser.select.MarkupSelectorItems.forSelector(MarkupSelectorItems.java:76)
      at org.attoparser.select.BlockSelectorMarkupHandler.setParseConfiguration(BlockSelectorMarkupHandler.java:400)
      at org.attoparser.HtmlMarkupHandler.setParseConfiguration(HtmlMarkupHandler.java:92)
      at org.attoparser.AbstractChainedMarkupHandler.setParseConfiguration(AbstractChainedMarkupHandler.java:89)
      at org.attoparser.MarkupEventProcessorHandler.setParseConfiguration(MarkupEventProcessorHandler.java:161)
      at org.attoparser.MarkupParser.parse(MarkupParser.java:248)
      at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
      at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100)
      at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:649)
      at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
      at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072)
      at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:366)
      at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:190)
      at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1401)
      at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1145)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
      at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
      at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
      at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
      at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
      at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
      at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
      - locked <0x159e> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
      at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
      at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.lang.Thread.run(Thread.java:750)
danielfernandez commented 2 years ago

Thanks. This was caused by an issue with unbalanced parentheses in selectors in the AttoParser library, now fixed in version 2.0.6.

Also, as for this code:

@Controller
public class InjectionController {

    @GetMapping("/tryInjection")
    public String tryInjection(String fragment) {
        return  "index::" + fragment;
    }

}

Please note that it would be never recommended that an application uses direct user input (a request parameter in this case) as a template or fragment selector without previous validation. This kind of user code could have undesirable security implications like the ones seen in this case, allowing an issue in the underlying software to be triggered by external users.