Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,12 @@ public class DynamicDatasourceAopProperties {
* aop allowedPublicOnly
*/
private Boolean allowedPublicOnly = true;
/**
* Whether to allow SpEL type references (e.g. T(java.lang.Runtime)) and constructor
* expressions in datasource key expressions resolved by DsSpelExpressionProcessor.
* Defaults to false (restricted / safe mode) to prevent SpEL injection attacks.
* Set to true only if your application genuinely requires such expressions and you
* fully understand the security risk.
*/
private Boolean allowedSpelTypeAccess = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
DsProcessor sessionProcessor = new DsSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.common.v3;

import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationException;

import java.lang.reflect.Method;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Security tests for {@link DsSpelExpressionProcessor} verifying that SpEL injection via
* type references (T(...)) is blocked to prevent Remote Code Execution, and that the
* behaviour can be restored by explicitly enabling {@code allowedSpelTypeAccess}.
*/
class DsSpelExpressionProcessorSecurityTest {

private DsSpelExpressionProcessor processor;
private MethodInvocation invocation;

@BeforeEach
void setUp() throws Exception {
processor = new DsSpelExpressionProcessor();

Method method = SampleService.class.getMethod("getByTenant", String.class);
invocation = mock(MethodInvocation.class);
when(invocation.getMethod()).thenReturn(method);
when(invocation.getArguments()).thenReturn(new Object[]{"tenant1"});
when(invocation.getThis()).thenReturn(new SampleService());
}

@Test
void normalSpelExpressionShouldWork() {
// #tenant reads the method parameter value normally
String result = processor.doDetermineDatasource(invocation, "#tenant");
assertEquals("tenant1", result);
}

@Test
void typeReferenceExpressionShouldBeBlockedByDefault() {
// T(...) type references must be blocked to prevent SpEL injection / RCE
assertThrows(EvaluationException.class, () ->
processor.doDetermineDatasource(invocation, "T(java.lang.Runtime).getRuntime().exec('id')")
);
}

@Test
void newInstanceExpressionShouldBeBlockedByDefault() {
// new Type(...) constructor invocations must also be blocked
assertThrows(EvaluationException.class, () ->
processor.doDetermineDatasource(invocation, "new java.lang.ProcessBuilder('id').start()")
Comment on lines +61 to +71
Comment on lines +69 to +71
);
}

@Test
void typeReferenceExpressionShouldWorkWhenExplicitlyAllowed() {
// When allowedSpelTypeAccess=true, T(...) expressions are permitted (opt-in unsafe mode)
processor.setAllowedSpelTypeAccess(true);
// T(java.lang.String) is a safe type reference to verify the restriction is lifted
String result = processor.doDetermineDatasource(invocation, "T(java.lang.String).valueOf(#tenant)");
assertEquals("tenant1", result);
}

static class SampleService {
public String getByTenant(String tenant) {
return tenant;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public DsProcessor dsProcessor(BeanFactory beanFactory) {
DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
spelExpressionProcessor.setAllowedSpelTypeAccess(properties.getAop().getAllowedSpelTypeAccess());
headerProcessor.setNextProcessor(sessionProcessor);
sessionProcessor.setNextProcessor(spelExpressionProcessor);
return headerProcessor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Collections;

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

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

/**
* Allow or restrict SpEL type references ({@code T(...)}) and constructor expressions in
* datasource key resolution. Defaults to {@code false} (restricted). Enable only when
* strictly necessary and you accept the associated security risk.
*
* @param allowedSpelTypeAccess {@code true} to allow type access, {@code false} to block it
*/
public void setAllowedSpelTypeAccess(boolean allowedSpelTypeAccess) {
this.allowedSpelTypeAccess = allowedSpelTypeAccess;
}

/**
* 设置解析上下文
*
Expand Down
Loading