java安全中几个重要机制
java 代理
代理的作用,就是在原有类的代码不发生改动的情况下,添加新功能。起到一个修饰器的作用。
假设我们现在有个类,用于打印helloworld
class hello{ |
我们想在不改动hello类的前提下,在输出helloworld时同时输出当前时间到文件,达到日志的功能,该如何解决这个问题呢?以这个问题为切入点,开始学习代理。
静态代理
静态代理很简单,不用接触什么新技术。就是创建一个”继承已有的一个类
“的类,通过重写父类的方法,达到不改动原有类的基础上增添新功能。纸上得来终觉浅,用代码来说明一下吧。
以刚刚的输出helloworld的类为例。我们编写一个它的子类,并在其中重写其gogo方法,添加日志生成功能
class hello{ |
然后主函数中我们只需调用static_hello_proxy中的gogo()方法就能达到日志功能了。这就是静态代理。
静态代理的缺点显而易见,如果我们想要代理不同的类,就要写出不同的静态代理类出来,同时我们也可能会遇到需要多个代理类来增添实现一个类的不同功能,有需要定义一大堆类出来。不容易维护,所以动态代理就诞生了,它很好的解决了静态代理会产生大量代理类的难题。
动态代理
使用动态代理,可以不用创建代理类,非常的方便。
动态代理由Proxy.newProxyInstance方法实现。
我们看看它的三个参数。
loader,顾名思义,就是被代理类的类加载器。
interfaces 被代理类所需要实现的接口,可通过 类对象.getinterfaces()获得
h 即 InvocationHandler接口类 的实现类,用于实现代理增添的方法
我们看看这个 InvocationHandler 类的结构。这个类只有一个invoke方法,且这个类是接口类。这个invoke方法就用于存放我们的增添的功能。
proxy即代理对象,method即对象中的某个方法,args即方法中的参数。
ok,接下来把刚刚那个类动态代理一下吧。但是需要注意的是动态代理只能代理接口实现类。
import java.io.File; |
OK,ALL DOWN
JNI
JNI是JAVA的一个接口,用来实现c/c++调用,为JAVA提供了一个操纵底层的能力。
JNI HelloWorld
java IDE:idea C IDE:vs2019
1.写好JAVA文件,定义好native方法。
public class test { |
2.使用javac指令为java文件的各native方法生成c语言头文件。
javac -cp . test.java -h . |
执行后会在当前目录下生成一个头文件
3.用vscode创建一个dll项目,将test.h与jdk include目录下的jni.h以及jdk/include/win32/jni_md.h统统移动到项目文件目录下。然后在vscode头文件里添加现有项,把刚刚放到项目文件下的3个头文件添加进去。
4.在c或c++中定义native方法的c/c++原型。
通过test.h我们可以看到哪些方法需要我们定义。这里是Java_test_hello方法。
我们随便定义一下
#include "test.h" |
编译为dll文件。
5.在idea里为当前项目指定依赖dll文件路径
先点击箭头指向处的编辑选项,然后在虚拟机选项里填上-Djava.library.path=你的dll路径
6.把dll文件拖进刚刚配置好的依赖路径,运行
JNI 数据类型
JNI类型数据在c文件里当作传入参数和返回结果,参数传入后其数据类型会被用指定方法转换为C类型然后被处理,处理完毕后使用指定方法转换为JNI类型返回给JAVA。
这里介绍一下c中JNI类型与c类型互转的函数
jstring转char*:env->GetStringUTFChars(str, &jsCopy) |
JNI 实现本地命令执行
相当于一个加强版的helloworld.
首先我们看一下,如果在c++中我们想执行本地命令执行该如何执行
#include<iostream> |
那么我们在JNI里如何实现呢?其实也差不多.
我们现在写好java代码
public class test { |
写好c++代码
#include "test.h" |
运行结果
类加载器
ClassLoader
在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)
、Extension ClassLoader(扩展类加载器)
、App ClassLoader(系统类加载器)
,AppClassLoader
是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader
加载类,ClassLoader.getSystemClassLoader()
返回的系统类加载器也是AppClassLoader
。
ClassLoader的主要方法有以下几个:
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(从byte[]获得一个Java类)resolveClass
(链接指定的Java类)
如何通过ClassLoader获取一个类对象?很简单
Class a = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime"); |
它对一个类的加载流程如下:
- ClassLoader调用loadClass(String name)方法加载指定类
- 调用findLoadedClass检查指定的类是否已经初始化,若已初始化则直接返回类对象
- 如果创建ClassLoader时传入父类加载器,则使用父类加载器加载指定类,否则使用JVM的Bootstrap ClassLoader(引导类加载器)加载
- 如果以上步骤没有完成加载,则使用findClass方法尝试加载指定类
- 如果当前ClassLoader类没有重写findClass方法则直接返回异常。若重写了该方法且通过findClass找到了传入的类名的对应的类字节码,那么就会使用defineClass去JVM注册该类
- 如果loadClass调用时传入resolve的参数为true,则那么还需要调用resolveClass方法连接类。该参数默认为false
- 返回被JVM加载后的指定类的类对象
ClassLoader自定义
java.lang.ClassLoader 是所有类加载器的父类,我们可以通过重写其findClass方法来实现自定义ClassLoader。
我们试着自定义一个,当对loadClass传入com.anbai.sec.classloader.TestHelloWorld 时会直接返回一个在加载器里已经定义好了的对应类的类对象。
class TestClassLoader extends ClassLoader{ |
然后主函数我们写
public static void main(String[] args) { |
经测试程序正常运行。我们的自定义ClassLoader就写好了。当然还有更多花里胡哨的重写方法,我们这里只是知道怎么重写即可。
URLClassLoader
URLClassLoader也是ClassLoader类下的一个重写类。这个类很好用,它可以通过网络协议获取远程的jar包,然后通过类加载器去获得其中的指定类的类对象。试试吧。
流程是先定义一个URL对象指向我们的jar包,然后实例化一个URLClassLoader对象,然后通过loadClass加载某个类获得类对象。
import java.lang.reflect.Field; |
然后我们jar包里的class是这个.JAVA的编译文件
public class fuck{ |
执行结果
反射
反射的基础
反射是java的一个特性,用于获取类的详细信息(方法,变量),并可以执行类中的方法。
获得一个类的类对象
要获取类的详细信息或执行其中的方法,首先肯定是要获取到那个类的类对象
方法一:我们需要创建一个Class类型的变量,用于接收Class.forname(“类”)返回的类对象。这个方法必须通过try..catch 来处理其中ClassNotFoundException
try{ |
方法二:我们实例化一个类的对象出来,然后通过 对象.getClass()获得其类对象
String a = new String; |
方法三:使用.class
Class clazz = String.class; |
这样,我们就获得一个指定类的类对象了。很简单。我们可以对一个类对象使用getName()方法获取其类名。
System.out.println(clazz.getName()); |
获取一个类对象的所有成员
获取属性并修改
对类对象使用 getFields() 或 getDeclaredFields() 方法即可获得属性数组,区别在于前者只能获取公有属性,后者能获取私有属性。
然后遍历属性数组,通过对每一项执行getName()获取属性名,get(实例化的对象)获得属性值
然后对某一项进行.set()对其进行修改
这里需要提的是,如果要获得或修改私有属性的值的时候,需要对私有属性使用setAccessible(true)来实现私有访问
import java.lang.reflect.Field; |
运行结果
另外,你也可以通过getFiled(“属性名”)来获取特定属性的Filed对象, getDeclaredFields() 同理
获取方法并执行
接下来是获取方法的一些信息
我们通过getMethod获取方法对象,然后通过getName获取方法名,getReturnType获取返回值类型,getParameterTypes获取传入的参数类型
import java.lang.reflect.Field; |
接下来是对method对象执行invoke方法调用,通过传入参数,指定对象,即可调用该方法。我在上述代码中添加了修改
import java.lang.reflect.Field; |
获取构造方法并执行
获得构造方法,主要是通过 getConstructors()和getDeclaredConstructors() ,后者能访问私有对象,下面是用遍历法获得所有构造函数信息
import java.lang.reflect.Constructor; |
运行结果
我们刚才提过,getFiled()附带参数可以指定访问某一个属性,同理,getConstructor也一样,我们想要执行某一个类的构造方法,往往这个方法更实用
要执行一个类的构造方法,那我们需要创建一个该类的实例化对象,这个过程我们用newInstance()方法实现
import java.lang.reflect.Constructor; |
执行结果
反射的进阶与安全
Class.forName 实质与类初始化
Class.forName(“…”) 常被我们拿来获得类对象,但是实际上,Class.forname有三个参数,只不过我们默认输第一个参数:类名就能完成工作了。
这是Class.fornName函数原型 |
这里我们看见有三个参数,第一个参数是指定类名就不多讲了,第二个参数是决定类是否初始化(这个稍后会详细阐明),第三个是ClassLoader类加载器(告诉JVM如何加载这个类这里不展开说)
关于一个类的初始化,有三种操作可以实现:构造方法,空块和static块,就像这样
public class TrainPrint { |
那么执行顺序是如何呢,在引入父类的情况下又是如何呢?我们写个demo看看
import java.lang.reflect.Constructor; |
我们可以清晰的看到执行顺序,对继承类来说,
1.会先执行父类static块
2.执行自己的static块
3.执行父类空快
4.执行父类构造方法
5.执行自己空快
6.执行自己构造方法
对于一个类来说,执行顺序则是
1.执行static块
2.执行空快
3.执行构造方法
那么对于Class.forName指定的是否进行类初始化参数,指的是哪个部分?static块,空块还是构造方法?答案是只会执行static块里的,且会优先执行父类的static块。
我们把上面代码中的主函数替换为
try { |
执行一下,看结果。发现只执行了static块
也就是说,Class.forName()会默认执行类的static代码块,是个比较危险的信号。
另外一提,xxx.class.newinstance() 会自动调用类的构造方法。
Runtime执行命令解析
一般来说,我们调用Runtime类来执行命令时的指定是这样的
Runtime.getRuntime().exe() |
我们到源码里分析这段代码
可见,当我们对Runtime类执行getRuntime()时会得到一个Runtime对象,然后我们就可以调用我们的exec方法了
同时,Runtime() 被private修饰符修饰了,这说明我们无法通过Runtime a = new Runtime()来实现一个Runtime的对象。
所以说,我们正常地使用runtime来执行命令只能依靠以上代码。
那么我们要是想要依靠反射来写一个Runtime执行任意命令的payload,那么该如何写呢?
先来一个错误示范,当我们用常规的思路去实现时。我们直接调用Runtime类里的exec方法,然后通过newInstance来实例化一个Runtime对象
Class clazz = Class.forName("java.lang.Runtime"); |
最终结果则是报错:class test cannot access a member of class java.lang.Runtime (in module java.base) with modifiers “private”
看来通过反射也不能直接调用exec方法,或者说不能实例化Runtime对象。
那么正确思路该是什么呢?应该是先调用getRuntime获得Runtime对象,然后再调用exec方法
Class clazz = Class.forName("java.lang.Runtime"); |
最终弹出计算器。
ProcessBuilder执行命令的反射实现
除了Runtime以外,还可以用ProcessBuilder类来执行命令。
它的正常情况使用如下.
ProcessBuilder pb = new ProcessBuilder("calc.exe"); |
我们来分析一下ProcessBuilder的构造方法,它的构造方法有很多个重载,我们分析一个吧。
可见,ProcessBuilder的构造方法把传入参数保存到了command属性里,然后commad会被以系统命令调用(这部分代码就不贴出来了)。
那么以反射的形式该如何实现呢。
Class clazz = Class.forName("java.lang.ProcessBuilder"); |
Servlet
Servlet生命周期
1.调用init()方法进行初始化
init()方法在第一次创建servlet时被调用,用于进行初始化操作。
public void init() throws ServletException{...code...} |
2.调用service()方法处理客户端请求
web服务器,即servlet容器调用service()方法处理来自客户端的请求,并返回响应。同时根据客户端请求的类型(Get,Post,delete等),做出不同的行为,这些行为是由service方法抽象出的其他方法(doGet,doPost等)。
所以这里说的调用service()方法也就是等同于调用doGet,doPost等方法。
doPost,doDelete等方法的定义形同于下 |
3.调用distroy()在Servlet销毁前完成清理活动
distrtoy()函数只调用一次,在Servlet被销毁前完成后台线程停止,数据库链接关闭等一系列清理工作。
public void destroy() { |
编写一个简单的Servlet
我们写一个程序,用来控制Get请求的响应
import javax.servlet.*; |
至于部署我是参考的这个文章https://blog.csdn.net/gaoqingliang521/article/details/108677301
我的版本时idea 2020.2,可能会有出入。
处理传参
servlet处理传参主要是通过doGet和doPost方法进行的。
接收参数值主要是靠下面的方法
- getParameter():您可以调用 request.getParameter() 方法来获取表单参数的值。
- getParameterValues():如果参数出现一次以上,则调用该方法,并返回多个值,例如复选框。
- getParameterNames():如果您想要得到当前请求中的所有参数的完整列表,则调用该方法。
我们写个demo试试
import javax.servlet.*; |
这个demo主要用于接收get数据,并做出一些处理返回.
那么如何接收POST数据呢?和doGet差不多
import javax.servlet.*; |
cookie&session
cookie
cookie的主要方法如下
序号 | 方法 & 描述 |
---|---|
1 | public void setDomain(String pattern) 该方法设置 cookie 适用的域,例如 runoob.com。 |
2 | public String getDomain() 该方法获取 cookie 适用的域,例如 runoob.com。 |
3 | public void setMaxAge(int expiry) 该方法设置 cookie 过期的时间(以秒为单位)。如果不这样设置,cookie 只会在当前 session 会话中持续有效。 |
4 | public int getMaxAge() 该方法返回 cookie 的最大生存周期(以秒为单位),默认情况下,-1 表示 cookie 将持续下去,直到浏览器关闭。 |
5 | public String getName() 该方法返回 cookie 的名称。名称在创建后不能改变。 |
6 | public void setValue(String newValue) 该方法设置与 cookie 关联的值。 |
7 | public String getValue() 该方法获取与 cookie 关联的值。 |
8 | public void setPath(String uri) 该方法设置 cookie 适用的路径。如果您不指定路径,与当前页面相同目录下的(包括子目录下的)所有 URL 都会返回 cookie。 |
9 | public String getPath() 该方法获取 cookie 适用的路径。 |
10 | public void setSecure(boolean flag) 该方法设置布尔值,表示 cookie 是否应该只在加密的(即 SSL)连接上发送。 |
11 | public void setComment(String purpose) 设置cookie的注释。该注释在浏览器向用户呈现 cookie 时非常有用。 |
12 | public String getComment() 获取 cookie 的注释,如果 cookie 没有注释则返回 null。 |
我们马上写一个demo,这个demo会用到 response.addCookie用于在响应头里添加cookie,cookie.setMaxAge 用于设置cookie的有效期,request.getCookies 用于获取请求里的cookie数组
这里的demo有两个,一个用于添加cookie,一个用于输出cookie
用于添加cookie |
session
servle提供了一系列session接口辅助这个流程
1 | public Object getAttribute(String name) 该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。 |
---|---|
2 | public Enumeration getAttributeNames() 该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。 |
3 | public long getCreationTime() 该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 |
4 | public String getId() 该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。 |
5 | public long getLastAccessedTime() 该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 |
6 | public int getMaxInactiveInterval() 该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。 |
7 | public void invalidate() 该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。 |
8 | public boolean isNew() 如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。 |
9 | public void removeAttribute(String name) 该方法将从该 session 会话移除指定名称的对象。 |
10 | public void setAttribute(String name, Object value) 该方法使用指定的名称绑定一个对象到该 session 会话。 |
11 | public void setMaxInactiveInterval(int interval) 该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。 |
写个demo吧,过一过就行了
import javax.servlet.*; |
JSP
概览
JSP是脚本语言,与PHP等语言类似。JSP本质就是servlet,它存在的意义就是简化servlet复杂的程序过程
JSP基础语法
代码段
JSP和PHP等可以嵌入到HTML的脚本语言类似,需要用一个代码段包裹代码。
其格式如下
<% code %> |
同时在代码段中还存在着指令标签:Page,include,taglib
他们的定义和作用如下
<%@ page ...... %> |
动作元素
与指令元素比较相似,但不同的是动作元素可以动态的插入,不像指令元素一样一开头就会被执行。
九大对象
JSP中有九大对象用于实现各种操作。
至于request和response,这俩用的是最多的是他们,
out对象主要用于在response中写入输出内容, out.println或者out.print用于输出内容,out.flush用于刷新输出流,其余在新手阶段都不咋用。
传参处理
- getParameter(): 使用 request.getParameter() 方法来获取表单参数的值。
- getParameterValues(): 获得如checkbox类(名字相同,但值有多个)的数据。 接收数组变量 ,如checkbox类型
- **getParameterNames():**该方法可以取得所有变量的名称,该方法返回一个 Enumeration。
- **getInputStream():**调用此方法来读取来自客户端的二进制数据流。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> |
cookie&session
cookie,session在JSP里的设置与servlet如出一辙
cookie和session在jsp的<%%>代码区里设置,设置方法与servlet如出一辙,因为<%%>代码区里本身就是调用的java代码,所以不再赘述
RMI
何为RMI
RMI,即远程方法调用,允许运行在一个JAVA虚拟机调用另一个JAVA虚拟机上的对象的方法.
RMI:实现一个远程接口
RMI的远程接口用于其他java虚拟机远程调用该接口下的对象的方法.
定义一个远程接口
import java.rmi.Remote; //引入Remote接口 |
远程接口的实现类
刚刚我们定义了一个Remote接口,但是他的方法还未具体实现.我们就来实现远程接口的实现类吧
import java.rmi.RemoteException; |
两台JVM通讯的第一步:RMI Registry
何为RMI Registry
可以理解为部署在被远程调用的JAVA虚拟机上的一个应用,用于将stub绑定到Registry服务的URL上。
服务端绑定stub到指定url
java.rmi.Naming.rebind("rmi://localhost:1099/hello", hello); |
随后客户端想要远程调用hello这个对象的方法时,就需
IHello hello = (IHello) Naming.lookup("rmi://localhost:1099/hello"); |
Stub和Skeleton
由上文可知当客户机远程调用一个对象时,返回的其实不是对象本身,而是stub.
你可以理解stub为一个中继站,当客户机调用该对象某个方法时,实际上是通过stub以socket的方式向服务器端的skeleton发送序列化处理的方法名和参数(skeleton可以理解为服务器端上的真实对象)
服务端的skeletion反序列化得到的方法名和参数并处理后再以socket的方式把该方法生成的结果传回stub,stub再把数据返回给客户机
代码实现远程调用
服务器端 |
结果
javassist
javassist 是一个类库,用于操作java字节码。
比如我们接下来就会使用javassist来动态操作字节码。
test2.java |
反编译d:\\test\\class1.class
// |
发现我们已经往test2.java的内容中插入了输出hello的静态代码块,并以class1.class写入d:\\test文件夹。
javassist的功能除此之外还有很多