Upgraded log4j, but still do not understand the nature of log4j vulnerabilities?

Abstract: It has been a long time since the log4j remote code vulnerability problem was widely exposed. Today, I fully explain JNDI and RMI and the deep reason of the vulnerability.

This article is shared from HUAWEI CLOUD Community " Upgrade log4j, but still do not understand the nature of log4j vulnerabilities? To fully explain jndi, rmi and the deep reason of this vulnerability for you! ", author: breakDraw.

It has been a while since the log4j remote code vulnerability was widely exposed.

Many people can only see a demo of "pop up a calculator", so they think "oh, just execute arbitrary code and start a calculator", but they don't know much about the principle of this vulnerability.

For students who are not very deep in java development and application, jndi and rmi are even more unfamiliar terms.

Here, we will gradually advance the answer to this question in the form of continuous questions, uncover the essence of this loophole step by step, and give some thoughts on this loophole.

Q: What is the "${}" symbol in log4j? What is the use?

A: You can print some special values ​​to the log by means of ${}.

For example, ${hostName} can print the hostname

${java:vm} print jvm information

${thread:threadName} can print the thread name

When you use this value as a log parameter, the value will be printed instead of the original parameter name.

It can be understood that the function of log4j is more powerful, you don't need to write java code to print this information, you can get these prints directly with a string.

All of the above can only be done by implementing the corresponding Lookup class, that is, either log4j is built in, or we add it ourselves.

Q: Is the above information printed on this machine the cause of the vulnerability? It looks like it can execute strange commands on the machine? Or look at the file path?
A: No.

The above lookups are all pre-defined loopup characters and cannot do arbitrary things! And even if you send these ${java.vm} or something, it can only be printed and collected on the server side. As an attacker, you cannot collect this information.

The real reason is because log4j supports ${jndi:xxxx}, which supports jndi for lookup to find objects and print them.

Q: What is JNDI?
A: JavaNamingandDirectoryInterface (JAVA naming and directory interface)

Simply put, you can use JNDI to use a name in the java environment to lookup to find something to use.

For example, you can configure a database connection directly in your own Java environment, the name is "java:MySqlDS",
and then other java processes search for "java:MysqlDs" through jndi, and then you will get a database connection.
In this way, if a machine has multiple processes, the same connection must be used, and the jndi database object of the entire java environment can be modified, and then other processes can take effect at the same time.

Connectionconn=null;

//Context就是jdni的类
Contextctx=newInitialContext();
//jndi关键方法,通过loopup找一个对象
ObjectdatasourceRef=ctx.lookup("java:MySqlDS");
//引用数据源
DataSourceds=(Datasource)datasourceRef;
conn=ds.getConnection();
	......
c.close();

In addition to database connection, he also supports loopup to find dns, you can get a dnsContext and then find the dns object corresponding to "sun.com"
Use JNDI for advanced DNS query

In this way, in log4j, you can obtain the domain name object corresponding to http://huaweicloud.com in the current machine through ${jndi:dns: http://huaweicloud.com } and print it to confirm whether the network request fails, whether it is obtained by dns something wrong.

This is why log4j introduces jndi, which makes it easier to obtain some printable objects for log statistics.

However, jndi also supports finding and retrieving a remote object via RMI/LDAP+url strings .
This operation of finding remote objects is the core problem of this vulnerability.

Only RMI is mentioned here. LDAP is similar and will not be discussed.

Q: What is RMI?
A: RMI, RemoteMethodInvocation.

Specific meaning:

  • The remote server implements concrete Java methods and provides interfaces
  • The client only needs to provide the corresponding parameters according to the definition of the interface class to call the remote method locally

In RMI, it actually returns a stub (stub) call object to the client, and then the client uses this stub object to make remote calls.

In this way, the client does not need to care about how the network behind it is written
or even know what port or ip the other party's service is,
so there is no need to write a bunch of sokect methods for a long time, and it also avoids always modifying the accessed url or something.

The specific process is as follows:

  1. The server side listens to a port, which is randomly selected by the JVM;
  2. The client does not know the communication address and port of the Server remote object, but the Stub contains this information and encapsulates the underlying network operations;
  3. The client side can call the method on the Stub;
  4. Stub connects to the communication port monitored by the Server and submits parameters;
  5. Execute a specific method on the remote server and return the result to the Stub;
  6. Stub returns the execution result to the client side, from the client's point of view, it seems that the Stub executes this method locally;

Q: Does the RMI client not need to care about the listening port of the server? Where does the client get the stub object from? It is impossible to generate it out of thin air.
A: The server side can start an RMI registry service RMIRegistry, the port is set to a unified 1099, and the ip is also fixed.

Then, when the client wants to get the stub object of a service such as order service order, it uses the name "order" to go to the RMI registry to request this stub . In
this case, the client only needs to know the RMI registry, no need Knowing the ip and port of other services saves management costs.

The server code looks like this:

//建立一个订单服务通信桩
OrderServerStubstub=newOrderServerStub();
//启动一个RMI注册中心,端口为1099
LocateRegistry.createRegistry(1099);
//把OrderServer这个桩,注册到rmi://0.0.0.0:1099/order这个url上
Naming.bind("rmi://0.0.0.0:1099/order",stub);

The client's code is so long, you can see that a loopup has found this pile.

Then you can directly call the queryOrder method in the stub to query the order!

Registryregistry=LocateRegistry.getRegistry("kingx_kali_host",1099);
OrderServerStubstub=(OrderServerStub)registry.lookup("hello");
stub.queryOrder("aaa");

Q: What is the relationship between JNDI and RMI? How are they linked together?
A: In the above code, you can see that RMI needs to write a piece of Java code to execute.

If you don't use RMI to store this communication object in the future, but use LDAP or the like, what should you do? Does the code have to be rewritten and then deployed?

And if you can use JNDI, you can get it through a small string, then it's simple.
Then when I need to switch the acquisition method of the communication object, I can switch the settings in JDNI.

And RMI just implements the spi interface of JNDI, so that it can support the use of JNDI+ strings to obtain objects

Here is the concept of SPI:

SPI, the full name of ServiceProviderInterface, is a service discovery mechanism. It searches for files in the META-INF/services folder under the ClassPath path and automatically loads the classes defined in the files.
This mechanism provides the possibility for many framework extensions. For example, the SPI mechanism is used in Dubbo and JDBC.

  • Speaking of people, spi means that the framework side provides an interface interface, and then as long as someone writes an implementation class under the class discovery path of the service, it can be used directly in the code.

In log4j, it just supports the acquisition of RMI objects in the way of ${jndi:rmi:xxxx:1099/path}.

The log4j developers may have only intended to use jndi to obtain various built-in objects of the java container, but they did not expect to ignore the way to obtain rmi.

As a result, our service may access the RMI service deployed by hackers and obtain an untrusted remote call object.

Q: But as mentioned earlier, we will only get a stub through RMI
. The content in the stub is only sent through a specific ip+port. The code is fixed.
No matter how malicious the command is, it will only be registered in RMI. The center is executed on the hacker's server , how can the attack be triggered on my side?

And the class file of this stub object is not available locally on our server, wouldn't it report a classNotFind exception?

A: An article about RMI injection said:

In addition to directly binding the remote object, the RMI server can also bind an external remote object (objects other than the current name directory system) through the References reference class.
After the Reference is bound, the server will first obtain the reference of the bound object through Referenceable.getReference() and save it in the directory. When the client looks up the remote object in lookup(), the client will obtain the corresponding objectfactory, and finally convert the reference to a specific object instance through the factory class.

  • In other words, RMI allows the client's java environment to not have this stub object
  • The RMI server (the service on port 1099) will return you a factory (serialized), allowing you to call the factory for conversion. And this factory that can be serialized is the root cause of the problem.

The entire utilization process is as follows:

  1. InitialContext.lookup(URI) is called in the target code, and the URI is user-controllable;
  2. The attacker controls the URI parameter to be the malicious RMI service address, such as: rmi://hacker_rmi_server//name;
  3. The attacker's RMI server returns a Reference object to the target, and the Reference object specifies a carefully constructed Factory class;
  4. When the target performs the lookup() operation, it will dynamically load and instantiate the Factory class, and then call factory.getObjectInstance() to obtain the external remote object instance;
  5. An attacker can write malicious code in the constructor of the Factory class file, static code block, getObjectInstance() method, etc. to achieve the effect of RCE;

Q: So how did the log4j-core 2.15 version change?
A: Limit the protocol used by jndi, and prohibit using ldap and rmi to call some remote services in jndi.

think

To be honest, the reason why this vulnerability has such a big impact is because the principle is too simple. Just send a demo of the rmi registry and the client to call the demo to others, and he can reproduce it, or even attack it in this way.

Why didn't the designers of log4j take this into account at the time?
A high probability may be because the spi mechanism of jndi is too extensible.
Maybe initially, jndi only supports named acquisition of objects such as dns, database driver, etc.

But later with the version update, JNDP supports RMI, LDAP and other implementations through the SPI mechanism, which was not considered by the log4j developers at the time.

In other words, this is a conflict between Java's high scalability and security , so the way JNDI is invoked should be used more carefully in the future.

 

Click Follow to learn about HUAWEI CLOUD's new technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324081172&siteId=291194637