CVE-2026-1225
Description
ACE vulnerability in configuration file processing by QOS.CH logback-core up to and including version 1.5.24 in Java applications, allows an attacker to instantiate classes already present on the class path by compromising an existing logback configuration file.
The instantiation of a potentially malicious Java class requires that said class is present on the user's class-path. In addition, the attacker must have write access to a configuration file. However, after successful instantiation, the instance is very likely to be discarded with no further ado.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ch.qos.logback:logback-coreMaven | < 1.5.25 | 1.5.25 |
Affected products
1Patches
11f97ae1844b1check for undeclared by referenced appenders
10 files changed · +192 −32
logback-classic/src/main/java/ch/qos/logback/classic/joran/ModelClassToModelHandlerLinker.java+4 −0 modified@@ -63,11 +63,15 @@ public void link(DefaultProcessor defaultProcessor) { defaultProcessor.addAnalyser(LoggerModel.class, () -> new AppenderRefDependencyAnalyser(context)); + // an appender may contain appender refs, e.g. AsyncAppender defaultProcessor.addAnalyser(AppenderModel.class, () -> new AppenderRefDependencyAnalyser(context)); defaultProcessor.addAnalyser(AppenderModel.class, () -> new FileCollisionAnalyser(context)); + + defaultProcessor.addAnalyser(AppenderModel.class, () -> new AppenderDeclarationAnalyser(context)); + sealModelFilters(defaultProcessor); }
logback-classic/src/test/input/joran/appenderRefByPropertyDefault.xml+15 −0 added@@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration> + +<configuration debug="false"> + + <appender name="A" class="ch.qos.logback.core.read.ListAppender"/> + + <logger name="ch.qos.logback.classic.joran" level="DEBUG"> + </logger> + + <root level="ERROR"> + <appender-ref ref="${someUndefinedProp:-A}"/> + </root> + +</configuration>
logback-classic/src/test/input/joran/refToUndefinedAppender.xml+28 −0 added@@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + ~ Logback: the reliable, generic, fast and flexible logging framework. + ~ Copyright (C) 1999-2026, 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. + --> + +<!DOCTYPE configuration> + +<configuration debug="false"> + + <appender name="A" class="ch.qos.logback.core.read.ListAppender"/> + + <root level="DEBUG"> + <appender-ref ref="NON_EXISTENT_APPENDER"/> + <appender-ref ref="A"/> + + </root> + +</configuration>
logback-classic/src/test/java/ch/qos/logback/classic/joran/JoranConfiguratorTest.java+34 −0 modified@@ -185,6 +185,40 @@ public void appenderRefSettingBySystemProperty() throws JoranException { assertEquals(1, listAppender.list.size()); System.clearProperty(propertyName); } + @Test + public void appenderRefSettingBySystemPropertyDefault() throws JoranException { + + configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefByPropertyDefault.xml"); + StatusPrinter.print(loggerContext); + final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran"); + final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A"); + assertNotNull(listAppender); + assertEquals(0, listAppender.list.size()); + final String msg = "hello world"; + logger.info(msg); + + assertEquals(1, listAppender.list.size()); + } + + + @Test + public void refToUndefinedAppender() throws JoranException { + + configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "refToUndefinedAppender.xml"); + StatusPrinter.print(loggerContext); + final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran"); + final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("A"); + assertNotNull(listAppender); + assertEquals(0, listAppender.list.size()); + final String msg = "hello world"; + logger.info(msg); + + assertEquals(1, listAppender.list.size()); + + checker.assertContainsMatch(Status.WARN, "Appender named \\[NON_EXISTENT_APPENDER\\] could not be found. Skipping attachment to Logger\\[ROOT\\]"); + + } + @Test public void statusListener() throws JoranException {
logback-core/src/main/java/ch/qos/logback/core/model/processor/AppenderDeclarationAnalyser.java+76 −0 added@@ -0,0 +1,76 @@ +/* + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2026, 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.model.processor; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.model.AppenderModel; +import ch.qos.logback.core.model.Model; + +import java.util.HashSet; +import java.util.Set; + +/** + * The AppenderAvailabilityAnalyser class is responsible for analyzing the availability + * of appenders. By available, we mean whether an appender with a given name is declared + * somewhere in the configuration. This availability information is later used by + * AppenderRefModelHandler to attempt to attach only those appenders that were previously + * declared. + */ +@PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS) +public class AppenderDeclarationAnalyser extends ModelHandlerBase { + + static final String DECLARED_APPENDER_NAME_SET_KEY = "DECLARED_APPENDER_NAME_SET"; + + + public AppenderDeclarationAnalyser(Context context) { + super(context); + } + + @Override + protected Class<AppenderModel> getSupportedModelClass() { + return AppenderModel.class; + } + + + @Override + public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { + AppenderModel appenderModel = (AppenderModel) model; + + String appenderName = appenderModel.getName(); + addAppenderDeclaration(mic, appenderName); + } + + + static public Set<String> getAppenderNameSet(ModelInterpretationContext mic) { + Set<String> set = (Set<String>) mic.getObjectMap().get(DECLARED_APPENDER_NAME_SET_KEY); + if(set == null) { + set = new HashSet<>(); + mic.getObjectMap().put(DECLARED_APPENDER_NAME_SET_KEY, set); + } + return set; + } + + static public void addAppenderDeclaration(ModelInterpretationContext mic, String appenderName) { + Set<String> set = getAppenderNameSet(mic); + set.add(appenderName); + } + + + static public boolean isAppenderDeclared(ModelInterpretationContext mic, String appenderName) { + Set<String> set = getAppenderNameSet(mic); + return set.contains(appenderName); + } + +}
logback-core/src/main/java/ch/qos/logback/core/model/processor/AppenderRefModelHandler.java+9 −2 modified@@ -1,14 +1,16 @@ package ch.qos.logback.core.model.processor; -import java.util.Map; - import ch.qos.logback.core.Appender; import ch.qos.logback.core.Context; import ch.qos.logback.core.joran.JoranConstants; import ch.qos.logback.core.model.AppenderRefModel; import ch.qos.logback.core.model.Model; import ch.qos.logback.core.spi.AppenderAttachable; +import java.util.Map; + +import static ch.qos.logback.core.model.processor.AppenderDeclarationAnalyser.isAppenderDeclared; + public class AppenderRefModelHandler extends ModelHandlerBase { boolean inError = false; @@ -50,6 +52,11 @@ void attachReferencedAppenders(ModelInterpretationContext mic, AppenderRefModel AppenderAttachable<?> appenderAttachable) { String appenderName = mic.subst(appenderRefModel.getRef()); + if(!isAppenderDeclared(mic, appenderName)) { + addWarn("Appender named [" + appenderName + "] could not be found. Skipping attachment to "+appenderAttachable+"."); + return; + } + Map<String, Appender> appenderBag = (Map<String, Appender>) mic.getObjectMap().get(JoranConstants.APPENDER_BAG); Appender appender = appenderBag.get(appenderName);
logback-core/src/main/java/ch/qos/logback/core/model/processor/DefaultProcessor.java+9 −7 modified@@ -13,15 +13,12 @@ */ package ch.qos.logback.core.model.processor; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.function.Supplier; import ch.qos.logback.core.Context; -import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; import ch.qos.logback.core.model.Model; import ch.qos.logback.core.model.ModelHandlerFactoryMethod; import ch.qos.logback.core.model.NamedComponentModel; @@ -270,7 +267,7 @@ protected int secondPhaseTraverse(Model model, ModelFilter modelFilter) { } private boolean dependencyIsADirectSubmodel(Model model) { - List<String> dependecyNames = this.mic.getDependeeNamesForModel(model); + List<String> dependecyNames = this.mic.getDependencyNamesForModel(model); if (dependecyNames == null || dependecyNames.isEmpty()) { return false; } @@ -289,14 +286,19 @@ private boolean dependencyIsADirectSubmodel(Model model) { private boolean allDependenciesStarted(Model model) { // assumes that DependencyDefinitions have been registered - List<String> dependencyNames = mic.getDependeeNamesForModel(model); + List<String> dependencyNames = mic.getDependencyNamesForModel(model); if (dependencyNames == null || dependencyNames.isEmpty()) { return true; } for (String name : dependencyNames) { - boolean isStarted = mic.isNamedDependeeStarted(name); - if (isStarted == false) { + boolean isRegistered = AppenderDeclarationAnalyser.isAppenderDeclared(mic, name); + if (!isRegistered) { + // non registered dependencies are not taken into account + continue; + } + boolean isStarted = mic.isNamedDependemcyStarted(name); + if (!isStarted) { return false; } }
logback-core/src/main/java/ch/qos/logback/core/model/processor/DependencyDefinition.java+11 −10 modified@@ -16,30 +16,31 @@ import ch.qos.logback.core.model.Model; /** - * Defines the relation between a dependee (Model) and a dependency (String). + * Defines the relation between a depender (of type Model) and a dependency name (String). * - * Note that a dependee may have multiple dependencies but + * Note that a depender may have multiple dependencies but * {@link DependencyDefinition} applies to just one dependency relation. * * @author ceki * */ public class DependencyDefinition { - // depender: a component of type Model which depends on a dependee + // OLD terminology: depender: a component of type Model which depends on a dependee + // NEW terminology: dependent: a component of type Model which depends on a dependency Model depender; - // dependee: the string name of a component depended upon by the depender of type Model - String dependee; + // dependee or dependency: the string name of a component depended upon by the depender of type Model + String dependency; - public DependencyDefinition(Model depender, String dependee) { + public DependencyDefinition(Model depender, String dependency) { this.depender = depender; - this.dependee = dependee; + this.dependency = dependency; } - public String getDependee() { - return dependee; + public String getDependency() { + return dependency; } public Model getDepender() { @@ -51,7 +52,7 @@ public Model getDepender() { public String toString() { return "DependencyDefinition{" + "depender=" + depender + - ", dependee='" + dependee + '\'' + + ", dependency='" + dependency + '\'' + '}'; } }
logback-core/src/main/java/ch/qos/logback/core/model/processor/FileCollisionAnalyser.java+0 −7 modified@@ -33,13 +33,6 @@ @PhaseIndicator(phase = ProcessingPhase.DEPENDENCY_ANALYSIS) public class FileCollisionAnalyser extends ModelHandlerBase { - // Key: appender name, Value: file path - final static String FA_FILE_COLLISION_MAP_KEY = "FA_FILE_COLLISION_MAP_KEY"; - - // Key: appender name, Value: FileNamePattern - Map<String, FileNamePattern> RFA_FILENAME_COLLISTION_MAP = new HashMap<>(); - - public FileCollisionAnalyser(Context context) { super(context); }
logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java+6 −6 modified@@ -183,24 +183,24 @@ public List<DependencyDefinition> getDependencyDefinitions() { return Collections.unmodifiableList(dependencyDefinitionList); } - public List<String> getDependeeNamesForModel(Model model) { + public List<String> getDependencyNamesForModel(Model model) { List<String> dependencyList = new ArrayList<>(); for (DependencyDefinition dd : dependencyDefinitionList) { if (dd.getDepender() == model) { - dependencyList.add(dd.getDependee()); + dependencyList.add(dd.getDependency()); } } return dependencyList; } - public boolean hasDependers(String dependeeName) { + public boolean hasDependers(String dependencyName) { - if (dependeeName == null || dependeeName.trim().length() == 0) { + if (dependencyName == null || dependencyName.trim().length() == 0) { new IllegalArgumentException("Empty dependeeName name not allowed here"); } for (DependencyDefinition dd : dependencyDefinitionList) { - if (dd.dependee.equals(dependeeName)) + if (dd.dependency.equals(dependencyName)) return true; } @@ -212,7 +212,7 @@ public void markStartOfNamedDependee(String name) { startedDependees.add(name); } - public boolean isNamedDependeeStarted(String name) { + public boolean isNamedDependemcyStarted(String name) { return startedDependees.contains(name); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.