Registration is now invite-only. Any user can make an invite, you need to create it here and give resulting link to someone to register.

Last commit

avatar
slava86 has added 44a0460d30
Security fixed

Files in

100644 | 343 lines (339 sloc) | 12 KB
From: Markus Koschany <apo@debian.org>
Date: Mon, 31 Jan 2022 11:37:03 +0100
Subject: CVE-2022-23307

Origin: https://github.com/qos-ch/reload4j/commit/64902fe18ce5a5dd40487051a2f6231d9fbbe9b0
---
 .../org/apache/log4j/chainsaw/LoggingReceiver.java |  8 +-
 .../log4j/net/HardenedLoggingEventInputStream.java | 56 +++++++++++++
 .../log4j/net/HardenedObjectInputStream.java       | 93 +++++++++++++++++++++
 src/test/input/xml/socketAppenderForChainsaw.xml   | 24 ++++++
 .../org/apache/log4j/net/SocketAppenderTest.java   | 96 ++++++++++++++++++++++
 5 files changed, 273 insertions(+), 4 deletions(-)
 create mode 100644 src/main/java/org/apache/log4j/net/HardenedLoggingEventInputStream.java
 create mode 100644 src/main/java/org/apache/log4j/net/HardenedObjectInputStream.java
 create mode 100644 src/test/input/xml/socketAppenderForChainsaw.xml
 create mode 100644 src/test/java/org/apache/log4j/net/SocketAppenderTest.java

diff --git a/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java b/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
index ca087ad..74ecc06 100644
--- a/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
+++ b/src/main/java/org/apache/log4j/chainsaw/LoggingReceiver.java
@@ -18,11 +18,11 @@ package org.apache.log4j.chainsaw;
 
 import java.io.EOFException;
 import java.io.IOException;
-import java.io.ObjectInputStream;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.SocketException;
 import org.apache.log4j.Logger;
