Skip to content

Commit 273fced

Browse files
CopilothuayanYu
andauthored
Fix SpEL injection (potential RCE) in DsSpelExpressionProcessor (#767)
* Initial plan * Fix SpEL injection vulnerability in DsSpelExpressionProcessor Agent-Logs-Url: https://github.com/baomidou/dynamic-datasource/sessions/db0ccd4f-03f6-4326-8c42-5d5f0a35961a Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com> * Add allowedSpelTypeAccess toggle for SpEL type-reference restriction Agent-Logs-Url: https://github.com/baomidou/dynamic-datasource/sessions/c4f1a2e2-7b0c-4023-b77b-c0a660bd855f Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: huayanYu <16700837+huayanYu@users.noreply.github.com>
1 parent da3df59 commit 273fced

6 files changed

Lines changed: 132 additions & 0 deletions

File tree

dynamic-datasource-spring-boot-common/src/main/java/com/baomidou/dynamic/datasource/spring/boot/autoconfigure/DynamicDatasourceAopProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,12 @@ public class DynamicDatasourceAopProperties {
3838
* aop allowedPublicOnly
3939
*/
4040
private Boolean allowedPublicOnly = true;
41+
/**
42+
* Whether to allow SpEL type references (e.g. T(java.lang.Runtime)) and constructor
43+
* expressions in datasource key expressions resolved by DsSpelExpressionProcessor.
44+
* Defaults to false (restricted / safe mode) to prevent SpEL injection attacks.
45+
* Set to true only if your application genuinely requires such expressions and you
46+
* fully understand the security risk.
47+
*/
48+
private Boolean allowedSpelTypeAccess = false;
4149
}

dynamic-datasource-spring-boot-starter/src/main/java/com/baomidou/dynamic/datasource/spring/boot/autoconfigure/DynamicDataSourceAopConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
6666
DsProcessor sessionProcessor = new DsSessionProcessor();
6767
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
6868
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
69+
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
6970
headerProcessor.setNextProcessor(sessionProcessor);
7071
sessionProcessor.setNextProcessor(spelExpressionProcessor);
7172
return headerProcessor;

dynamic-datasource-spring-boot3-starter/src/main/java/com/baomidou/dynamic/datasource/spring/boot/autoconfigure/DynamicDataSourceAopConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
6666
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
6767
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
6868
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
69+
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
6970
headerProcessor.setNextProcessor(sessionProcessor);
7071
sessionProcessor.setNextProcessor(spelExpressionProcessor);
7172
return headerProcessor;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.common.v3;
17+
18+
import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
19+
import org.aopalliance.intercept.MethodInvocation;
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Test;
22+
import org.springframework.expression.EvaluationException;
23+
24+
import java.lang.reflect.Method;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertThrows;
28+
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.when;
30+
31+
/**
32+
* Security tests for {@link DsSpelExpressionProcessor} verifying that SpEL injection via
33+
* type references (T(...)) is blocked to prevent Remote Code Execution, and that the
34+
* behaviour can be restored by explicitly enabling {@code allowedSpelTypeAccess}.
35+
*/
36+
class DsSpelExpressionProcessorSecurityTest {
37+
38+
private DsSpelExpressionProcessor processor;
39+
private MethodInvocation invocation;
40+
41+
@BeforeEach
42+
void setUp() throws Exception {
43+
processor = new DsSpelExpressionProcessor();
44+
45+
Method method = SampleService.class.getMethod("getByTenant", String.class);
46+
invocation = mock(MethodInvocation.class);
47+
when(invocation.getMethod()).thenReturn(method);
48+
when(invocation.getArguments()).thenReturn(new Object[]{"tenant1"});
49+
when(invocation.getThis()).thenReturn(new SampleService());
50+
}
51+
52+
@Test
53+
void normalSpelExpressionShouldWork() {
54+
// #tenant reads the method parameter value normally
55+
String result = processor.doDetermineDatasource(invocation, "#tenant");
56+
assertEquals("tenant1", result);
57+
}
58+
59+
@Test
60+
void typeReferenceExpressionShouldBeBlockedByDefault() {
61+
// T(...) type references must be blocked to prevent SpEL injection / RCE
62+
assertThrows(EvaluationException.class, () ->
63+
processor.doDetermineDatasource(invocation, "T(java.lang.Runtime).getRuntime().exec('id')")
64+
);
65+
}
66+
67+
@Test
68+
void newInstanceExpressionShouldBeBlockedByDefault() {
69+
// new Type(...) constructor invocations must also be blocked
70+
assertThrows(EvaluationException.class, () ->
71+
processor.doDetermineDatasource(invocation, "new java.lang.ProcessBuilder('id').start()")
72+
);
73+
}
74+
75+
@Test
76+
void typeReferenceExpressionShouldWorkWhenExplicitlyAllowed() {
77+
// When allowedSpelTypeAccess=true, T(...) expressions are permitted (opt-in unsafe mode)
78+
processor.setAllowedSpelTypeAccess(true);
79+
// T(java.lang.String) is a safe type reference to verify the restriction is lifted
80+
String result = processor.doDetermineDatasource(invocation, "T(java.lang.String).valueOf(#tenant)");
81+
assertEquals("tenant1", result);
82+
}
83+
84+
static class SampleService {
85+
public String getByTenant(String tenant) {
86+
return tenant;
87+
}
88+
}
89+
}

dynamic-datasource-spring-boot4-starter/src/main/java/com/baomidou/dynamic/datasource/spring/boot/autoconfigure/DynamicDataSourceAopConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
6666
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
6767
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
6868
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
69+
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
6970
headerProcessor.setNextProcessor(sessionProcessor);
7071
sessionProcessor.setNextProcessor(spelExpressionProcessor);
7172
return headerProcessor;

dynamic-datasource-spring/src/main/java/com/baomidou/dynamic/datasource/processor/DsSpelExpressionProcessor.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
import org.springframework.core.DefaultParameterNameDiscoverer;
2323
import org.springframework.core.ParameterNameDiscoverer;
2424
import org.springframework.expression.BeanResolver;
25+
import org.springframework.expression.EvaluationException;
2526
import org.springframework.expression.ExpressionParser;
2627
import org.springframework.expression.ParserContext;
2728
import org.springframework.expression.spel.standard.SpelExpressionParser;
2829
import org.springframework.expression.spel.support.StandardEvaluationContext;
2930

3031
import java.lang.reflect.Method;
32+
import java.util.Collections;
3133

3234
/**
3335
* SpEL表达式处理器
@@ -69,6 +71,18 @@ public String getExpressionSuffix() {
6971
}
7072
};
7173
private BeanResolver beanResolver;
74+
/**
75+
* Whether to allow SpEL type references (e.g. {@code T(java.lang.Runtime)}) and constructor
76+
* expressions in datasource key expressions.
77+
* <p>
78+
* Defaults to {@code false} (restricted / safe mode). When {@code false}, any attempt to use
79+
* {@code T(...)} type-references or {@code new} constructor expressions will throw an
80+
* {@link EvaluationException}, preventing potential SpEL-injection / RCE attacks.
81+
* <p>
82+
* Set to {@code true} only if your application genuinely requires such expressions and you
83+
* fully understand the security implications.
84+
*/
85+
private boolean allowedSpelTypeAccess = false;
7286

7387
@Override
7488
public boolean matches(String key) {
@@ -82,10 +96,28 @@ public String doDetermineDatasource(MethodInvocation invocation, String key) {
8296
ExpressionRootObject rootObject = new ExpressionRootObject(method, arguments, invocation.getThis());
8397
StandardEvaluationContext context = new MethodBasedEvaluationContext(rootObject, method, arguments, NAME_DISCOVERER);
8498
context.setBeanResolver(beanResolver);
99+
if (!allowedSpelTypeAccess) {
100+
// Prevent SpEL injection: block T(...) type references and new instance creation
101+
context.setTypeLocator(typeName -> {
102+
throw new EvaluationException("Type access is not allowed in DS SpEL expressions: " + typeName);
103+
});
104+
context.setConstructorResolvers(Collections.emptyList());
105+
}
85106
final Object value = PARSER.parseExpression(key, parserContext).getValue(context);
86107
return value == null ? null : value.toString();
87108
}
88109

110+
/**
111+
* Allow or restrict SpEL type references ({@code T(...)}) and constructor expressions in
112+
* datasource key resolution. Defaults to {@code false} (restricted). Enable only when
113+
* strictly necessary and you accept the associated security risk.
114+
*
115+
* @param allowedSpelTypeAccess {@code true} to allow type access, {@code false} to block it
116+
*/
117+
public void setAllowedSpelTypeAccess(boolean allowedSpelTypeAccess) {
118+
this.allowedSpelTypeAccess = allowedSpelTypeAccess;
119+
}
120+
89121
/**
90122
* 设置解析上下文
91123
*

0 commit comments

Comments
 (0)