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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
Description: CVE-2017-5645: When using the TCP socket server or UDP socket
server to receive serialized log events from another application,
a specially crafted binary payload can be sent that, when deserialized,
can execute arbitrary code.
.
This patch adds class filtering to AbstractSocketServer. This allows
a whitelist of class names to be specified to configure which classes
are allowed to be deserialized in both TcpSocketServer and UdpSocketServer.
Origin: backport, https://github.com/apache/logging-log4j2/commit/5dcc192
Bug: https://issues.apache.org/jira/browse/LOG4J2-1863
Bug-Debian: https://bugs.debian.org/860489
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/AbstractSocketServer.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/AbstractSocketServer.java
@@ -25,6 +25,8 @@
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import org.apache.logging.log4j.LogManager;
@@ -71,6 +73,9 @@
"-a" }, converter = InetAddressConverter.class, description = "Server socket local bind address.")
private InetAddress localBindAddress;
+ @Parameter(names = {"--classes", "-C"}, description = "Additional classes to allow deserialization")
+ private List<String> allowedClasses;
+
String getConfigLocation() {
return configLocation;
}
@@ -102,6 +107,14 @@
void setLocalBindAddress(InetAddress localBindAddress) {
this.localBindAddress = localBindAddress;
}
+
+ List<String> getAllowedClasses() {
+ return allowedClasses == null ? Collections.<String>emptyList() : allowedClasses;
+ }
+
+ void setAllowedClasses(final List<String> allowedClasses) {
+ this.allowedClasses = allowedClasses;
+ }
}
/**
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/ObjectInputStreamLogEventBridge.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/ObjectInputStreamLogEventBridge.java
@@ -19,15 +19,34 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
+import java.util.Collections;
+import java.util.List;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LogEventListener;
+import org.apache.logging.log4j.core.util.FilteredObjectInputStream;
/**
* Reads and logs serialized {@link LogEvent} objects from an {@link ObjectInputStream}.
*/
public class ObjectInputStreamLogEventBridge extends AbstractLogEventBridge<ObjectInputStream> {
+ private final List<String> allowedClasses;
+
+ public ObjectInputStreamLogEventBridge() {
+ this(Collections.<String>emptyList());
+ }
+
+ /**
+ * Constructs an ObjectInputStreamLogEventBridge with additional allowed classes to deserialize.
+ *
+ * @param allowedClasses class names to also allow for deserialization
+ * @since 2.8.2
+ */
+ public ObjectInputStreamLogEventBridge(final List<String> allowedClasses) {
+ this.allowedClasses = allowedClasses;
+ }
+
@Override
public void logEvents(final ObjectInputStream inputStream, final LogEventListener logEventListener)
throws IOException {
@@ -40,6 +59,6 @@
@Override
public ObjectInputStream wrapStream(final InputStream inputStream) throws IOException {
- return new ObjectInputStream(inputStream);
+ return new FilteredObjectInputStream(inputStream, allowedClasses);
}
}
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/TcpSocketServer.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/TcpSocketServer.java
@@ -26,6 +26,8 @@
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.util.Collections;
+import java.util.List;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -154,9 +156,26 @@
*/
public static TcpSocketServer<ObjectInputStream> createSerializedSocketServer(final int port, final int backlog,
InetAddress localBindAddress) throws IOException {
+ return createSerializedSocketServer(port, backlog, localBindAddress, Collections.<String>emptyList());
+ }
+
+ /**
+ * Creates a socket server that reads serialized log events.
+ *
+ * @param port the port to listen
+ * @param localBindAddress The server socket's local bin address
+ * @param allowedClasses additional class names to allow for deserialization
+ * @return a new a socket server
+ * @throws IOException
+ * if an I/O error occurs when opening the socket.
+ * @since 2.8.2
+ */
+ public static TcpSocketServer<ObjectInputStream> createSerializedSocketServer(
+ final int port, final int backlog, final InetAddress localBindAddress, final List<String> allowedClasses
+ ) throws IOException {
LOGGER.entry(port);
final TcpSocketServer<ObjectInputStream> socketServer = new TcpSocketServer<>(port, backlog, localBindAddress,
- new ObjectInputStreamLogEventBridge());
+ new ObjectInputStreamLogEventBridge(allowedClasses));
return LOGGER.exit(socketServer);
}
@@ -191,8 +210,8 @@
if (cla.getConfigLocation() != null) {
ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation()));
}
- final TcpSocketServer<ObjectInputStream> socketServer = TcpSocketServer
- .createSerializedSocketServer(cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress());
+ final TcpSocketServer<ObjectInputStream> socketServer = TcpSocketServer.createSerializedSocketServer(
+ cla.getPort(), cla.getBacklog(), cla.getLocalBindAddress(), cla.getAllowedClasses());
final Thread serverThread = new Log4jThread(socketServer);
serverThread.start();
if (cla.isInteractive()) {
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/UdpSocketServer.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/server/UdpSocketServer.java
@@ -26,6 +26,7 @@
import java.io.OptionalDataException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
+import java.util.List;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.util.Log4jThread;
@@ -66,6 +67,21 @@
}
/**
+ * Creates a socket server that reads serialized log events.
+ *
+ * @param port the port to listen
+ * @param allowedClasses additional classes to allow for deserialization
+ * @return a new a socket server
+ * @throws IOException if an I/O error occurs when opening the socket.
+ * @since 2.8.2
+ */
+ public static UdpSocketServer<ObjectInputStream> createSerializedSocketServer(final int port,
+ final List<String> allowedClasses)
+ throws IOException {
+ return new UdpSocketServer<>(port, new ObjectInputStreamLogEventBridge(allowedClasses));
+ }
+
+ /**
* Creates a socket server that reads XML log events.
*
* @param port
@@ -95,7 +111,7 @@
ConfigurationFactory.setConfigurationFactory(new ServerConfigurationFactory(cla.getConfigLocation()));
}
final UdpSocketServer<ObjectInputStream> socketServer = UdpSocketServer
- .createSerializedSocketServer(cla.getPort());
+ .createSerializedSocketServer(cla.getPort(), cla.getAllowedClasses());
final Thread serverThread = new Log4jThread(socketServer);
serverThread.start();
if (cla.isInteractive()) {
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FilteredObjectInputStream.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.apache.logging.log4j.core.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Extended ObjectInputStream that only allows certain classes to be deserialized.
+ *
+ * @since 2.8.2
+ */
+public class FilteredObjectInputStream extends ObjectInputStream {
+
+ private static final List<String> REQUIRED_JAVA_CLASSES = Arrays.asList(
+ // for StandardLevel
+ "java.lang.Enum",
+ // for location information
+ "java.lang.StackTraceElement",
+ // for Message delegate
+ "java.rmi.MarshalledObject",
+ "[B"
+ );
+
+ private final Collection<String> allowedClasses;
+
+ public FilteredObjectInputStream(final InputStream in, final Collection<String> allowedClasses) throws IOException {
+ super(in);
+ this.allowedClasses = allowedClasses;
+ }
+
+ @Override
+ protected Class<?> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+ String name = desc.getName();
+ if (!(isAllowedByDefault(name) || allowedClasses.contains(name))) {
+ throw new InvalidObjectException("Class is not allowed for deserialization: " + name);
+ }
+ return super.resolveClass(desc);
+ }
+
+ private static boolean isAllowedByDefault(final String name) {
+ return name.startsWith("org.apache.logging.log4j.") ||
+ name.startsWith("[Lorg.apache.logging.log4j.") ||
+ REQUIRED_JAVA_CLASSES.contains(name);
+ }
+
+}
|