+import org.apache.log4j.net.HardenedLoggingEventInputStream;
 import org.apache.log4j.spi.LoggingEvent;
 
 /**
@@ -58,10 +58,10 @@ class LoggingReceiver extends Thread {
         public void run() {
             LOG.debug("Starting to get data");
             try {
-                final ObjectInputStream ois =
-                    new ObjectInputStream(mClient.getInputStream());
+                final HardenedLoggingEventInputStream hleis =
+                    new HardenedLoggingEventInputStream(mClient.getInputStream());
                 while (true) {
-                    final LoggingEvent event = (LoggingEvent) ois.readObject();
+                    final LoggingEvent event = (LoggingEvent) hleis.readObject();
                     mModel.addEvent(new EventDetails(event));
                 }
             } catch (EOFException e) {
diff --git a/src/main/java/org/apache/log4j/net/HardenedLoggingEventInputStream.java b/src/main/java/org/apache/log4j/net/HardenedLoggingEventInputStream.java
new file mode 100644
index 0000000..f0f6a20
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/HardenedLoggingEventInputStream.java
@@ -0,0 +1,56 @@
+/*
+ * 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.log4j.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Priority;
+import org.apache.log4j.spi.LocationInfo;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.ThrowableInformation;
+
+// === Copied from the logback project with permission ==
+public class HardenedLoggingEventInputStream extends HardenedObjectInputStream {
+
+    static final String ARRAY_PREFIX = "[L";
+
+    static public List<String> getWhilelist() {
+
+	List<String> whitelist = new ArrayList<String>();
+	whitelist.add(LoggingEvent.class.getName());
+	whitelist.add(Level.class.getName());
+	whitelist.add(Priority.class.getName());
+	whitelist.add(ThrowableInformation.class.getName());
+	whitelist.add(LocationInfo.class.getName());
+
+	return whitelist;
+    }
+
+    public HardenedLoggingEventInputStream(InputStream is) throws IOException {
+	super(is, getWhilelist());
+    }
+
+    public HardenedLoggingEventInputStream(InputStream is, List<String> additionalAuthorizedClasses)
+	    throws IOException {
+	this(is);
+	super.addToWhitelist(additionalAuthorizedClasses);
+    }
+}
diff --git a/src/main/java/org/apache/log4j/net/HardenedObjectInputStream.java b/src/main/java/org/apache/log4j/net/HardenedObjectInputStream.java
new file mode 100644
index 0000000..c911572
--- /dev/null
+++ b/src/main/java/org/apache/log4j/net/HardenedObjectInputStream.java
@@ -0,0 +1,93 @@
+/*
+ * 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.log4j.net;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HardenedObjectInputStream restricts the set of classes that can be
+ * deserialized to a set of explicitly whitelisted classes. This prevents
+ * certain type of attacks from being successful.
+ * 
+ * <p>
+ * It is assumed that classes in the "java.lang" and "java.util" packages are
+ * always authorized.
+ * </p>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @since 1.2.18
+ * 
+ *        === Copied from the logback project with permission ==
+ */
+public class HardenedObjectInputStream extends ObjectInputStream {
+
+    static final String ARRAY_CLASS_PREFIX = "[L";
+    final List<String> whitelistedClassNames;
+    final static String[] JAVA_PACKAGES = new String[] { "java.lang", "java.util", ARRAY_CLASS_PREFIX + "java.lang" };
+
+    public HardenedObjectInputStream(InputStream in, String[] whilelist) throws IOException {
+	super(in);
+
+	this.whitelistedClassNames = new ArrayList<String>();
+	if (whilelist != null) {
+	    for (int i = 0; i < whilelist.length; i++) {
+		this.whitelistedClassNames.add(whilelist[i]);
+	    }
+	}
+    }
+
+    public HardenedObjectInputStream(InputStream in, List<String> whitelist) throws IOException {
+	super(in);
+
+	this.whitelistedClassNames = new ArrayList<String>();
+	this.whitelistedClassNames.addAll(whitelist);
+    }
+
+    @Override
+    protected Class<?> resolveClass(ObjectStreamClass anObjectStreamClass) throws IOException, ClassNotFoundException {
+
+	String incomingClassName = anObjectStreamClass.getName();
+
+	if (!isWhitelisted(incomingClassName)) {
+	    throw new InvalidClassException("Unauthorized deserialization attempt", incomingClassName);
+	}
+
+	return super.resolveClass(anObjectStreamClass);
+    }
+
+    private boolean isWhitelisted(String incomingClassName) {
+	for (int i = 0; i < JAVA_PACKAGES.length; i++) {
+	    if (incomingClassName.startsWith(JAVA_PACKAGES[i]))
+		return true;
+	}
+	for (String whiteListed : whitelistedClassNames) {
+	    if (incomingClassName.equals(whiteListed))
+		return true;
+	}
+	return false;
+    }
+
+    protected void addToWhitelist(List<String> additionalAuthorizedClasses) {
+	whitelistedClassNames.addAll(additionalAuthorizedClasses);
+    }
+}
\ No newline at end of file
diff --git a/src/test/input/xml/socketAppenderForChainsaw.xml b/src/test/input/xml/socketAppenderForChainsaw.xml
new file mode 100644
index 0000000..b022ba5
--- /dev/null
+++ b/src/test/input/xml/socketAppenderForChainsaw.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration debug="true" 
+xmlns:log4j='http://jakarta.apache.org/log4j/'>
+
+
+  <!-- primary appender -->
+  <appender name="REMOTE" class="org.apache.log4j.net.SocketAppender">
+    
+    <param name="RemoteHost" value="localhost"/>
+    <param name="Port" value="4445"/>
+    <param name="ReconnectionDelay" value="10"/>
+  </appender>
+
+  <root>
+    <priority value ="trace" />
+    <appender-ref ref="REMOTE" />
+  </root>
+
+        
+</log4j:configuration>
+
+ 
diff --git a/src/test/java/org/apache/log4j/net/SocketAppenderTest.java b/src/test/java/org/apache/log4j/net/SocketAppenderTest.java
new file mode 100644
index 0000000..6c4a390
--- /dev/null
+++ b/src/test/java/org/apache/log4j/net/SocketAppenderTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.log4j.net;
+
+import static org.apache.log4j.TestContants.TEST_INPUT_PREFIX;
+import static org.junit.Assert.assertEquals;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Logger;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SocketAppenderTest {
+
+    /* JUnit's setUp and tearDown */
+
+    @Before
+    public void setUp() {
+	DOMConfigurator.configure(TEST_INPUT_PREFIX + "xml/SocketAppenderTestConfig.xml");
+	// DOMConfigurator.configure(TEST_INPUT_PREFIX +
+	// "xml/socketAppenderForChainsaw.xml");
+
+	logger = Logger.getLogger(SocketAppenderTest.class);
+	secondary = (LastOnlyAppender) Logger.getLogger("org.apache.log4j.net.SocketAppenderTestDummy")
+		.getAppender("lastOnly");
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    /* Tests */
+
+    @Test
+    public void testFallbackErrorHandlerWhenStarting() {
+	String msg = "testFallbackErrorHandlerWhenStarting";
+	logger.debug(msg);
+
+	// above debug log will fail and shoul be redirected to secondary appender
+	assertEquals("SocketAppender with FallbackErrorHandler", msg, secondary.getLastMessage());
+    }
+
+    /* Fields */
+
+    private static Logger logger;
+    private static LastOnlyAppender secondary;
+
+    /* Inner classes */
+
+    /**
+     * Inner-class For debugging purposes only Saves last LoggerEvent
+     */
+    static public class LastOnlyAppender extends AppenderSkeleton {
+	protected void append(LoggingEvent event) {
+	    this.lastEvent = event;
+	}
+
+	public boolean requiresLayout() {
+	    return false;
+	}
+
+	public void close() {
+	    this.closed = true;
+	}
+
+	/**
+	 * @return last appended LoggingEvent's message
+	 */
+	public String getLastMessage() {
+	    if (this.lastEvent != null)
+		return this.lastEvent.getMessage().toString();
+	    else
+		return "";
+	}
+
+	private LoggingEvent lastEvent;
+    };
+
+}
\ No newline at end of file