热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JSPWebshell那些事——攻击篇(上)

作者:阿里云云安全中心 前言目前很多大型厂商都选择使用Java进行Web项目的开发,近年来随着各种JAVA指定环境RCE漏洞的出现,Java Web的安全逐渐被人们所重视,与漏洞相关的还有用于后期维持

作者:阿里云云安全中心

前言

目前很多大型厂商都选择使用Java进行Web项目的开发,近年来随着各种JAVA指定环境RCE漏洞的出现,Java Web的安全逐渐被人们所重视,与漏洞相关的还有用于后期维持权限的Webshell。与PHP不同的是,JSP的语言特性较为严格,属于强类型语言,并且在JDK9以前并没有所谓的eval函数。一般而言JSP的变形免杀较为困难,但是依旧存在很多的”黑魔法”。

不知攻,焉知防。阿里云安骑士Webshell检测系统在迭代升级过程中,除了内部的不断绕过尝试以外,也长期邀请大量白帽子进行持续的绕过测试。经过不断总结沉淀在JSP Webshell查杀引擎方面我们形成了基于字节码跟反汇编代码的检测方式,可以有效对抗云上高强度对抗性样本。

本文分为函数调用篇/战略战术篇/内存马篇/降维打击篇四个部分,将从攻击者的角度与大家一起分享JSP Webshell的攻击姿势。

关于JSP

JSP全称”Java Server Page”,其本质是一种Java Servlet。

JSP在第一次被访问的时候会先被翻译成Java文件,这个步骤由Tomcat等web容器完成;接着Java文件会被编译成JVM可以识别的class文件,这个步骤由JDK完成。

函数调用篇

直接调用

常见的直接调用是通过 java.lang.Runtime#exec和java.lang.ProcessBuilder#start

java.lang.Runtime

java.lang.ProcessBuilder

反射调用

反射可以说是Java中最强大的技术,很多优秀的框架都是通过反射完成的。一般的类都是在编译期就确定下来并装载到JVM中,但是通过反射我们就可以实现类的动态加载。如果查阅源码可以发现,图中提到的很多命令执行方式的底层都是反射。

因为反射可以把我们所要调用的类跟函数放到一个字符串的位置,这样我们就可以利用各种字符串变形甚至自定义的加解密函数来实现对恶意类的隐藏。

除此以外,反射可以直接调用各种私有类方法,文章接下来的部分会让大家进一步体会到反射的强大。

加载字节码

说到加载字节码就必须提到java.lang.ClassLoader这个抽象类,其作用主要是将 class 文件加载到 jvm 虚拟机中去,里面有几个重要的方法。


  • loadClass(),加载一个类,该方法会先查看目标类是否已经被加载,查看父级加载器并递归调用loadClass(),如果都没找到则调用findClass()。

  • findClass(),根据类的名称或位置加载.class字节码文件,获取字节码数组,然后调用defineClass()。

  • defineClass(),将字节码加载到jvm中去,转化为Class对象


更详细的说明可以参考这篇文章:https://zhuanlan.zhihu.com/p/103151189


调用defineClass

提到defineClass就想到了冰蝎,冰蝎可以说是第一个实现JSP一句话的Webshell管理工具。其中defineClass这个函数是冰蝎实现的核心。

因为java在1.8以前并没有像php的eval函数,所以要实现动态执行payload就要另外想办法。因为java世界中所有的执行都是依赖于字节码,不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java虚拟机的规范,那么它就能够执行该字节码文件。所以如果可以让服务端做到动态地将字节码解析成Class,就可以实现“JSP一句话”的效果。

正常情况下,Java并没有提供直接解析class字节数组的接口。不过classloader内部实现了一个protected的defineClass方法,可以将byte[]直接转换为Class。但是因为该方法是protected的,我们没办法在外部直接调用。这里就有两种处理办法:

第一种是继承,直接自定义一个类继承classloader,然后在子类中调用父类的defineClass方法。这种方式比较简单,所以原版冰蝎中采用的这种办法。

第二种是反射,通过反射来修改保护属性,从而调用defineClass。

以下为蚁剑基于冰蝎的原理实现的JSP一句话样本。利用ClassLoader类中的defineClass,我们就可以把一个自定义的类传入并加载。

