Servlet
技术是 Web 开发的原点,Tomcat 和 Jetty 这样的 Web 容器,负责加载和运行 Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。Tomcat是实现Servlet的首选web服务器。
客户端发起一个http请求,比如get或者post类型。
Servlet容器接收到请求,根据请求信息,封装成HttpServletRequest和HttpServletResponse对象。
Servlet容器调用HttpServlet的init()方法,init方法只在第一次请求的时候被调用。
Servlet容器调用service()方法。
service()方法根据请求类型,这里是get类型,分别调用doGet或者doPost方法,这里调用doGet方法。(这个地方一般需要重写方法) 在doXXX方法中是我们自己写的业务逻辑 业务逻辑处理完成之后,返回给Servlet容器,然后容器将结果返回给客户端。
容器关闭时候,会调用destory方法
filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。
1、Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。
2、当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。
3、当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
4、但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
5、只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
6、如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
与servlet一样,Filter的创建和销毁也由web容器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
Filter对象创建后会驻留在内存
,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
注意
:有时候,filter不止一个,这样就会形成filter链。(根据实际的代码场景逻辑进行判断,从而进行绕过)
JavaWeb开发中的监听器(Listener)就是Application、Session和Request三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
注意:javaweb中有九大内置对象 :request,response,pageContext,session,application,out,page,config,exception,需要导入相应的包才可以使用
tomcat是http服务器+servlet容器。
Tomcat 作为Servlet容器,将http请求文本接收并解析,然后封装成HttpServletRequest类型的request对象,传递给servlet;同时会将响应的信息封装为HttpServletResponse类型的response对象,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器
一个 WEB 服务器 + 一个 Servlet 容器
需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是连接器和容器,连接器用来处理外部网络连接,容器用来处理内部 Servlet
Tomcat 设计了 4 种容器,分别是Engine
、Host
、Context
和Wrapper
。要注意的是这 4 种容器不是平行关系,属于父子关系,
一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 ServletRequest 和 ServletResponse 对象进行交流。
假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy
,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?
user.shopping.com
,因此 Mapper 会找到 Host2 这个容器。java的四个基本特征是:封装,继承,多态和反射
在研究java安全的时候,大多数需要用到反射
这个特性,因为,大部分情况下是不允许以正常的方式去实例化对象的。
java反射:在运行状态中,对于任意一个类,都能够知道这个类的属性和方法。
对于任意一个对象,都能够调用他的属性和方法
所以我们需要用反射的方式去实例化对象进行操作。
反射的本质相当于,直接从内存中去加载jvm编译过的,已经有的class对象,然后反向调用。
双亲委派机制,可以参考jvm的分析
当一个类加载器收到了类加载的请求的时候,它不会直接去加载这个类,而是把这个请求委托给父加载器加载。只有当父加载器无法加载这个类的时候,他才会尝试去加载这个类
BootstrapClassLoader
是启动类加载器,由 C 语言实现,用来加载 JVM
启动时所需要的核心类,比如rt.jar
、resources.jar
等。ExtClassLoader
是扩展类加载器,用来加载\jre\lib\ext
目录下 JAR 包。AppClassLoader
是系统类加载器,用来加载 classpath
下的类,应用程序默认用它来加载类。但是Tomcat却打破了双亲委派机制
Tomcat打破双亲委派的做法,我们称之为热加载
Tomcat 本质是通过一个后台线程做周期性的任务,定期检测类文件的变化,如果有变化就重新加载类。
Tomcat 的自定义类加载器 WebAppClassLoader
打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader
的两个方法:findClass
和 loadClass
。
public Class> findClass(String name) throws ClassNotFoundException {
...
Class> clazz = null;
try {
//1. 先在 Web 应用目录下查找类
clazz = findClassInternal(name);
} catch (RuntimeException e) {
throw e;
}
if (clazz == null) {
try {
//2. 如果在本地目录没有找到,交给父加载器去查找
clazz = super.findClass(name);
} catch (RuntimeException e) {
throw e;
}
//3. 如果父类也没找到,抛出 ClassNotFoundException
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
更多细节,请看这个师傅的文章:java - Tomcat 架构原理解析到架构设计借鉴 - 个人文章 - SegmentFault 思否
我们返回来继续说java反射
反射功能可以在运行时(动态):
常见的java反射获取对象的方式有这么几种:
类名.class
,如:com.anbai.sec.classloader.TestHelloWorld.class
。Class.forName("com.anbai.sec.classloader.TestHelloWorld")
。classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld");
例如,注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servlet(wrapper)添加到当前的context的children中。主要过程如下:
①获取 目标类型的Class对象
②通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
③通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作
在java中,只有被JVM加载后的类才能被调用,或者在需要时通过反射通知JVM加载.
Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法
例如修改类的字节码、向classLoader的classpath下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。
第一次有人请求Instrumentation的实例时,它会创建一个代理JAR文件,然后查找其自身进程的PID,并将其附加到JVM中
Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。
在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。
在Example下新建两个类
Bird.java:
public class Bird {
public void say()
{
System.out.println("bird is gone.");
}
}
然后把编译后的Bird.class复制出来,放到一个文件夹下。然后把Bird.java再改成如下:
Bird.java:
public class Bird {
public void say()
{
System.out.println("bird say hello");
}
}
Main.java:
public class Main {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
while(true)
{
Bird bird=new Bird();
bird.say();
Thread.sleep(3000);
}
}
}
把整个工程打包成可执行jar包Example.jar,放到之前那个文件夹。
在工程Agent中新建2个类:
AgentEntry.java:
public class AgentEntry {
public static void agentmain(String agentArgs, Instrumentation inst)
throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {
inst.addTransformer(new Transformer (), true);
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class c : loadedClasses) {
if (c.getName().equals("Bird")) {
try {
inst.retransformClasses(c);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("Class changed!");
}
}
Transformer.java:
public class Transformer implements ClassFileTransformer {
static byte[] mergeByteArray(byte[]... byteArray) {
int totalLength = 0;
for(int i = 0; i < byteArray.length; i ++) {
if(byteArray[i] == null) {
continue;
}
totalLength += byteArray[i].length;
}
byte[] result = new byte[totalLength];
int cur = 0;
for(int i = 0; i < byteArray.length; i++) {
if(byteArray[i] == null) {
continue;
}
System.arraycopy(byteArray[i], 0, result, cur, byteArray[i].length);
cur += byteArray[i].length;
}
return result;
}
public static byte[] getBytesFromFile(String fileName) {
try {
byte[] result=new byte[] {};
InputStream is = new FileInputStream(new File(fileName));
byte[] bytes = new byte[1024];
int num = 0;
while ((num = is.read(bytes)) != -1) {
result=mergeByteArray(result,Arrays.copyOfRange(bytes, 0, num));
}
is.close();
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public byte[] transform(ClassLoader classLoader, String className, Class> c,
ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {
if (!className.equals("Bird")) {
return null;
}
return getBytesFromFile("d:/Bird.class");
}
}
这个也要打包成jar包,但是直接打包肯定是打包不了的
首先在SRC下创建META-INF
文件夹然后再常见文件MANIFEST.MF
,内容如下
MAINFEST.MF:
Manifest-Version: 1.0
Agent-Class: AgentEntry
Can-Retransform-Classes: true
然后把Agent工程打包为Agent.jar.
然后新建run工程中,在其中新建1个类:
Attach.java:
public class Attach {
public static void main(String[] args) throws Exception {
VirtualMachine vm = null;
List listAfter = null;
List listBefore = null;
listBefore = VirtualMachine.list();
while (true) {
try {
listAfter = VirtualMachine.list();
if (listAfter.size() <= 0)
continue;
for (VirtualMachineDescriptor vmd : listAfter) {
vm = VirtualMachine.attach(vmd);
listBefore.add(vmd);
System.out.println("i find a vm,agent.jar was injected.");
Thread.sleep(1000);
if (null != vm) {
vm.loadAgent("agent.jar");
vm.detach();
}
}
break;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//import com.sun.tools.attach.VirtualMachineDescriptor;
//import com.sun.tools.attach.VirtualMachine;
//这里import的是这两个包,导入其他包会出错,同时还要把 java/lib下的tools.jar放到lib 移到项目中来,否则会报错
报错解决方法参考:“程序包com.sun.tools.javac.util不存在” 问题解决_java: 程序包sun.font不存在-CSDN博客
之后,打包成可执行jar包run.jar,放到该文件夹下。
先后执行
java -jar Example.jar
java -jar run.jar
就会有这样的情况
我们就实现了在动态运行中改变方法体。