Skip to content

Commit 20cd962

Browse files
authored
security: fix REC security vulnerability during Raft deserialization (#6702)
1 parent b34cfff commit 20cd962

7 files changed

Lines changed: 95 additions & 10 deletions

File tree

changes/en-us/2.x.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Add changes here for all PR submitted to the 2.x branch.
3636
### refactor:
3737

3838
### security:
39-
39+
- [[#6702](https://github.com/apache/incubator-seata/pull/6702)] fix REC security vulnerability during Raft deserialization
4040

4141
### test:
4242
- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] add unit test for sql-parser-core

changes/zh-cn/2.x.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939

4040
### security:
41+
- [[#6702](https://github.com/apache/incubator-seata/pull/6702)] 修复Raft反序列化时存在RCE安全漏洞
4142

4243
### test:
4344
- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] 添加sql-parser-core模块测试用例

common/src/main/java/org/apache/seata/common/exception/ErrorCode.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ public enum ErrorCode {
2424
/**
2525
* 0001 ~ 0099 Configuration related errors
2626
*/
27-
ERR_CONFIG(ErrorType.Config, 0001);
27+
ERR_CONFIG(ErrorType.Config, 0001),
28+
2829
/**
29-
* The error code of the transaction exception.
30+
* 0100 ~ 0199 Security related errors
3031
*/
32+
ERR_DESERIALIZATION_SECURITY(ErrorType.Security, 0156);
3133

34+
/**
35+
* The error code of the transaction exception.
36+
*/
3237
private int code;
3338
private ErrorType type;
3439

@@ -77,6 +82,10 @@ enum ErrorType {
7782
* Network error type.
7883
*/
7984
Network,
85+
/**
86+
* Security related error type.
87+
*/
88+
Security,
8089
/**
8190
* Tm error type.
8291
*/

common/src/main/resources/error/ErrorCode_en.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
#
1717
ERR_PREFIX=ERR-CODE: [Seata-{code}][{key}]
1818
ERR_POSTFIX=More: [https://seata.apache.org/docs/next/overview/faq#{code}]
19-
ERR_CONFIG=config error, {0}
19+
ERR_CONFIG=config error, {0}
20+
ERR_DESERIALIZATION_SECURITY=deserialization security error, {0}

server/src/main/java/org/apache/seata/server/cluster/raft/sync/RaftSyncMessageSerializer.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
import java.io.IOException;
2222
import java.io.ObjectInputStream;
2323
import java.io.ObjectOutputStream;
24+
import java.io.ObjectStreamClass;
25+
import java.util.ArrayList;
26+
import java.util.List;
2427
import java.util.Optional;
28+
import org.apache.seata.common.exception.ErrorCode;
29+
import org.apache.seata.common.exception.SeataRuntimeException;
2530
import org.apache.seata.common.loader.EnhancedServiceLoader;
2631
import org.apache.seata.core.compressor.CompressorFactory;
2732
import org.apache.seata.core.serializer.Serializer;
@@ -36,6 +41,14 @@ public class RaftSyncMessageSerializer {
3641

3742
private static final Logger LOGGER = LoggerFactory.getLogger(RaftSyncMessageSerializer.class);
3843

44+
private static final List<String> PERMITS = new ArrayList<>();
45+
46+
static {
47+
PERMITS.add(RaftSyncMessage.class.getName());
48+
PERMITS.add(io.seata.server.cluster.raft.sync.msg.RaftSyncMessage.class.getName());
49+
PERMITS.add("[B");
50+
}
51+
3952
public static byte[] encode(RaftSyncMessage raftSyncMessage) throws IOException {
4053
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
4154
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
@@ -62,12 +75,22 @@ public static byte[] encode(io.seata.server.cluster.raft.sync.msg.RaftSyncMessag
6275

6376
public static RaftSyncMessage decode(byte[] raftSyncMsgByte) {
6477
try (ByteArrayInputStream bin = new ByteArrayInputStream(raftSyncMsgByte);
65-
ObjectInputStream ois = new ObjectInputStream(bin)) {
78+
ObjectInputStream ois = new ObjectInputStream(bin) {
79+
@Override
80+
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
81+
if (!PERMITS.contains(desc.getName())) {
82+
throw new SeataRuntimeException(ErrorCode.ERR_DESERIALIZATION_SECURITY,
83+
"Failed to deserialize object: " + desc.getName() + " is not permitted");
84+
}
85+
86+
return super.resolveClass(desc);
87+
}
88+
}) {
6689
Object object = ois.readObject();
6790
RaftSyncMessage raftSyncMessage;
6891
if (object instanceof io.seata.server.cluster.raft.sync.msg.RaftSyncMessage) {
6992
io.seata.server.cluster.raft.sync.msg.RaftSyncMessage oldRaftSyncMessage =
70-
(io.seata.server.cluster.raft.sync.msg.RaftSyncMessage)object;
93+
(io.seata.server.cluster.raft.sync.msg.RaftSyncMessage)object;
7194
raftSyncMessage = new RaftSyncMessage();
7295
raftSyncMessage.setCodec(oldRaftSyncMessage.getCodec());
7396
raftSyncMessage.setCompressor(oldRaftSyncMessage.getCompressor());
@@ -77,13 +100,16 @@ public static RaftSyncMessage decode(byte[] raftSyncMsgByte) {
77100
raftSyncMessage = (RaftSyncMessage)object;
78101
}
79102
Serializer serializer = EnhancedServiceLoader.load(Serializer.class,
80-
SerializerType.getByCode(raftSyncMessage.getCodec()).name());
103+
SerializerType.getByCode(raftSyncMessage.getCodec()).name());
81104
Optional.ofNullable(raftSyncMessage.getBody())
82-
.ifPresent(value -> raftSyncMessage.setBody(serializer.deserialize(CompressorFactory
83-
.getCompressor(raftSyncMessage.getCompressor()).decompress((byte[])raftSyncMessage.getBody()))));
105+
.ifPresent(value -> raftSyncMessage.setBody(serializer.deserialize(CompressorFactory
106+
.getCompressor(raftSyncMessage.getCompressor()).decompress((byte[])raftSyncMessage.getBody()))));
84107
return raftSyncMessage;
85-
} catch (ClassNotFoundException | IOException e) {
108+
} catch (Exception e) {
86109
LOGGER.info("Failed to read raft synchronization log: {}", e.getMessage(), e);
110+
if (e instanceof SeataRuntimeException) {
111+
throw (SeataRuntimeException)e;
112+
}
87113
throw new RuntimeException(e);
88114
}
89115
}

server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
*/
1717
package org.apache.seata.server.raft;
1818

19+
import java.io.ByteArrayOutputStream;
1920
import java.io.IOException;
21+
import java.io.ObjectOutputStream;
2022
import java.util.ArrayList;
2123
import java.util.HashMap;
2224
import java.util.List;
2325
import java.util.Map;
2426

27+
import org.apache.seata.common.exception.SeataRuntimeException;
2528
import org.apache.seata.common.metadata.ClusterRole;
2629
import org.apache.seata.common.metadata.Node;
2730
import org.apache.seata.core.exception.TransactionException;
@@ -62,6 +65,19 @@ public static void setUp(ApplicationContext context){
6265
public static void destroy(){
6366
SessionHolder.destroy();
6467
}
68+
69+
@Test
70+
public void testSecurityMsgSerialize() throws IOException {
71+
TestSecurity testSecurity = new TestSecurity();
72+
byte[] bytes;
73+
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
74+
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
75+
oos.writeObject(testSecurity);
76+
bytes = bos.toByteArray();
77+
}
78+
Assertions.assertThrows(SeataRuntimeException.class,()->RaftSyncMessageSerializer.decode(bytes));
79+
}
80+
6581
@Test
6682
public void testMsgSerialize() throws IOException {
6783
RaftSyncMessage raftSyncMessage = new RaftSyncMessage();
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.seata.server.raft;
18+
19+
public class TestSecurity implements java.io.Serializable {
20+
21+
private static final long serialVersionUID = 543214259201495900L;
22+
23+
String a = "test";
24+
25+
public String getA() {
26+
return a;
27+
}
28+
29+
public void setA(String a) {
30+
this.a = a;
31+
}
32+
}

0 commit comments

Comments
 (0)