前言
在前面的文章中,系统性的分析了tomcat中间件下的Listener / Filter / Servlet / Executor / Upgrade / Valve等等类型的内存马的实现
这里我们继续接着之前的内容继续总结内存马相关的内容,这里我们一起来看看JSP型的内存马的实现原理和方式
前置
想要成功实现一个JSP类型的内存马,详细了解在tomcat容器下的JSP的加载流程是不可避免的
JSP大致的执行过程如下
当用户请求一个JSP页面,将会向tomcat中发送请求
jsp容器将会将对应的JSP页面转化成Servlet源码
将转化而来的java源码编译成.class字节码
加载编译的.class并执行逻辑
将执行结果返回给用户端
正文
JSP加载流程
首先是在经过一系列的filter之后将会把JSP请求传递给JspServlet#service
进行处理
前面都是一些jsp请求的url信息的获取和debug模式的日志打印等等,通过调用preCompile
方法来判断是否对请求资源进行了预编译
这里的常量PRECOMPILE
是jsp_precompile
,只有在请求jsp页面是附带有查询参数为jsp_precompile才会进行预编译
之后就是JSP的关键方法serviceJspFile
的调用
首先就是调用JspRuntimeContext#getWrapper
来获取请求jsp对应的wrapper,如果首次访问该jsp资源将会返回null值,并且将会创建对应jsp资源的一个JspServletWrapper
对象,并将其添加进入JspServletWrapper
类的jsps
属性中,在这之前,这里将会对jsp资源是否存在进行判断,若不存在将会出现异常
JspServletWrapper
是Servlet的一个包装类,所有被注册了的jsp servlet都会存放在rctxt中
在创建了对应的JspServletWrapper
之后,将会通过调用它的service
方法对请求进行处理
快进到JspServletWrapper#serive
方法中的关键功能——编译环节
在经过前面一系列的判断语句之后,在满足了条件this.options.getDevelopment() || this.mustCompile
环节之后可以进入编译的环节
默认的编译器是org.apache.jasper.compiler.JDTCompiler
在创建好并成功初始化编译器之后通过调用编译器的isOutDated
方法判断是否需要进行编译
之后编译过程就是删除掉原来已经生成的java和class文件
这里因为第一次访问,本身就没有进行编译,更不可能存在
之后就是调用JDTCompiler#compile
进行编译的过程
在这里调用generateJava
方法生成了请求资源的java代码
在目标目录已经生成了java代码
类似的,之后通过调用generateClass
方法来进行java代码的编译
就这样结束了JspServletWrapper#service
中的生成java代码和编译的一部分功能,在编译完成之后也会将mustCompile
属性置为false
紧接着就是调用JspServletWrapper#getServlet
方法进行jsp servlet的注册功能
首先将会判断this.getReloadInternal() || this.theServlet == null
,其中theServlet是用来判断是否存在有servelt,这里是第一次访问,是不存在的,所以为null
其中destroy
方法是用来销毁该servlet的
紧接着就是通过下面的代码创建了一个servlet的实例
InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(this.config);
servlet = (Servlet)instanceManager.newInstance(this.ctxt.getFQCN(), this.ctxt.getJspLoader());
并进行jsp Servlet的初始化
最后将创建好的servlet保存在theServlet
属性中
最后的最后就是将调用创建好的servlet#service
方法进行请求的处理
内存马实现
想要实现一个内存马,必须能够满足条件