BCEL字节码

这个就是一个比较神奇的类了,可以直接通过classname来进行字节码的加载。

查看loadClass方法的源码,发现会判断传入的bcelcode是否有”$$BCEL$$”这个字符串,就会将后面的内容转换成标准字节码,然后使用defineClass进行加载。

protected Class loadClass(String class_name, boolean resolve)
throws ClassNotFoundException
{
...
if(cl == null) {
JavaClass clazz = null;
/* Third try: Special request?
*/
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
else { // Fourth try: Load classes via repository
if ((clazz = repository.loadClass(class_name)) != null) {
clazz = modifyClass(clazz);
}
else
throw new ClassNotFoundException(class_name);
}
if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);
} else // Fourth try: Use default class loader
cl = Class.forName(class_name);
}
if(resolve)
resolveClass(cl);
}

URLClassLoader远程加载

URLClassLoader是ClassLoader的子类,它用于从指定的目录或者URL路径加载类和资源。当URL里的参数是由”http://”开头时,会加载URL路径下的类。

URLClassLoader本地加载

当URL里的参数是由”file://”开头时,会加载本地路径下的类。

由于加载的字节码是固定的并且不可直接修改,没办法直接实现对命令的动态解析。要么配合冰蝎一样的客户端,每次都调用ASM等字节码框架动态生成字节码传过去,要么就想其他办法把我们要执行的指令传递进去。

这个例子利用了一个很巧妙的方法:把收到的指令拼凑成源代码后直接在服务端进行编译,然后写入到本地文件中,再利用URLClassLoader对写入的文件进行加载。

表达式类调用


ScriptEngineManager

通过ScriptEngineManager这个类可以实现Java跟JS的相互调用,虽然Java自己没有eval函数,但是ScriptEngineManager有eval函数,并且可以直接调用Java对象,也就相当于间接实现了Java的eval功能。但是写出来的代码必须是JS风格的,不够正宗,所以将这部分归类为“表达式类调用”部分。

EL表达式


表达式语言(Expression Language),或称EL表达式,简称EL,是Java中的一种特殊的通用编程语言,借鉴于Javascript和XPath。主要作用是在Java Web应用程序嵌入到网页(如JSP)中,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换。

https://blog.csdn.net/FZW_Faith/article/details/54235104

除了ScriptEngineManager以外,ELProcessor也有自己的eval函数,并且可以调用Java对象执行命令。

Expression

java.beans.Expression同样可以实现命令执行,第一个参数是目标对象,第二个参数是所要调用的目标对象的方法,第三个参数是参数数组。这个类的优势是可以把要执行的方法放到一个字符串的位置,不过限制就是第一个参数必须是Object。不过我们可以配合反射将Runtime类的关键字给隐藏掉。

除了上面提到的以外还有OGNL(Struct),SpEL(Spring)等表达式,但不是jdk自带的,在这里不予分析。

反序列化

序列化的过程是保存对象的过程,与之相反的,反序列化就是把对象还原的过程。在这里提到的反序列化并不仅仅指直接ObjectInputStream读入二进制流,利用XML/XSLT同样可以使保存的对象还原,达到反序列化的目的。

重写ObjectInputStream的resolveClass

XMLDecoder

XMLDecoder可以将XMLEncoder创建的xml文档内容反序列化为一个Java对象,研究过Weblogic系列漏洞的同学对这个类一定不陌生。通过传入恶意的XML文档即可实现任意命令的执行。

XSLT

XSL 指扩展样式表语言(EXtensible Stylesheet Language), 它是一个 XML 文档的样式表语言。通过构建恶意的模板让Webshell来解析,同样可以达到命令执行的目的。

JNDI注入


JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。

提到jndi注入就想到了fastjson,通过lookup一个恶意的远程Java对象即可达到任意命令执行。相关的文章已有很多,这里不再赘述。

JNI调用

JNI全称 Java Native Interface,通过JNI接口可以调用C/C++方法,同样可以实现命令执行的目的。

详细介绍:https://javasec.org/javase/JNI/

JShell

JShell 是 Java 9 新增的一个交互式的编程环境工具。与 Python 的解释器类似,可以直接输入表达式并查看其执行结果。

但是由于JDK8跟JDK9之间更改幅度较大,目前来说并没有普遍使用,所以暂时实战效果并不明显。

战略战术篇

由于Java面向对象的特性,几乎每个类都不是独立的,背后都是有一系列的继承关系。查杀引擎可能会识别常见的恶意类,但是我们就可以通过查找恶意类的底层实现或者高层包装类进行绕过,从而实现Webshell的免杀。

向下走–寻找底层实现类

这里以常见的Runtime类跟Expression类为例

ProcessImpl

查看Runtime类中exec方法的源码,可以发现exec实际上调用了ProcessBuilder的start方法

进一步查看ProcessBuilder可以发现是触发了java.lang.ProcessImpl的start方法

跟进ProcessImpl的start发现最后调用了其构造方法。

看一下ProcessImpl的构造方法是private类型的,并且没有任何共有构造器,所以直接实例化ProcessImpl就会报错。

在Java中,如果想要阻止一个类直接被实例化一般有两种方法,一种是直接把类名用private修饰,另一种是只设置私有的构造器。虽然我们不能直接new一个ProcessImpl,但是可以利用反射去调用非public类的方法。

Statement

上文中提到了Expression的getValue方法可以实现表达式的执行,看一下他的源码的内容

发现Expression类继承了Statement,并且再构造函数中调用的也是父类的构造函数

查看getValue方法,发现调用了父类的invoke函数

查看invoke函数,跳转到了java.beans.Statement#invoke

跟进java.beans.Statement#invokeInternal发现底层的实现其实就是反射

综上所述,Expression的getValue实际上是调用了Statement类的invoke()函数,再通过一系列的反射实现表达式的计算。但是invoke函数不是public类型的,不能直接调用。但是我们可以发现同类中的java.beans.Statement#execute方法调用了invoke,且同时满足是public类型,可以直接调用。Statement类也是public的,可以直接new,所以我们就可以构造出一个新的利用方式。

ELManager

查看ELProcessor的eval的底层实现,找到javax.el.ELProcessor#getValue

其实是调用了this.factory的createValueExpression方法,跟进this.factory发现是ELProcessor类的构造方法中通过ELManager.getExpressionFactory()获取的。

所以就可以构造如下形式进行绕过。

向上走–寻找调用跟包装类

既然可以用底层类来绕过,那么我们当然可以寻找哪些类对我们的恶意类进行了调用跟包装。

sun.net.www.MimeLauncher

从源码中可以看到sun.net.www.MimeLauncher#run方法中最后调用了Runtime类的exec方法

但是这个类是package-private修饰的,所以不能直接调用。不过没关系,我们还有反射。

构造所需参数,然后通过反射调用run方法

在源码中grep一下关键字可以看到同样的类还有几个,这里不再赘述。

最后

Java博大精深,深入挖掘还可以发现更多有趣的特性。本文仅为抛砖引玉,如果有不严谨的地方欢迎指正。

关于我们

阿里云安全-能力建设团队以安全技术为本,结合云计算时代的数据与算力优势,建设全球领先的企业安全产品,为阿里集团以及公有云百万用户的基础安全保驾护航。

团队研究方向涵盖WEB安全、二进制安全、企业入侵检测与响应、安全数据分析、威胁情报等。

知乎链接:https://zhuanlan.zhihu.com/p/120973806


推荐阅读
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 标题: ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 本文介绍了在sqoop1.4.*版本中,如何实现自定义分隔符的方法及步骤。通过修改sqoop生成的java文件,并重新编译,可以满足实际开发中对分隔符的需求。具体步骤包括修改java文件中的一行代码,重新编译所需的hadoop包等。详细步骤和编译方法在本文中都有详细说明。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • SpringMVC工作流程概述
    SpringMVC工作流程概述 ... [详细]
  • 本文介绍了关于apache、phpmyadmin、mysql、php、emacs、path等知识点,以及如何搭建php环境。文章提供了详细的安装步骤和所需软件列表,希望能帮助读者解决与LAMP相关的技术问题。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
author-avatar
哥的微笑帅_655
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有