This is an automated email from the ASF dual-hosted git repository.
albumenj pushed a commit to branch 2.6.x
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/2.6.x by this push:
new b100a6d [2.6.X]Add some serialize check (#7685)
b100a6d is described below
commit b100a6d22b5cf051f76dad2b05aaf88e74e22154
Author: Wu Zhiguo <[email protected]>
AuthorDate: Thu May 13 12:13:42 2021 +0800
[2.6.X]Add some serialize check (#7685)
* fix security problem
* use Boolean.parseBoolean to check system env
* ignore '#' when parsing url
* fix url was truncated expectedly
* fix ut
* fix ut
---
.../rpc/cluster/router/script/ScriptRouter.java | 125 ++++++---
.../com.alibaba.dubbo.rpc.cluster.RouterFactory | 1 -
.../com.alibaba.dubbo.rpc.cluster.RouterFactory | 1 +
.../java/com/alibaba/dubbo/common/Constants.java | 15 +-
.../main/java/com/alibaba/dubbo/common/URL.java | 7 +
.../common/beanutil/JavaBeanSerializeUtil.java | 2 +
.../com/alibaba/dubbo/common/utils/LFUCache.java | 286 +++++++++++++++++++++
.../com/alibaba/dubbo/common/utils/PojoUtils.java | 1 +
.../dubbo/common/utils/SerializeClassChecker.java | 150 +++++++++++
.../alibaba/dubbo/common/utils/StringUtils.java | 8 +
.../main/resources/security/serialize.blockedlist | 167 ++++++++++++
dubbo-config/dubbo-config-api/pom.xml | 5 +
.../alibaba/dubbo/config/GenericServiceTest.java | 6 +
.../com.alibaba.dubbo.rpc.cluster.RouterFactory | 1 +
.../alibaba/dubbo/rpc/filter/GenericFilter.java | 19 ++
.../protocol/dubbo/DubboInvokerAvilableTest.java | 8 +-
.../rpc/protocol/hessian/HessianProtocolTest.java | 10 +
.../dubbo/rpc/protocol/http/HttpProtocolTest.java | 7 +
18 files changed, 784 insertions(+), 35 deletions(-)
diff --git
a/dubbo-cluster/src/main/java/com/alibaba/dubbo/rpc/cluster/router/script/ScriptRouter.java
b/dubbo-cluster/src/main/java/com/alibaba/dubbo/rpc/cluster/router/script/ScriptRouter.java
index 89241ad..700abbd 100644
---
a/dubbo-cluster/src/main/java/com/alibaba/dubbo/rpc/cluster/router/script/ScriptRouter.java
+++
b/dubbo-cluster/src/main/java/com/alibaba/dubbo/rpc/cluster/router/script/ScriptRouter.java
@@ -20,6 +20,7 @@ import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
+import com.alibaba.dubbo.common.utils.StringUtils;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.RpcContext;
@@ -32,6 +33,13 @@ import javax.script.CompiledScript;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permissions;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -53,17 +61,51 @@ public class ScriptRouter extends AbstractRouter {
private final String rule;
+ private CompiledScript function;
+
+ private AccessControlContext accessControlContext;
+
+ {
+ //Just give permission of reflect to access member.
+ Permissions perms = new Permissions();
+ perms.add(new RuntimePermission("accessDeclaredMembers"));
+ // Cast to Certificate[] required because of ambiguity:
+ ProtectionDomain domain = new ProtectionDomain(new CodeSource(null,
(Certificate[]) null), perms);
+ accessControlContext = new AccessControlContext(new
ProtectionDomain[]{domain});
+ }
+
public ScriptRouter(URL url) {
this.url = url;
- String type = url.getParameter(Constants.TYPE_KEY);
this.priority = url.getParameter(Constants.PRIORITY_KEY,
DEFAULT_PRIORITY);
- String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
- if (type == null || type.length() == 0) {
- type = Constants.DEFAULT_SCRIPT_TYPE_KEY;
+
+ this.engine = getEngine(url);
+ this.rule = getRule(url);
+
+ try {
+ Compilable compilable = (Compilable) engine;
+ function = compilable.compile(rule);
+ } catch (ScriptException e) {
+ logger.error("route error, rule has been ignored. rule: " + rule +
+ ", url: " + RpcContext.getContext().getUrl(), e);
}
- if (rule == null || rule.length() == 0) {
- throw new IllegalStateException(new IllegalStateException("route
rule can not be empty. rule:" + rule));
+ }
+
+ /**
+ * get rule from url parameters.
+ */
+ private String getRule(URL url) {
+ String vRule = url.getParameterAndDecoded(Constants.RULE_KEY);
+ if (StringUtils.isEmpty(vRule)) {
+ throw new IllegalStateException("route rule can not be empty.");
}
+ return vRule;
+ }
+
+ /**
+ * create ScriptEngine instance by type from url parameters, then cache it
+ */
+ private ScriptEngine getEngine(URL url) {
+ String type = url.getParameter(Constants.TYPE_KEY,
Constants.DEFAULT_SCRIPT_TYPE_KEY);
ScriptEngine engine = engines.get(type);
if (engine == null) {
engine = new ScriptEngineManager().getEngineByName(type);
@@ -72,38 +114,61 @@ public class ScriptRouter extends AbstractRouter {
}
engines.put(type, engine);
}
- this.engine = engine;
- this.rule = rule;
+
+ return engine;
}
+
@Override
@SuppressWarnings("unchecked")
- public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url,
Invocation invocation) throws RpcException {
- try {
- List<Invoker<T>> invokersCopy = new
ArrayList<Invoker<T>>(invokers);
- Compilable compilable = (Compilable) engine;
- Bindings bindings = engine.createBindings();
- bindings.put("invokers", invokersCopy);
- bindings.put("invocation", invocation);
- bindings.put("context", RpcContext.getContext());
- CompiledScript function = compilable.compile(rule);
- Object obj = function.eval(bindings);
- if (obj instanceof Invoker[]) {
- invokersCopy = Arrays.asList((Invoker<T>[]) obj);
- } else if (obj instanceof Object[]) {
- invokersCopy = new ArrayList<Invoker<T>>();
- for (Object inv : (Object[]) obj) {
- invokersCopy.add((Invoker<T>) inv);
+ public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers, URL
url, final Invocation invocation) throws RpcException {
+ if (engine == null || function == null) {
+ return invokers;
+ }
+ final Bindings bindings = createBindings(invokers, invocation);
+ return getRoutedInvokers(AccessController.doPrivileged(new
PrivilegedAction() {
+ @Override
+ public Object run() {
+ try {
+ return function.eval(bindings);
+ } catch (ScriptException e) {
+ logger.error("route error, rule has been ignored. rule: "
+ rule + ", method:" +
+ invocation.getMethodName() + ", url: " +
RpcContext.getContext().getUrl(), e);
+ return invokers;
}
- } else {
- invokersCopy = (List<Invoker<T>>) obj;
}
- return invokersCopy;
- } catch (ScriptException e) {
- //fail then ignore rule .invokers.
- logger.error("route error , rule has been ignored. rule: " + rule
+ ", method:" + invocation.getMethodName() + ", url: " +
RpcContext.getContext().getUrl(), e);
+ }, accessControlContext));
+ }
+
+ /**
+ * get routed invokers from result of script rule evaluation
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> List<Invoker<T>> getRoutedInvokers(Object obj) {
+ if (obj instanceof Invoker[]) {
+ return Arrays.asList((Invoker<T>[]) obj);
+ } else if (obj instanceof Object[]) {
+ Object[] objects = (Object[]) obj;
+ List<Invoker<T>> invokers = new ArrayList<Invoker<T>>();
+ for (Object object : objects) {
+ invokers.add((Invoker<T>) object);
+ }
+
return invokers;
+ } else {
+ return (List<Invoker<T>>) obj;
}
}
+ /**
+ * create bindings for script engine
+ */
+ private <T> Bindings createBindings(List<Invoker<T>> invokers, Invocation
invocation) {
+ Bindings bindings = engine.createBindings();
+ // create a new List of invokers
+ bindings.put("invokers", new ArrayList<Invoker<T>>(invokers));
+ bindings.put("invocation", invocation);
+ bindings.put("context", RpcContext.getContext());
+ return bindings;
+ }
}
diff --git
a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
index 239c6f0..efa0123 100644
---
a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
+++
b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
@@ -1,3 +1,2 @@
file=com.alibaba.dubbo.rpc.cluster.router.file.FileRouterFactory
-script=com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouterFactory
condition=com.alibaba.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
\ No newline at end of file
diff --git
a/dubbo-cluster/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
b/dubbo-cluster/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
new file mode 100644
index 0000000..a7f6ddc
--- /dev/null
+++
b/dubbo-cluster/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
@@ -0,0 +1 @@
+script=com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouterFactory
diff --git a/dubbo-common/src/main/java/com/alibaba/dubbo/common/Constants.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/Constants.java
index 126319f..0bb2b2e 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/Constants.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/Constants.java
@@ -647,8 +647,17 @@ public class Constants {
public static final String TELNET = "telnet";
- /*
- * private Constants(){ }
- */
+ public static final String DOT_REGEX = "\\.";
+
+ public static final String UNDERLINE_SEPARATOR = "_";
+
+ public static final String CLASS_DESERIALIZE_BLOCK_ALL =
"dubbo.security.serialize.blockAllClassExceptAllow";
+
+ public static final String CLASS_DESERIALIZE_ALLOWED_LIST =
"dubbo.security.serialize.allowedClassList";
+
+ public static final String CLASS_DESERIALIZE_BLOCKED_LIST =
"dubbo.security.serialize.blockedClassList";
+
+ public static final String ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE =
"dubbo.security.serialize.generic.native-java-enable";
+ public static final String SERIALIZE_BLOCKED_LIST_FILE_PATH =
"security/serialize.blockedlist";
}
diff --git a/dubbo-common/src/main/java/com/alibaba/dubbo/common/URL.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/URL.java
index f8a1119..f49aa55 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/URL.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/URL.java
@@ -204,6 +204,13 @@ public final class URL implements Serializable {
}
url = url.substring(0, i);
}
+
+ // ignore the url content following '#'
+ int poundIndex = url.indexOf('#');
+ if (poundIndex != -1) {
+ url = url.substring(0, poundIndex);
+ }
+
i = url.indexOf("://");
if (i >= 0) {
if (i == 0) throw new IllegalStateException("url missing protocol:
\"" + url + "\"");
diff --git
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/beanutil/JavaBeanSerializeUtil.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/beanutil/JavaBeanSerializeUtil.java
index d89f4c0..643f0ca 100644
---
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/beanutil/JavaBeanSerializeUtil.java
+++
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/beanutil/JavaBeanSerializeUtil.java
@@ -20,6 +20,7 @@ import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.utils.LogHelper;
import com.alibaba.dubbo.common.utils.ReflectUtils;
+import com.alibaba.dubbo.common.utils.SerializeClassChecker;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
@@ -439,6 +440,7 @@ public final class JavaBeanSerializeUtil {
if (isReferenceType(name)) {
name = name.substring(1, name.length() - 1);
}
+ SerializeClassChecker.getInstance().validateClass(name);
return Class.forName(name, false, loader);
}
diff --git
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LFUCache.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LFUCache.java
new file mode 100644
index 0000000..75441c9
--- /dev/null
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/LFUCache.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.alibaba.dubbo.common.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LFUCache<K, V> {
+
+ private Map<K, CacheNode<K, V>> map;
+ private CacheDeque<K, V>[] freqTable;
+
+ private final int capacity;
+ private int evictionCount;
+ private int curSize = 0;
+
+ private final ReentrantLock lock = new ReentrantLock();
+ private static final int DEFAULT_INITIAL_CAPACITY = 1000;
+
+ private static final float DEFAULT_EVICTION_FACTOR = 0.75f;
+
+ public LFUCache() {
+ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_EVICTION_FACTOR);
+ }
+
+ /**
+ * Constructs and initializes cache with specified capacity and eviction
+ * factor. Unacceptable parameter values followed with
+ * {@link IllegalArgumentException}.
+ *
+ * @param maxCapacity cache max capacity
+ * @param evictionFactor cache proceedEviction factor
+ */
+ @SuppressWarnings("unchecked")
+ public LFUCache(final int maxCapacity, final float evictionFactor) {
+ if (maxCapacity <= 0) {
+ throw new IllegalArgumentException("Illegal initial capacity: " +
+ maxCapacity);
+ }
+ boolean factorInRange = evictionFactor <= 1 && evictionFactor > 0;
+ if (!factorInRange || Float.isNaN(evictionFactor)) {
+ throw new IllegalArgumentException("Illegal eviction factor value:"
+ + evictionFactor);
+ }
+ this.capacity = maxCapacity;
+ this.evictionCount = (int) (capacity * evictionFactor);
+ this.map = new HashMap<K, CacheNode<K, V>>();
+ this.freqTable = new CacheDeque[capacity + 1];
+ for (int i = 0; i <= capacity; i++) {
+ freqTable[i] = new CacheDeque<K, V>();
+ }
+ for (int i = 0; i < capacity; i++) {
+ freqTable[i].nextDeque = freqTable[i + 1];
+ }
+ freqTable[capacity].nextDeque = freqTable[capacity];
+ }
+
+ public int getCapacity() {
+ return capacity;
+ }
+
+ public V put(final K key, final V value) {
+ CacheNode<K, V> node;
+ lock.lock();
+ try {
+ node = map.get(key);
+ if (node != null) {
+ CacheNode.withdrawNode(node);
+ node.value = value;
+ freqTable[0].addLastNode(node);
+ map.put(key, node);
+ } else {
+ node = freqTable[0].addLast(key, value);
+ map.put(key, node);
+ curSize++;
+ if (curSize > capacity) {
+ proceedEviction();
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ return node.value;
+ }
+
+ public V remove(final K key) {
+ CacheNode<K, V> node = null;
+ lock.lock();
+ try {
+ if (map.containsKey(key)) {
+ node = map.remove(key);
+ if (node != null) {
+ CacheNode.withdrawNode(node);
+ }
+ curSize--;
+ }
+ } finally {
+ lock.unlock();
+ }
+ return (node != null) ? node.value : null;
+ }
+
+ public V get(final K key) {
+ CacheNode<K, V> node = null;
+ lock.lock();
+ try {
+ if (map.containsKey(key)) {
+ node = map.get(key);
+ CacheNode.withdrawNode(node);
+ node.owner.nextDeque.addLastNode(node);
+ }
+ } finally {
+ lock.unlock();
+ }
+ return (node != null) ? node.value : null;
+ }
+
+ /**
+ * Evicts less frequently used elements corresponding to eviction factor,
+ * specified at instantiation step.
+ *
+ * @return number of evicted elements
+ */
+ private int proceedEviction() {
+ int targetSize = capacity - evictionCount;
+ int evictedElements = 0;
+
+ FREQ_TABLE_ITER_LOOP:
+ for (int i = 0; i <= capacity; i++) {
+ CacheNode<K, V> node;
+ while (!freqTable[i].isEmpty()) {
+ node = freqTable[i].pollFirst();
+ remove(node.key);
+ if (targetSize >= curSize) {
+ break FREQ_TABLE_ITER_LOOP;
+ }
+ evictedElements++;
+ }
+ }
+ return evictedElements;
+ }
+
+ /**
+ * Returns cache current size.
+ *
+ * @return cache size
+ */
+ public int getSize() {
+ return curSize;
+ }
+
+ static class CacheNode<K, V> {
+
+ CacheNode<K, V> prev;
+ CacheNode<K, V> next;
+ K key;
+ V value;
+ CacheDeque<K, V> owner;
+
+ CacheNode() {
+ }
+
+ CacheNode(final K key, final V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * This method takes specified node and reattaches it neighbors nodes
+ * links to each other, so specified node will no longer tied with
them.
+ * Returns united node, returns null if argument is null.
+ *
+ * @param node note to retrieve
+ * @param <K> key
+ * @param <V> value
+ * @return retrieved node
+ */
+ static <K, V> CacheNode<K, V> withdrawNode(
+ final CacheNode<K, V> node) {
+ if (node != null && node.prev != null) {
+ node.prev.next = node.next;
+ if (node.next != null) {
+ node.next.prev = node.prev;
+ }
+ }
+ return node;
+ }
+
+ }
+
+ /**
+ * Custom deque implementation of LIFO type. Allows to place element at top
+ * of deque and poll very last added elements. An arbitrary node from the
+ * deque can be removed with {@link CacheNode#withdrawNode(CacheNode)}
+ * method.
+ *
+ * @param <K> key
+ * @param <V> value
+ */
+ static class CacheDeque<K, V> {
+
+ CacheNode<K, V> last;
+ CacheNode<K, V> first;
+ CacheDeque<K, V> nextDeque;
+
+ /**
+ * Constructs list and initializes last and first pointers.
+ */
+ CacheDeque() {
+ last = new CacheNode<K, V>();
+ first = new CacheNode<K, V>();
+ last.next = first;
+ first.prev = last;
+ }
+
+ /**
+ * Puts the node with specified key and value at the end of the deque
+ * and returns node.
+ *
+ * @param key key
+ * @param value value
+ * @return added node
+ */
+ CacheNode<K, V> addLast(final K key, final V value) {
+ CacheNode<K, V> node = new CacheNode<K, V>(key, value);
+ node.owner = this;
+ node.next = last.next;
+ node.prev = last;
+ node.next.prev = node;
+ last.next = node;
+ return node;
+ }
+
+ CacheNode<K, V> addLastNode(final CacheNode<K, V> node) {
+ node.owner = this;
+ node.next = last.next;
+ node.prev = last;
+ node.next.prev = node;
+ last.next = node;
+ return node;
+ }
+
+ /**
+ * Retrieves and removes the first node of this deque.
+ *
+ * @return removed node
+ */
+ CacheNode<K, V> pollFirst() {
+ CacheNode<K, V> node = null;
+ if (first.prev != last) {
+ node = first.prev;
+ first.prev = node.prev;
+ first.prev.next = first;
+ node.prev = null;
+ node.next = null;
+ }
+ return node;
+ }
+
+ /**
+ * Checks if link to the last node points to link to the first node.
+ *
+ * @return is deque empty
+ */
+ boolean isEmpty() {
+ return last.next == first;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/PojoUtils.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/PojoUtils.java
index 754fd2d..f069151 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/PojoUtils.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/PojoUtils.java
@@ -371,6 +371,7 @@ public class PojoUtils {
if (pojo instanceof Map<?, ?> && type != null) {
Object className = ((Map<Object, Object>) pojo).get("class");
if (className instanceof String) {
+ SerializeClassChecker.getInstance().validateClass((String)
className);
try {
type = ClassHelper.forName((String) className);
} catch (ClassNotFoundException e) {
diff --git
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/SerializeClassChecker.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/SerializeClassChecker.java
new file mode 100644
index 0000000..fc19223
--- /dev/null
+++
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/SerializeClassChecker.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.alibaba.dubbo.common.utils;
+
+import com.alibaba.dubbo.common.beanutil.JavaBeanSerializeUtil;
+import com.alibaba.dubbo.common.Constants;
+import com.alibaba.dubbo.common.logger.Logger;
+import com.alibaba.dubbo.common.logger.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class SerializeClassChecker {
+ private static final Logger logger =
LoggerFactory.getLogger(SerializeClassChecker.class);
+
+ private static volatile SerializeClassChecker INSTANCE = null;
+
+ private final boolean BLOCK_ALL_CLASS_EXCEPT_ALLOW;
+ private final Set<String> CLASS_DESERIALIZE_ALLOWED_SET = new
ConcurrentHashSet<String>();
+ private final Set<String> CLASS_DESERIALIZE_BLOCKED_SET = new
ConcurrentHashSet<String>();
+
+ private final Object CACHE = new Object();
+ private final LFUCache<String, Object> CLASS_ALLOW_LFU_CACHE = new
LFUCache<String, Object>();
+ private final LFUCache<String, Object> CLASS_BLOCK_LFU_CACHE = new
LFUCache<String, Object>();
+
+ private final AtomicLong counter = new AtomicLong(0);
+
+ private SerializeClassChecker() {
+ String blockAllClassExceptAllow =
System.getProperty(Constants.CLASS_DESERIALIZE_BLOCK_ALL, "false");
+ BLOCK_ALL_CLASS_EXCEPT_ALLOW =
Boolean.parseBoolean(blockAllClassExceptAllow);
+
+ String[] lines;
+ try {
+ ClassLoader classLoader =
JavaBeanSerializeUtil.class.getClassLoader();
+ if (classLoader != null) {
+ lines =
IOUtils.readLines(classLoader.getResourceAsStream(Constants.SERIALIZE_BLOCKED_LIST_FILE_PATH));
+ } else {
+ lines =
IOUtils.readLines(ClassLoader.getSystemResourceAsStream(Constants.SERIALIZE_BLOCKED_LIST_FILE_PATH));
+ }
+ for (String line : lines) {
+ line = line.trim();
+ if (StringUtils.isEmpty(line) || line.startsWith("#")) {
+ continue;
+ }
+ CLASS_DESERIALIZE_BLOCKED_SET.add(line);
+ }
+
+ } catch (IOException e) {
+ logger.error("Failed to load blocked class list! Will ignore
default blocked list.", e);
+ }
+
+ String allowedClassList =
System.getProperty(Constants.CLASS_DESERIALIZE_ALLOWED_LIST,
"").trim().toLowerCase(Locale.ROOT);
+ String blockedClassList =
System.getProperty(Constants.CLASS_DESERIALIZE_BLOCKED_LIST,
"").trim().toLowerCase(Locale.ROOT);
+
+ if (StringUtils.isNotEmpty(allowedClassList)) {
+ String[] classStrings = allowedClassList.trim().split(",");
+ CLASS_DESERIALIZE_ALLOWED_SET.addAll(Arrays.asList(classStrings));
+ }
+
+ if (StringUtils.isNotEmpty(blockedClassList)) {
+ String[] classStrings = blockedClassList.trim().split(",");
+ CLASS_DESERIALIZE_BLOCKED_SET.addAll(Arrays.asList(classStrings));
+ }
+
+ }
+
+ public static SerializeClassChecker getInstance() {
+ if (INSTANCE == null) {
+ synchronized (SerializeClassChecker.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new SerializeClassChecker();
+ }
+ }
+ }
+ return INSTANCE;
+ }
+
+ /**
+ * For ut only
+ */
+ @Deprecated
+ protected static void clearInstance() {
+ INSTANCE = null;
+ }
+
+ /**
+ * Check if a class is in block list, using prefix match
+ *
+ * @throws IllegalArgumentException if class is blocked
+ * @param name class name ( all are convert to lower case )
+ */
+ public void validateClass(String name) {
+ name = name.toLowerCase(Locale.ROOT);
+ if (CACHE == CLASS_ALLOW_LFU_CACHE.get(name)) {
+ return;
+ }
+
+ if (CACHE == CLASS_BLOCK_LFU_CACHE.get(name)) {
+ error(name);
+ }
+
+ for (String allowedPrefix : CLASS_DESERIALIZE_ALLOWED_SET) {
+ if (name.startsWith(allowedPrefix)) {
+ CLASS_ALLOW_LFU_CACHE.put(name, CACHE);
+ return;
+ }
+ }
+
+ for (String blockedPrefix : CLASS_DESERIALIZE_BLOCKED_SET) {
+ if (BLOCK_ALL_CLASS_EXCEPT_ALLOW ||
name.startsWith(blockedPrefix)) {
+ CLASS_BLOCK_LFU_CACHE.put(name, CACHE);
+ error(name);
+ }
+ }
+
+ CLASS_ALLOW_LFU_CACHE.put(name, CACHE);
+ }
+
+ private void error(String name) {
+ String notice = "Trigger the safety barrier! " +
+ "Catch not allowed serialize class. " +
+ "Class name: " + name + " . " +
+ "This means currently maybe being attacking by others." +
+ "If you are sure this is a mistake, " +
+ "please add this class name to `" +
Constants.CLASS_DESERIALIZE_ALLOWED_LIST +
+ "` as a system environment property.";
+ if (counter.incrementAndGet() % 1000 == 0 || counter.get() < 100) {
+ logger.error(notice);
+ }
+ throw new IllegalArgumentException(notice);
+ }
+
+}
\ No newline at end of file
diff --git
a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/StringUtils.java
b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/StringUtils.java
index faf349f..d6a0e56 100644
--- a/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/StringUtils.java
+++ b/dubbo-common/src/main/java/com/alibaba/dubbo/common/utils/StringUtils.java
@@ -427,4 +427,12 @@ public final class StringUtils {
}
return buf.toString();
}
+
+ public static String toOSStyleKey(String key) {
+ key = key.toUpperCase().replaceAll(Constants.DOT_REGEX,
Constants.UNDERLINE_SEPARATOR);
+ if (!key.startsWith("DUBBO_")) {
+ key = "DUBBO_" + key;
+ }
+ return key;
+ }
}
\ No newline at end of file
diff --git a/dubbo-common/src/main/resources/security/serialize.blockedlist
b/dubbo-common/src/main/resources/security/serialize.blockedlist
new file mode 100644
index 0000000..de0b68d
--- /dev/null
+++ b/dubbo-common/src/main/resources/security/serialize.blockedlist
@@ -0,0 +1,167 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+#
+aj.org.objectweb.asm.
+br.com.anteros.
+ch.qos.logback.
+clojure.core$constantly
+clojure.main$eval_opt
+com.alibaba.citrus.springext.support.parser.abstractnamedproxybeandefinitionparser$proxytargetfactory
+com.alibaba.citrus.springext.util.springextutil.abstractproxy
+com.alibaba.druid.pool.druiddatasource
+com.alibaba.druid.stat.jdbcdatasourcestat
+com.alibaba.fastjson.annotation
+com.alipay.custrelation.service.model.redress.pair
+com.caucho.
+com.ibatis.
+com.mchange
+com.mysql.cj.jdbc.admin.
+com.mysql.cj.jdbc.mysqlconnectionpooldatasource
+com.mysql.cj.jdbc.mysqldatasource
+com.mysql.cj.jdbc.mysqlxadatasource
+com.mysql.cj.log.
+com.p6spy.engine.
+com.rometools.rome.feed.impl.equalsbean
+com.rometools.rome.feed.impl.tostringbean
+com.sun.
+com.taobao.eagleeye.wrapper
+com.zaxxer.hikari.
+flex.messaging.util.concurrent.
+java.awt.i
+java.awt.p
+java.beans.expression
+java.io.closeable
+java.io.serializable
+java.lang.autocloseable
+java.lang.class
+java.lang.cloneable
+java.lang.iterable
+java.lang.object
+java.lang.readable
+java.lang.runnable
+java.lang.thread
+java.lang.unixprocess
+java.net.inetaddress
+java.net.socket
+java.net.url
+java.rmi
+java.security.signedobject
+java.util.collection
+java.util.eventlistener
+java.util.jar.
+java.util.logging.
+java.util.prefs.
+java.util.serviceloader$lazyiterator
+javassist.
+javax.activation.
+javax.imageio.imageio$containsfilter
+javax.imageio.spi.serviceregistry
+javax.management.
+javax.naming.
+javax.net.
+javax.print.
+javax.script.
+javax.sound.
+javax.swing.j
+javax.tools.
+javax.xml
+jdk.internal.
+jodd.db.connection.
+junit.
+net.bytebuddy.dynamic.loading.bytearrayclassloader
+net.sf.cglib.
+net.sf.ehcache.hibernate.
+net.sf.ehcache.transaction.manager.
+oracle.jdbc.
+oracle.jms.aq
+oracle.net
+org.aoju.bus.proxy.provider.
+org.apache.activemq.activemqconnectionfactory
+org.apache.activemq.activemqxaconnectionfactory
+org.apache.activemq.jms.pool.
+org.apache.activemq.pool.
+org.apache.activemq.spring.
+org.apache.aries.transaction.
+org.apache.axis2.jaxws.spi.handler.
+org.apache.axis2.transport.jms.
+org.apache.bcel
+org.apache.carbondata.core.scan.expression.expressionresult
+org.apache.catalina.
+org.apache.cocoon.
+org.apache.commons.beanutils
+org.apache.commons.collections.comparators.
+org.apache.commons.collections.functors
+org.apache.commons.collections.functors.
+org.apache.commons.collections.transformer
+org.apache.commons.collections4.comparators
+org.apache.commons.collections4.functors
+org.apache.commons.collections4.transformer
+org.apache.commons.configuration
+org.apache.commons.dbcp
+org.apache.commons.fileupload
+org.apache.commons.jelly.
+org.apache.commons.logging.
+org.apache.commons.proxy.
+org.apache.cxf.jaxrs.provider.
+org.apache.hadoop.shaded.com.zaxxer.hikari.
+org.apache.http.auth.
+org.apache.http.conn.
+org.apache.http.cookie.
+org.apache.http.impl.
+org.apache.ibatis.datasource
+org.apache.ibatis.executor.
+org.apache.ibatis.javassist.
+org.apache.ibatis.ognl.
+org.apache.ibatis.parsing.
+org.apache.ibatis.reflection.
+org.apache.ibatis.scripting.
+org.apache.ignite.cache.jta.
+org.apache.log4j.
+org.apache.logging.
+org.apache.myfaces.context.servlet
+org.apache.openjpa.ee.
+org.apache.shiro.jndi.
+org.apache.shiro.realm.
+org.apache.tomcat
+org.apache.wicket.util
+org.apache.xalan
+org.apache.xbean.
+org.apache.xpath.xpathcontext
+org.codehaus.groovy.runtime
+org.codehaus.jackson.
+org.eclipse.jetty.
+org.geotools.filter.constantexpression
+org.h2.jdbcx.
+org.h2.server.
+org.hibernate
+org.javasimon.
+org.jaxen.
+org.jboss
+org.jdom.
+org.jdom2.transform.
+org.logicalcobwebs.
+org.mortbay.jetty.
+org.mozilla.javascript
+org.objectweb.asm.
+org.osjava.sj.
+org.python.core
+org.quartz.
+org.slf4j.
+org.springframework.
+org.yaml.snakeyaml.tokens.directivetoken
+sun.rmi.server.unicastref
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/pom.xml
b/dubbo-config/dubbo-config-api/pom.xml
index 22e9d30..1881d81 100644
--- a/dubbo-config/dubbo-config-api/pom.xml
+++ b/dubbo-config/dubbo-config-api/pom.xml
@@ -31,6 +31,11 @@
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
+ <artifactId>dubbo-common</artifactId>
+ <version>${project.parent.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
diff --git
a/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/GenericServiceTest.java
b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/GenericServiceTest.java
index 9981cb1..54f0c2c 100644
---
a/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/GenericServiceTest.java
+++
b/dubbo-config/dubbo-config-api/src/test/java/com/alibaba/dubbo/config/GenericServiceTest.java
@@ -31,6 +31,7 @@ import com.alibaba.dubbo.rpc.service.GenericException;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
@@ -46,6 +47,11 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class GenericServiceTest {
+ @Before
+ public void setup() {
+ System.setProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE,
"true");
+ }
+
@Test
public void testGenericServiceException() {
ServiceConfig<GenericService> service = new
ServiceConfig<GenericService>();
diff --git
a/dubbo-registry/dubbo-registry-default/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
b/dubbo-registry/dubbo-registry-default/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
new file mode 100644
index 0000000..a7f6ddc
--- /dev/null
+++
b/dubbo-registry/dubbo-registry-default/src/test/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.RouterFactory
@@ -0,0 +1 @@
+script=com.alibaba.dubbo.rpc.cluster.router.script.ScriptRouterFactory
diff --git
a/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/filter/GenericFilter.java
b/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/filter/GenericFilter.java
index b8d9d87..1d82c9f 100644
---
a/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/filter/GenericFilter.java
+++
b/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/filter/GenericFilter.java
@@ -24,6 +24,8 @@ import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.io.UnsafeByteArrayInputStream;
import com.alibaba.dubbo.common.io.UnsafeByteArrayOutputStream;
+import com.alibaba.dubbo.common.logger.Logger;
+import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.common.serialize.Serialization;
import com.alibaba.dubbo.common.utils.PojoUtils;
import com.alibaba.dubbo.common.utils.ReflectUtils;
@@ -48,6 +50,7 @@ import java.lang.reflect.Method;
*/
@Activate(group = Constants.PROVIDER, order = -20000)
public class GenericFilter implements Filter {
+ private static final Logger logger =
LoggerFactory.getLogger(GenericFilter.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws
RpcException {
@@ -74,6 +77,17 @@ public class GenericFilter implements Filter {
||
ProtocolUtils.isDefaultGenericSerialization(generic)) {
args = PojoUtils.realize(args, params,
method.getGenericParameterTypes());
} else if (ProtocolUtils.isJavaGenericSerialization(generic)) {
+ if (!nativeJavaSerializerEnabled()) {
+ String notice = "Trigger the safety barrier! " +
+ "Native Java Serializer is not allowed by
default." +
+ "This means currently maybe being attacking by
others. " +
+ "If you are sure this is a mistake, " +
+ "please set `" +
Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE + "` enable in configuration! " +
+ "Before doing so, please make sure you have
configure JEP290 to prevent serialization attack.";
+ logger.error(notice);
+ throw new RpcException(new
IllegalStateException(notice));
+ }
+
for (int i = 0; i < args.length; i++) {
if (byte[].class == args[i].getClass()) {
try {
@@ -141,4 +155,9 @@ public class GenericFilter implements Filter {
return invoker.invoke(inv);
}
+ private boolean nativeJavaSerializerEnabled() {
+ return
Boolean.parseBoolean(System.getProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE))
+ ||
Boolean.parseBoolean(System.getenv(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE))
+ ||
Boolean.parseBoolean(System.getenv(StringUtils.toOSStyleKey(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE)));
+ }
}
diff --git
a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/com/alibaba/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/com/alibaba/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
index 42bbd76..f10d44b 100644
---
a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/com/alibaba/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
+++
b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/com/alibaba/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java
@@ -27,6 +27,7 @@ import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.ProxyFactory;
import com.alibaba.dubbo.rpc.protocol.dubbo.support.ProtocolUtils;
import junit.framework.Assert;
+import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
@@ -52,7 +53,12 @@ public class DubboInvokerAvilableTest {
@Before
public void setUp() throws Exception {
- protocol = new DubboProtocol();
+ protocol = DubboProtocol.getDubboProtocol();
+ }
+
+ @After
+ public void tearDown() {
+ protocol.destroy();
}
@Test
diff --git
a/dubbo-rpc/dubbo-rpc-hessian/src/test/java/com/alibaba/dubbo/rpc/protocol/hessian/HessianProtocolTest.java
b/dubbo-rpc/dubbo-rpc-hessian/src/test/java/com/alibaba/dubbo/rpc/protocol/hessian/HessianProtocolTest.java
index bae0333..167f0a4 100644
---
a/dubbo-rpc/dubbo-rpc-hessian/src/test/java/com/alibaba/dubbo/rpc/protocol/hessian/HessianProtocolTest.java
+++
b/dubbo-rpc/dubbo-rpc-hessian/src/test/java/com/alibaba/dubbo/rpc/protocol/hessian/HessianProtocolTest.java
@@ -16,6 +16,7 @@
*/
package com.alibaba.dubbo.rpc.protocol.hessian;
+import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.beanutil.JavaBeanDescriptor;
import com.alibaba.dubbo.common.beanutil.JavaBeanSerializeUtil;
@@ -34,6 +35,7 @@ import
com.alibaba.dubbo.rpc.protocol.hessian.HessianServiceImpl.MyException;
import com.alibaba.dubbo.rpc.service.GenericService;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
@@ -47,6 +49,11 @@ import static org.junit.Assert.fail;
*/
public class HessianProtocolTest {
+ @Before
+ public void setup() {
+ System.setProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE,
"true");
+ }
+
@Test
public void testHessianProtocol() {
HessianServiceImpl server = new HessianServiceImpl();
@@ -83,6 +90,8 @@ public class HessianProtocolTest {
@Test
public void testGenericInvokeWithNativeJava() throws IOException,
ClassNotFoundException {
+ // temporary enable native java generic serialize
+ System.setProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE,
"true");
HessianServiceImpl server = new HessianServiceImpl();
Assert.assertFalse(server.isCalled());
ProxyFactory proxyFactory =
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
@@ -106,6 +115,7 @@ public class HessianProtocolTest {
Assert.assertEquals("Hello, haha", objectInput.readObject());
invoker.destroy();
exporter.unexport();
+ System.clearProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE);
}
@Test
diff --git
a/dubbo-rpc/dubbo-rpc-http/src/test/java/com/alibaba/dubbo/rpc/protocol/http/HttpProtocolTest.java
b/dubbo-rpc/dubbo-rpc-http/src/test/java/com/alibaba/dubbo/rpc/protocol/http/HttpProtocolTest.java
index 014eed6..e05fb9e 100644
---
a/dubbo-rpc/dubbo-rpc-http/src/test/java/com/alibaba/dubbo/rpc/protocol/http/HttpProtocolTest.java
+++
b/dubbo-rpc/dubbo-rpc-http/src/test/java/com/alibaba/dubbo/rpc/protocol/http/HttpProtocolTest.java
@@ -16,6 +16,7 @@
*/
package com.alibaba.dubbo.rpc.protocol.http;
+import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.beanutil.JavaBeanDescriptor;
import com.alibaba.dubbo.common.beanutil.JavaBeanSerializeUtil;
@@ -27,6 +28,7 @@ import
com.alibaba.dubbo.common.serialize.nativejava.NativeJavaSerialization;
import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.service.GenericService;
import junit.framework.Assert;
+import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
@@ -40,6 +42,11 @@ import static org.junit.Assert.fail;
*/
public class HttpProtocolTest {
+ @Before
+ public void setup() {
+ System.setProperty(Constants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE,
"true");
+ }
+
@Test
public void testHttpProtocol() {
HttpServiceImpl server = new HttpServiceImpl();