初识JavaWeb安全(2)
前言
上回了解了基础的Java
反射机制,以及如何执行系统命令的方法,下面我们还是从反射说起。
<!--more-->
有参构造函数payload
有关newInstance
之前我们构造命令执行payload
的时候知道了这个方法用于创建该类的一个对象,但实际上该方法并非任何类都能够创建对象,使用它时需要注意,以下两种情况不能够构建相应对象:
1.构造方法需要传入参数
import java.lang.Class.*;
public class main{
public static void main(String[] args) throws Exception {
Class c = Class.forName("Test");
Test mytest = (Test) c.newInstance();
}
}
class Test{
public String words;
public Test(String str1){
this.words = str1;
}
}
Java
工厂模式中经常使用上面代码类似的方式创建类,但由于我们Test
类的构造方法需要带有参数时,你会收到下面的报错(IDE并不会显示这里出现错误)
Idea
报错提示,找不到此方法,这里可以发现newInstance()
找的实际上是无参数构造方法,从源码的角度看得会更清楚一点:
2.构造方法是私有的
import java.lang.Class.*;
public class main{
public static void main(String[] args) throws Exception {
Class c = Class.forName("Test");
Test mytest = (Test) c.newInstance();
}
}
class Test{
public String words;
private Test(){
this.words = "hello";
}
}
从方法源码的角度来看,是会检查构造方法是否为公有方法:
举个例子,我们不能直接使用这样的方法来执行命令:
Class c = Class.forName("java.lang.Runtime");
c.getMethod("exec", String.class).invoke(c.newInstance(),"ls");
原因是因为java.lang.Runtime
的构造方法为私有方法:
从源码上我们也能看出java.lang.Runtime
类是采用单例模式,即不会出现多个实例的情况,当我们需要使用Runtime
类型的对象时,只需要用getRuntime
得到,而不需要再次创建一个新的实例,形象一点理解,这里Runtime
的一个实例相当于一个shell
,我们只需要一个shell
执行命令,而不需要多个。
总结:
这里我们会发现newInstance()
创建对象是有局限性的,在某些的Java
的payload
中使用了newinstance()
创建对象,可能会因为以上两种情况失效,因此我们需要一个更泛用的创建对象方法,或者一个拥有非私有构造方法的执行命令类。
有关getConstructor
一点想法
getConstructor()
和getMethod
类似,前者获得构造方法,后者获得普通方法,那么问题来了,能不能用getMethod
获得一个类的构造方法呢?个人认为是不能的。
从源码上来看两者的区别
getMethod()
:
getConstructor
:
最终返回reflect
中的Constructor
类
Part1.第一种构造函数
Java
除了java.lang.Runtime
能够执行命令之外,还有一个类能够执行方法java.lang.ProcessBuilder
,这个类拥有两个构造方法,我们先说第一种:
这个类可以通过下面这种方式执行命令:
import java.util.Arrays;
import java.util.List;
public class main{
public static void main(String[] args) throws Exception {
Class myclass = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)myclass.getConstructor(List.class).newInstance(Arrays.asList("pycharm"))).start();
}
}
这里需要使用强转类型,然而一般情况下,我们是不会控制格式类似与这样的代码,因此强转类型我们也需要使用反射完成,前文也说过了invoke
可以用来执行函数(普通方法),
public class main{
public static void main(String[] args) throws Exception {
Class myclass = Class.forName("java.lang.ProcessBuilder");
myclass.getMethod("start").invoke(myclass.getConstructor(List.class).newInstance(Arrays.asList("pycharm")));
}
}
PS:invoke
执行方法时,如果普通方法,第一个参数为该对象的实例,如果为静态方法,第一个参数为该类。
当然这里可能会产生疑惑
newInstance
只能调用无参数构造函数,这里确是调用的有参数的构造函数,实际上这里和上文的newInstance
不同,上文的newInstance
全名应该是Class.newInstance
,这里是Constructor.newInstance
,这里的newInstance
可以根据获得的Constructor
直接调用,而非Class.newInstance
中的先找再调用。既然这个方法与之前那个不同我们就需要了解一下怎么用,根据源码注释:
有道云翻译了一下意思就是:
使用此对象表示的构造函数时,需要创建并初始化构造函数的新实例
声明类,并使用指定的初始化参数,各个参数将自动展开以进行匹配。
总结一下就是需要我们传入构造函数需要的参数,在这里是一个List
类型,这里我们注意到,传入参数为Object...
,他实际上就是一个对象的数组,因此我们这里需要传入一个装有List
类型数据的数组(可变长度参数不一定是直接传入数组,单个对象也可以),这也是为什么要使用Arrays.asList()
将字符数组转化为列表的原因:
同样给出图示:
Part2.第二种构造函数:
第二种构造函数形式为:
这里传入的是String
类型的可变参数,事实上可变参数在底层是一个数组也就是这里的
public ProcessBuilder(String... command) {}
//等价于
public ProcessBuilder(String[] command) {}
两者本质上是一个函数,例程为:
public class main{
public static void main(String[] args) throws Exception {
String[] word_list = {"hello","world"};
Test my_test = new Test();
my_test.say(word_list);
}
}
class Test{
public String words;
public Test(){
this.words = "hello";
}
public void say(String... saying){
for (String word:saying){
System.out.println(word);
}
}
}
因此在这里我们第二个构造函数使用时,只需要传入String[]类
即可,同样的我们可以利用第二个构造函数执行系统命令,如下:
public class main{
public static void main(String[] args) throws Exception {
Class pb = Class.forName("java.lang.ProcessBuilder");
pb.getMethod("start").invoke(pb.getConstructor(String[].class).newInstance(new String[][]{{"pycharm"}}));
}
}
这里我们直接给出了反射类型的payload,涉及的内容和前文一样不再赘述。
- 虽然理论上可变
String
的可变参数是可以直接传入String
的,但由于getConstructor
寻找构造函数时,是通过参数类型寻找的,String...
在底层表现为String[]
,因此这里不能传入String
否则会直接报错找不到该方法,因此getConstructor
需要传入的是String[].class
- 由于
Constructor.newInstrance()
需要传入构造函数参数类型的数组,因此我们不能只传入String[]{}
而是需要将它在包裹一层String[]{}
,因此后面用的是String
类型的二维数组。如果不是二维字符串数组,则会抛出一个非法错误的异常。
私有方法的payload
有关getDeclared:
getDeclared
系列方法和get
系列区别在于,前者获得是该类中所有已经声明的内容(你在该类里创建好的内容,无论变量还是方法,但不包括继承的内容),后者获得的是所有公有的内容(public) getConstructor在一些特殊情况下也可以获得私有的构造函数。
有了这个方法之后,Runtime
创建对象时的私有构造方法就可以解决了,我们通过getDeclaredConstructor
获得其私有的构造方法,然后用getConstructor
一样的方法调用他:
public class main{
public static void main(String[] args) throws Exception {
Class rt = Class.forName("java.lang.Runtime");
Constructor c = rt.getDeclaredConstructor();
c.setAccessible(true);
rt.getMethod("exec",String.class).invoke(c.newInstance(),"pycharm");
}
}
唯一不同的是会需要setAccessible(true)
这行代码,这个反射的返回值为void
,而不是调用该反射的类,所以不能放在一行反射代码里执行,只能单独执行。
参考文章: