Java利用技巧——通过反射修改属性
0x00 前言
在上篇文章《Zimbra漏洞调试环境搭建》提到了通过反射枚举JspServletWrapper实例的实现,本文将要以此为例,详细介绍实现的思路和细节,便于以此类推,实现其他功能。
0x01 简介
本文将要介绍以下内容:
- 反射中的常用操作
- 获得类的所有字段
- 获得类的所有方法
- 调用类的方法
- 枚举JspServletWrapper实例的实现细节
0x02 反射中的常用操作
1.获得类的所有字段
getField():
能够获取本类以及父类中的public字段
getDeclaredField():
能够获取本类中的所有字段
这里以Zimbra环境为例,给出示例代码
(1)获取request对象的所有字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field[] fields=request.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
(2)获取request对象的父类的所有字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field[] fields=request.getClass().getSuperclass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
2.获得类的所有方法
getMethods():
能够获取本类以及父类或者父接口中的公共方法(public修饰符修饰的)
getDeclaredMethods():
能够获取本类中的所有方法,包括private、protected、默认以及public的方法
这里以Zimbra环境为例,给出示例代码
(1)获取request对象的所有方法
<%@ page import="java.lang.reflect.Field "%>
<%@ page import="java.lang.reflect.Method "%>
<%
Method[] methods = request.getClass().getDeclaredMethods();
for(Method method:methods){
out.print(method.getName() + "<br>");
}
%>
(2)获取request对象的父类的所有方法
<%@ page import="java.lang.reflect.Field "%>
<%@ page import="java.lang.reflect.Method "%>
<%
Method[] methods = request.getClass().getSuperclass().getDeclaredMethods();
for(Method method:methods){
out.print(method.getName() + "<br>");
}
%>
3.调用类的指定方法
这里以Zimbra环境为例,给出示例代码
搭建好Zimbra漏洞调试环境后,定位request对象的getHeader(String name)
方法,代码细节如下:
public String getHeader(String name) {
org.eclipse.jetty.http.MetaData.Request metadata = this._metaData;
return metadata == null ? null : metadata.getFields().get(name);
}
对照代码细节,参数类型为String
调用request对象的getHeader(String name)
方法,参数为"User-Agent"
,实现代码如下:
<%@ page import="java.lang.reflect.Field "%>
<%@ page import="java.lang.reflect.Method "%>
<%
Method m1 = request.getClass().getDeclaredMethod("getHeader", String.class);
out.println(m1.invoke(request, "User-Agent"));
%>
0x03 枚举JspServletWrapper实例的实现细节
1.下断点
选择文件servlet-api-3.1.jar,依次选中javax.servlet
->http
->HttpServlet.class
,在合适的位置下断点,当运行到断点时,可以查看request对象的完整结构,如下图
查看request对象的结构,我们最终定位到了JspServletWrapper实例的位置,直观的映射为:request
->_scope
->_servlet
->rctxt
->jsps
接下来,我们需要按照这个映射,通过多次反射最终获得JspServletWrapper实例
(1)读取request对象的所有字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field[] fields=request.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
在回显的结果中能够找到_scope
(2)从request对象获得_scope实例并再次枚举字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object obj1 = f.get(request);
Field[] fields=obj1.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
在回显的结果中能够找到_servlet
(3)获得_servlet实例并再次枚举字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object obj1 = f.get(request);
f = obj1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object obj2 = f.get(obj1);
Field[] fields=obj2.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
回显的结果为:serialVersionUID
这里没有rctxt字段
尝试寻找原因:开启调试器,定位至关键位置,如下图
从图中可以看到,_servlet
的类为JettyJspServlet
JettyJspServlet继承自JspServlet,成员变量只有serialVersionUID,这与我们通过访问jsp页面得到的结果一致
查看JspServlet的相关实现代码,如下图
从图中可以看到,rctxt为JspServlet的成员变量,属性为private,所以子类JettyJspServlet无法继承成员变量rctxt
这里我们可以直接选取_servlet
实例的父类进行枚举字段
(4)选取_servlet
实例的父类进行枚举字段
<%@ page import="java.lang.reflect.Field" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object obj1 = f.get(request);
f = obj1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object obj2 = f.get(obj1);
f = obj2.getClass().getSuperclass().getDeclaredField("rctxt");
f.setAccessible(true);
Object obj3 = f.get(obj2);
Field[] fields=obj3.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
out.println(fields[i].getName() + "<br>");
}
%>
在回显的结果中能够找到jsps
(5)获得jsps实例并枚举字段
开启调试器,查看jsps的类型,如下图
从图中可以看到,jsps的类为ConcurrentHashMap,这里只需要枚举出所有Key即可
添加需要导入的包后,得出最终实现代码:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object obj1 = f.get(request);
f = obj1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object obj2 = f.get(obj1);
f = obj2.getClass().getSuperclass().getDeclaredField("rctxt");
f.setAccessible(true);
Object obj3 = f.get(obj2);
f = obj3.getClass().getDeclaredField("jsps");
f.setAccessible(true);
ConcurrentHashMap obj4 = (ConcurrentHashMap)f.get(obj3);
Enumeration enu = obj4.keys();
while (enu.hasMoreElements()) {
out.println(enu.nextElement() + "<br>");
}
%>
补充:删除指定JspServletWrapper的实例
只需要调用ConcurrentHashMap的remove方法即可
示例代码:
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
Field f = request.getClass().getDeclaredField("_scope");
f.setAccessible(true);
Object obj1 = f.get(request);
f = obj1.getClass().getDeclaredField("_servlet");
f.setAccessible(true);
Object obj2 = f.get(obj1);
f = obj2.getClass().getSuperclass().getDeclaredField("rctxt");
f.setAccessible(true);
Object obj3 = f.get(obj2);
f = obj3.getClass().getDeclaredField("jsps");
f.setAccessible(true);
ConcurrentHashMap obj4 = (ConcurrentHashMap)f.get(obj3);
obj4.remove("/test.jsp");
%>
0x04 小结
本文介绍了通过反射枚举JspServletWrapper实例的具体实现,记录思路和细节,便于以此类推,修改其他内容。