This security patch addresses the following vulnerabilities: CVE-2024-12801 (bsc#1234743) Server-Side Request Forgery (SSRF) in SaxEventRecorder CVE-2024-12798 (bsc#1234742) ACE vulnerability in JaninoEventEvaluator It is based on the following upstream patches: https://github.com/qos-ch/logback/commit/2cb6d520df7592ef1c3a198f1b5df3c10c93e183.patch https://github.com/qos-ch/logback/commit/5f05041cba4c4ac0a62748c5c527a2da48999f2d.patch https://github.com/qos-ch/logback/commit/6ddf91890a4c23e855132c89086ad7e069d81755.patch The change addresses the above issues by: - removing SaxEventRecorder and JaninoEventEvaluator - ignoring external DTD files in DOCTYPE to further reduce vulnerability to SSRF attacks --- a/logback-access/src/main/java/ch/qos/logback/access/boolex/JaninoEventEvaluator.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.access.boolex; - -import java.util.ArrayList; -import java.util.List; - -import ch.qos.logback.access.spi.IAccessEvent; -import ch.qos.logback.core.CoreConstants; -import ch.qos.logback.core.boolex.JaninoEventEvaluatorBase; -import ch.qos.logback.core.boolex.Matcher; - -public class JaninoEventEvaluator extends JaninoEventEvaluatorBase { - - public final static List DEFAULT_PARAM_NAME_LIST = new ArrayList(); - public final static List DEFAULT_PARAM_TYPE_LIST = new ArrayList(); - - static { - DEFAULT_PARAM_NAME_LIST.add("event"); - DEFAULT_PARAM_TYPE_LIST.add(IAccessEvent.class); - } - - @Override - protected String getDecoratedExpression() { - String expression = getExpression(); - if (!expression.contains("return")) { - expression = "return " + expression + ";"; - addInfo("Adding [return] prefix and a semicolon suffix. Expression becomes [" + expression + "]"); - addInfo("See also " + CoreConstants.CODES_URL + "#block"); - } - return expression; - } - - @Override - protected String[] getParameterNames() { - List fullNameList = new ArrayList(); - fullNameList.addAll(DEFAULT_PARAM_NAME_LIST); - - for (int i = 0; i < matcherList.size(); i++) { - Matcher m = (Matcher) matcherList.get(i); - fullNameList.add(m.getName()); - } - - return (String[]) fullNameList.toArray(CoreConstants.EMPTY_STRING_ARRAY); - } - - @Override - protected Class[] getParameterTypes() { - List fullTypeList = new ArrayList(); - fullTypeList.addAll(DEFAULT_PARAM_TYPE_LIST); - for (int i = 0; i < matcherList.size(); i++) { - fullTypeList.add(Matcher.class); - } - return (Class[]) fullTypeList.toArray(CoreConstants.EMPTY_CLASS_ARRAY); - } - - @Override - protected Object[] getParameterValues(IAccessEvent accessEvent) { - final int matcherListSize = matcherList.size(); - - int i = 0; - Object[] values = new Object[DEFAULT_PARAM_NAME_LIST.size() + matcherListSize]; - - values[i++] = accessEvent; - - for (int j = 0; j < matcherListSize; j++) { - values[i++] = matcherList.get(j); - } - - return values; - } - -} --- a/logback-classic/src/main/java/ch/qos/logback/classic/boolex/JaninoEventEvaluator.java +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.classic.boolex; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.slf4j.Marker; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.IThrowableProxy; -import ch.qos.logback.classic.spi.LoggerContextVO; -import ch.qos.logback.classic.spi.ThrowableProxy; -import ch.qos.logback.core.CoreConstants; -import ch.qos.logback.core.boolex.JaninoEventEvaluatorBase; -import ch.qos.logback.core.boolex.Matcher; - -public class JaninoEventEvaluator extends JaninoEventEvaluatorBase { - - public final static String IMPORT_LEVEL = "import ch.qos.logback.classic.Level;\r\n"; - - public final static List DEFAULT_PARAM_NAME_LIST = new ArrayList(); - public final static List DEFAULT_PARAM_TYPE_LIST = new ArrayList(); - - static { - DEFAULT_PARAM_NAME_LIST.add("DEBUG"); - DEFAULT_PARAM_NAME_LIST.add("INFO"); - DEFAULT_PARAM_NAME_LIST.add("WARN"); - DEFAULT_PARAM_NAME_LIST.add("ERROR"); - - DEFAULT_PARAM_NAME_LIST.add("event"); - DEFAULT_PARAM_NAME_LIST.add("message"); - - DEFAULT_PARAM_NAME_LIST.add("formattedMessage"); - DEFAULT_PARAM_NAME_LIST.add("logger"); - DEFAULT_PARAM_NAME_LIST.add("loggerContext"); - DEFAULT_PARAM_NAME_LIST.add("level"); - DEFAULT_PARAM_NAME_LIST.add("timeStamp"); - DEFAULT_PARAM_NAME_LIST.add("marker"); - DEFAULT_PARAM_NAME_LIST.add("mdc"); - DEFAULT_PARAM_NAME_LIST.add("throwableProxy"); - DEFAULT_PARAM_NAME_LIST.add("throwable"); - - DEFAULT_PARAM_TYPE_LIST.add(int.class); - DEFAULT_PARAM_TYPE_LIST.add(int.class); - DEFAULT_PARAM_TYPE_LIST.add(int.class); - DEFAULT_PARAM_TYPE_LIST.add(int.class); - - DEFAULT_PARAM_TYPE_LIST.add(ILoggingEvent.class); - DEFAULT_PARAM_TYPE_LIST.add(String.class); - DEFAULT_PARAM_TYPE_LIST.add(String.class); - DEFAULT_PARAM_TYPE_LIST.add(String.class); - DEFAULT_PARAM_TYPE_LIST.add(LoggerContextVO.class); - DEFAULT_PARAM_TYPE_LIST.add(int.class); - DEFAULT_PARAM_TYPE_LIST.add(long.class); - DEFAULT_PARAM_TYPE_LIST.add(Marker.class); - DEFAULT_PARAM_TYPE_LIST.add(Map.class); - DEFAULT_PARAM_TYPE_LIST.add(IThrowableProxy.class); - DEFAULT_PARAM_TYPE_LIST.add(Throwable.class); - } - - protected String getDecoratedExpression() { - String expression = getExpression(); - if (!expression.contains("return")) { - expression = "return " + expression + ";"; - addInfo("Adding [return] prefix and a semicolon suffix. Expression becomes [" + expression + "]"); - addInfo("See also " + CoreConstants.CODES_URL + "#block"); - - } - return IMPORT_LEVEL + expression; - } - - protected String[] getParameterNames() { - List fullNameList = new ArrayList(); - fullNameList.addAll(DEFAULT_PARAM_NAME_LIST); - - for (int i = 0; i < matcherList.size(); i++) { - Matcher m = (Matcher) matcherList.get(i); - fullNameList.add(m.getName()); - } - - return (String[]) fullNameList.toArray(CoreConstants.EMPTY_STRING_ARRAY); - } - - protected Class[] getParameterTypes() { - List fullTypeList = new ArrayList(); - fullTypeList.addAll(DEFAULT_PARAM_TYPE_LIST); - for (int i = 0; i < matcherList.size(); i++) { - fullTypeList.add(Matcher.class); - } - return (Class[]) fullTypeList.toArray(CoreConstants.EMPTY_CLASS_ARRAY); - } - - protected Object[] getParameterValues(ILoggingEvent loggingEvent) { - final int matcherListSize = matcherList.size(); - - int i = 0; - Object[] values = new Object[DEFAULT_PARAM_NAME_LIST.size() + matcherListSize]; - - values[i++] = Level.DEBUG_INTEGER; - values[i++] = Level.INFO_INTEGER; - values[i++] = Level.WARN_INTEGER; - values[i++] = Level.ERROR_INTEGER; - - values[i++] = loggingEvent; - values[i++] = loggingEvent.getMessage(); - values[i++] = loggingEvent.getFormattedMessage(); - values[i++] = loggingEvent.getLoggerName(); - values[i++] = loggingEvent.getLoggerContextVO(); - values[i++] = loggingEvent.getLevel().toInteger(); - values[i++] = loggingEvent.getTimeStamp(); - // In order to avoid NullPointerException, we could push a dummy marker if - // the event's marker is null. However, this would surprise user who - // expect to see a null marker instead of a dummy one. - values[i++] = loggingEvent.getMarker(); - values[i++] = loggingEvent.getMDCPropertyMap(); - - IThrowableProxy iThrowableProxy = loggingEvent.getThrowableProxy(); - - if (iThrowableProxy != null) { - values[i++] = iThrowableProxy; - if (iThrowableProxy instanceof ThrowableProxy) { - values[i++] = ((ThrowableProxy) iThrowableProxy).getThrowable(); - } else { - values[i++] = null; - } - } else { - values[i++] = null; - values[i++] = null; - } - - for (int j = 0; j < matcherListSize; j++) { - values[i++] = (Matcher) matcherList.get(j); - } - - return values; - } - -} --- a/logback-classic/src/main/java/ch/qos/logback/classic/util/DefaultNestedComponentRules.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/util/DefaultNestedComponentRules.java @@ -14,7 +14,6 @@ package ch.qos.logback.classic.util; import ch.qos.logback.classic.PatternLayout; -import ch.qos.logback.classic.boolex.JaninoEventEvaluator; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.core.AppenderBase; import ch.qos.logback.core.UnsynchronizedAppenderBase; @@ -38,8 +37,6 @@ public class DefaultNestedComponentRules registry.add(AppenderBase.class, "encoder", PatternLayoutEncoder.class); registry.add(UnsynchronizedAppenderBase.class, "encoder", PatternLayoutEncoder.class); - registry.add(EvaluatorFilter.class, "evaluator", JaninoEventEvaluator.class); - SSLNestedComponentRegistryRules.addDefaultNestedComponentRegistryRules(registry); } --- /dev/null +++ b/logback-core-blackbox/src/test/blackboxInput/joran/conditional/ifWithExec.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + --- a/logback-core/src/main/java/ch/qos/logback/core/boolex/JaninoEventEvaluatorBase.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.core.boolex; - -import java.util.ArrayList; -import java.util.List; - -import org.codehaus.janino.ScriptEvaluator; - -/** - * Abstract class which sets the groundwork for janino based evaluations. - * - * @author Ceki Gülcü - * - * @param - */ -abstract public class JaninoEventEvaluatorBase extends EventEvaluatorBase { - - static Class EXPRESSION_TYPE = boolean.class; - static Class[] THROWN_EXCEPTIONS = new Class[1]; - - static public final int ERROR_THRESHOLD = 4; - static { - THROWN_EXCEPTIONS[0] = EvaluationException.class; - } - - private String expression; - - ScriptEvaluator scriptEvaluator; - private int errorCount = 0; - - abstract protected String getDecoratedExpression(); - - abstract protected String[] getParameterNames(); - - abstract protected Class[] getParameterTypes(); - - abstract protected Object[] getParameterValues(E event); - - protected List matcherList = new ArrayList(); - - @Override - public void start() { - try { - assert context != null; - scriptEvaluator = new ScriptEvaluator(getDecoratedExpression(), EXPRESSION_TYPE, getParameterNames(), getParameterTypes(), THROWN_EXCEPTIONS); - super.start(); - } catch (Exception e) { - addError("Could not start evaluator with expression [" + expression + "]", e); - } - } - - public boolean evaluate(E event) throws EvaluationException { - if (!isStarted()) { - throw new IllegalStateException("Evaluator [" + name + "] was called in stopped state"); - } - try { - Boolean result = (Boolean) scriptEvaluator.evaluate(getParameterValues(event)); - return result.booleanValue(); - } catch (Exception ex) { - errorCount++; - if (errorCount >= ERROR_THRESHOLD) { - stop(); - } - throw new EvaluationException("Evaluator [" + name + "] caused an exception", ex); - } - } - - public String getExpression() { - return expression; - } - - public void setExpression(String expression) { - this.expression = expression; - } - - public void addMatcher(Matcher matcher) { - matcherList.add(matcher); - } - - public List getMatcherList() { - return matcherList; - } -} --- a/logback-core/src/main/java/ch/qos/logback/core/joran/event/SaxEventRecorder.java +++ b/logback-core/src/main/java/ch/qos/logback/core/joran/event/SaxEventRecorder.java @@ -15,6 +15,7 @@ package ch.qos.logback.core.joran.event; import static ch.qos.logback.core.CoreConstants.XML_PARSING; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -45,6 +46,26 @@ public class SaxEventRecorder extends De cai = new ContextAwareImpl(context, this); } + /** + * An implementation which disallows external DTDs + * + * @param publicId The public identifier, or null if none is + * available. + * @param systemId The system identifier provided in the XML + * document. + * @return + * @throws SAXException + * @throws IOException + * @since 1.5.13 + */ + @Override + public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { + addWarn("Document Type Declaration (DOCTYPE) with external file reference is"); + addWarn("disallowed to prevent Server-Side Request Forgery (SSRF) attacks."); + addWarn("returning contents of SYSTEM " +systemId+ " as a white space"); + return new InputSource(new ByteArrayInputStream(" ".getBytes())); + } + public List saxEventList = new ArrayList(); Locator locator; ElementPath globalElementPath = new ElementPath(); @@ -102,7 +123,6 @@ public class SaxEventRecorder extends De } public void startElement(String namespaceURI, String localName, String qName, Attributes atts) { - String tagName = getTagName(localName, qName); globalElementPath.push(tagName); ElementPath current = globalElementPath.duplicate(); --- a/logback-core/src/main/java/ch/qos/logback/core/joran/event/stax/StaxEventRecorder.java +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.core.joran.event.stax; - -import ch.qos.logback.core.Context; -import ch.qos.logback.core.joran.spi.ElementPath; -import ch.qos.logback.core.joran.spi.JoranException; -import ch.qos.logback.core.spi.ContextAwareBase; - -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Characters; -import javax.xml.stream.events.EndElement; -import javax.xml.stream.events.StartElement; -import javax.xml.stream.events.XMLEvent; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -public class StaxEventRecorder extends ContextAwareBase { - - List eventList = new ArrayList(); - ElementPath globalElementPath = new ElementPath(); - - public StaxEventRecorder(Context context) { - setContext(context); - } - - public void recordEvents(InputStream inputStream) throws JoranException { - try { - XMLEventReader xmlEventReader = XMLInputFactory.newInstance().createXMLEventReader(inputStream); - read(xmlEventReader); - } catch (XMLStreamException e) { - throw new JoranException("Problem parsing XML document. See previously reported errors.", e); - } - } - - public List getEventList() { - return eventList; - } - - private void read(XMLEventReader xmlEventReader) throws XMLStreamException { - while (xmlEventReader.hasNext()) { - XMLEvent xmlEvent = xmlEventReader.nextEvent(); - switch (xmlEvent.getEventType()) { - case XMLEvent.START_ELEMENT: - addStartElement(xmlEvent); - break; - case XMLEvent.CHARACTERS: - addCharacters(xmlEvent); - break; - case XMLEvent.END_ELEMENT: - addEndEvent(xmlEvent); - break; - default: - break; - } - } - } - - private void addStartElement(XMLEvent xmlEvent) { - StartElement se = xmlEvent.asStartElement(); - String tagName = se.getName().getLocalPart(); - globalElementPath.push(tagName); - ElementPath current = globalElementPath.duplicate(); - StartEvent startEvent = new StartEvent(current, tagName, se.getAttributes(), se.getLocation()); - eventList.add(startEvent); - } - - private void addCharacters(XMLEvent xmlEvent) { - Characters characters = xmlEvent.asCharacters(); - StaxEvent lastEvent = getLastEvent(); - - if (lastEvent instanceof BodyEvent) { - BodyEvent be = (BodyEvent) lastEvent; - be.append(characters.getData()); - } else { - // ignore space only text if the previous event is not a BodyEvent - if (!characters.isWhiteSpace()) { - BodyEvent bodyEvent = new BodyEvent(characters.getData(), xmlEvent.getLocation()); - eventList.add(bodyEvent); - } - } - } - - private void addEndEvent(XMLEvent xmlEvent) { - EndElement ee = xmlEvent.asEndElement(); - String tagName = ee.getName().getLocalPart(); - EndEvent endEvent = new EndEvent(tagName, ee.getLocation()); - eventList.add(endEvent); - globalElementPath.pop(); - } - - StaxEvent getLastEvent() { - if (eventList.isEmpty()) { - return null; - } - int size = eventList.size(); - if (size == 0) - return null; - return eventList.get(size - 1); - } - -} --- a/logback-core/src/main/java/ch/qos/logback/core/net/ssl/SSLParametersConfiguration.java +++ b/logback-core/src/main/java/ch/qos/logback/core/net/ssl/SSLParametersConfiguration.java @@ -19,8 +19,6 @@ import java.util.List; import javax.net.ssl.SSLEngine; -import org.codehaus.janino.Java; - import ch.qos.logback.core.spi.ContextAwareBase; import ch.qos.logback.core.util.OptionHelper; import ch.qos.logback.core.util.StringCollectionUtil; --- /dev/null +++ b/logback-core/src/test/input/joran/event-ssrf.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + XXX& + + \ No newline at end of file --- a/logback-classic/src/main/java/ch/qos/logback/classic/joran/action/EvaluatorAction.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2015, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.classic.joran.action; - -import ch.qos.logback.classic.boolex.JaninoEventEvaluator; -import ch.qos.logback.core.joran.action.AbstractEventEvaluatorAction; - -public class EvaluatorAction extends AbstractEventEvaluatorAction { - protected String defaultClassName() { - return JaninoEventEvaluator.class.getName(); - } -} --- a/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/joran/JoranConfigurator.java @@ -46,7 +46,6 @@ public class JoranConfigurator extends J rs.addRule(new ElementSelector("configuration/contextName"), new ContextNameAction()); rs.addRule(new ElementSelector("configuration/contextListener"), new LoggerContextListenerAction()); rs.addRule(new ElementSelector("configuration/insertFromJNDI"), new InsertFromJNDIAction()); - rs.addRule(new ElementSelector("configuration/evaluator"), new EvaluatorAction()); rs.addRule(new ElementSelector("configuration/appender/sift"), new SiftAction()); rs.addRule(new ElementSelector("configuration/appender/sift/*"), new NOPAction()); --- a/logback-access/src/main/java/ch/qos/logback/access/joran/JoranConfigurator.java +++ b/logback-access/src/main/java/ch/qos/logback/access/joran/JoranConfigurator.java @@ -15,7 +15,6 @@ package ch.qos.logback.access.joran; import ch.qos.logback.access.PatternLayout; import ch.qos.logback.access.PatternLayoutEncoder; -import ch.qos.logback.access.boolex.JaninoEventEvaluator; import ch.qos.logback.access.joran.action.ConfigurationAction; import ch.qos.logback.access.joran.action.EvaluatorAction; import ch.qos.logback.access.sift.SiftAction; @@ -67,7 +66,6 @@ public class JoranConfigurator extends J @Override protected void addDefaultNestedComponentRegistryRules(DefaultNestedComponentRegistry registry) { registry.add(AppenderBase.class, "layout", PatternLayout.class); - registry.add(EvaluatorFilter.class, "evaluator", JaninoEventEvaluator.class); registry.add(AppenderBase.class, "encoder", PatternLayoutEncoder.class); registry.add(UnsynchronizedAppenderBase.class, "encoder", PatternLayoutEncoder.class); --- a/logback-access/src/main/java/ch/qos/logback/access/joran/action/EvaluatorAction.java +++ b/logback-access/src/main/java/ch/qos/logback/access/joran/action/EvaluatorAction.java @@ -13,13 +13,12 @@ */ package ch.qos.logback.access.joran.action; -import ch.qos.logback.access.boolex.JaninoEventEvaluator; import ch.qos.logback.core.joran.action.AbstractEventEvaluatorAction; public class EvaluatorAction extends AbstractEventEvaluatorAction { @Override protected String defaultClassName() { - return JaninoEventEvaluator.class.getName(); + return AbstractEventEvaluatorAction.class.getName(); } }