注:这一节主要是消除很多人把JSP当作了JavaWeb的全部的误解,了解MVC及其框架思想。MVC是用于组织代码用一种业务逻辑和数据显示分离的方法,不管是Java的Struts2、SpringMVC还是PHP的ThinkPHP都爆出过高危的任意代码执行,本节重在让更多的人了解MVC和MVC框架安全,由浅到深尽可能的照顾没Java基础的朋友。所谓攻击JavaWeb,如果连JavaWeb是个什么,有什么特性都不知道,就算能用Struts刷再多的RANK又有何意义?还不如沏一杯清茶,读一本好书,不浮躁,撸上一天。

0×00、初识MVC


传统的开发存在结构混乱易用性差耦合度高可维护性差等多种问题,为了解决这些毛病分层思想和MVC框架就出现了。MVC是三个单词的缩写,分别为: 模型(Model),视图(View) 和控制(Controller)。 MVC模式的目的就是实现Web系统的职能分工。

Model层实现系统中的业务逻辑,通常可以用JavaBean或EJB来实现。

View*层用于与用户的交互,通常用JSP来实现(前面有讲到,JavaWeb项目中如果不采用JSP作为展现层完全可以没有任何JSP文件,甚至是过滤一切JSP请求,JEECMS是一个最为典型的案例)。

Controller层Model与View之间沟通的桥梁,它可以分派用户的请求并选择恰当的视图用于显示,同时它也可以解释用户的输入并将它们映射为模型层可执行的操作。

2013072423121653672.jpg

Model1和Model2:

Model1主要是用JSP去处理来自客户端的请求,所有的业务逻辑都在一个或者多个JSP页面里面完成,这种是最不科学的。举例:http://localhost/show_user.jsp?id=2。JSP页面获取到参数id=2就会带到数据库去查询数据库当中id等于 2的用户数据,由于这样的实现方式虽然简单,但是维护成本就非常高。JSP页面跟逻辑业务都捆绑在一起高耦合了。而软件开发的目标就是为了去解耦,让程序之间的依赖性减小。在model1里面SQL注入等攻击简直就是家常便饭。因为在页面里面频繁的去处理各种业务会非常麻烦,更别说关注安全了。典型的Model1的代码就是之前用于演示的SQL注入的JSP页面。

Model1的流程:

2013072423125077387.png

Model 2表示的是基于MVC模式的框架,JSP+Servlet。Model2已经带有一定的分层思想了,即Jsp只做简单的展现层,Servlet做后端的业务逻辑处理。这样视图和业务逻辑就相应的分开了。例如:http://localhost/ShowUserServlet?id=2。也就是说把请求交给Servlet处理,Servlet处理完成后再交给jsp或HTML做页面展示。JSP页面就不必要去关心你传入的id=2是怎么查询出来的,而是怎么样去显示id=2的用户的信息(多是用EL表达式或JSP脚本做页面展现)。视图和逻辑分开的好处是可以更加清晰的去处理业务逻辑,这样的出现安全问题的几率会相对降低。

2013072423133120903.png

Mvc框架存在的问题:

当Model1和Model2都难以满足开发需求的时候,通用性的MVC框架也就产生了,模型视图控制器,各司其责程序结构一目了然,业务安全相关控制井井有序,这便是MVC框架给我们带来的好处,但是不幸的是由于MVC的框架的实现各自不同,某些东西因为其越来越强大,而衍生出来越来越多的安全问题,典型的由于安全问题处理不当造成近期无数互联网站被黑阔攻击的MVC框架便是Struts2。神器过于锋利伤到自己也就在所难免了。而在Struts和Spring当中最喜欢被人用来挖0day的就是标签和OGNL的安全处理问题了。

Spring Mvc:

Spring 框架提供了构建 Web应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还是 Struts 这样的 Web 框架。通过策略接口,Spring 框架是高度可配置的,而且包含多种视图技术,例如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText 和 POI、Freemarker。Spring MVC 框架并不知道使用的视图,所以不会强迫您只使用 JSP 技术。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。

Struts2:

Struts是apache基金会jakarta项目组的一个开源项目,采用MVC模式,能够很好的帮助我们提高开发web项目的效率。Struts主要采用了servlet和jsp技术来实现,把servlet、jsp、标签库等技术整合到整个框架中。Struts2比Struts1内部实现更加复杂,但是使用起来更加简单,功能更加强大。

Struts2历史版本下载地址:http://archive.apache.org/dist/struts/binaries/

官方网站是: http://struts.apache.org/

常见MVC比较:

按性能排序:1、Jsp+servlet>2、struts1>2、spring mvc>3、struts2+freemarker>>4、struts2,ognl,值栈。

开发效率上,基本正好相反。值得强调的是,Spring mvc开发效率和Struts2不相上下。

Struts2的性能低的原因是因为OGNL和值栈造成的。所以如果你的系统并发量高,可以使用freemaker进行显示,而不是采用OGNL和值栈。这样,在性能上会有相当大得提高。

而每一次Struts2的远程代码执行的原因都是因为OGNL。

当前JavaWeb当中最为流行的MVC框架主要有Spring MVC和Struts。相比Struts2而言,SpringMVC具有更轻巧,更简易,更安全等优点。但是由于SpringMVC历史远没有Struts那么悠久,SpringMVC想要在一朝一夕颠覆Struts1、2还是非常有困难的。

JavaWeb的Servlet和Filter:

可以说JavaWeb和PHP的实现有着本质的区别,PHP属于解释性语言.不需要在服务器启动的时候就通过一堆的配置去初始化apps而是在任意一个请求到达以后再去加载配置完成来自客户端的请求。ASP和PHP有个非常大的共同点就是不需要预先编译成类似Java的字节码文件,所有的类方法都存在于*.PHP文件当中。而在Java里面可以在项目启动时去加载配置到Servlet容器内。在web.xml里面配置一个Servlet或者Filter后可以非常轻松的拦截、过滤来自于客户端的任意后缀请求。在系列2的时候就有提到Servlet,这里再重温一下。

Servlet配置:
<servlet>
    <servlet-name>LoginServlet</servlet-name>
<servlet-class>org.javaweb.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/servlet/LoginServlet.action</url-pattern>
</servlet-mapping>
Filter配置:
<filter>
    <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Filter在JavaWeb当中用来做权限控制再合适不过了,再也不用在每个页面都去做session验证了。假如过滤的url-pattern是/admin/*那么所有URI中带有admin的请求都必须经过如下Filter过滤:

2013072423184674687.png

Servlet和Filter一样都可以拦截所有的URL的任意方式的请求。其中url-pattern可以是任意的URL也可以是诸如*.action通配符。既然能拦截任意请求如若要做参数和请求的净化就会非常简单了。servlet-name即标注一个Servlet名为LoginServlet它对应的Servlet所在的类是org.javaweb.servlet.LoginServlet.java。由此即可发散开来,比如如何在Java里面实现通用的恶意请求(通用的SQL注入、XSS、CSRF、Struts2等攻击)?敏感页面越权访问?(传统的动态脚本的方式实现是在每个页面都去加session验证非常繁琐,有了filter过滤器,便可以非常轻松的去限制目录权限)。

上面贴出来的过滤器是Struts2的典型配置,StrutsPrepareAndExecuteFilter过滤了/*,即任意的URL请求也就是Struts2的第一个请求入口。任何一个Filter都必须去实现javax.servlet.Filter的Filter接口,即init、doFilter、destroy这三个接口,这里就不细讲了,有兴趣的朋友自己下载JavaEE6的源码包看下。

public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException;
public void destroy();

TIPS:

在Eclipse里面看一个接口有哪些实现,选中一个方法快捷键Ctrl+t就会列举出当前接口的所有实现了。例如下图我们可以轻易的看到当前项目下实现Filter接口的有如下接口,其中SecFilter是我自行实现的,StrutsPrepareAndExecuteFilter是Struts2实现的,这个实现是用于Struts2启动和初始化的,下面会讲到:

2013072423193885093.png

0×01、Struts概述


Struts1、Struts2、Webwork关系:

Struts1是第一个广泛流行的mvc框架,使用及其广泛。但是,随着技术的发展,尤其是JSF、ajax等技术的兴起,Struts1有点跟不上时代的步伐,以及他自己在设计上的一些硬伤,阻碍了他的发展。

同时,大量新的mvc框架渐渐大踏步发展,尤其是webwork。Webwork是opensymphony组织开发的。Webwork实现了更加优美的设计,更加强大而易用的功能。

后来,struts和webwork两大社区决定合并两个项目,完成struts2.事实上,struts2是以webwork为核心开发的,更加类似于webwork框架,跟struts1相差甚远

STRUTS2框架内部流程:

1. 客户端发送请求的tomcat服务器。服务器接受,将HttpServletRequest传进来。
2. 请求经过一系列过滤器(如:ActionContextCleanUp、SimeMesh等)
3. FilterDispatcher被调用。FilterDispatcher调用ActionMapper来决定这个请求是否要调用某个Action
4. ActionMapper决定调用某个ActionFilterDispatcher把请求交给ActionProxy
5. ActionProxy通过Configuration Manager查看struts.xml,从而找到相应的Action类
6. ActionProxy创建一个ActionInvocation对象
7. ActionInvocation对象回调Action的execute方法
8. Action执行完毕后,ActionInvocation根据返回的字符串,找到对应的result。然后将Result内容通过HttpServletResponse返回给服务器。

SpringMVC框架内部流程

1.用户发送请求给服务器。url:user.do
2.服务器收到请求。发现DispatchServlet可以处理。于是调用DispatchServlet。
3.DispatchServlet内部,通过HandleMapping检查这个url有没有对应的Controller。如果有,则调用Controller。
4.Controller开始执行。
5.Controller执行完毕后,如果返回字符串,则ViewResolver将字符串转化成相应的视图对象;如果返回ModelAndView对象,该对象本身就包含了视图对象信息。
6.DispatchServlet将执视图对象中的数据,输出给服务器。
7.服务器将数据输出给客户端。

在看完Struts2和SpringMVC的初始化方式之后不知道有没有对MVC架构更加清晰的了解。

Struts2请求处理流程分析:

2013072423210282176.png

1、服务器启动的时候会自动去加载当前项目的web.xml
2、在加载web.xml配置的时候会去自动初始化Struts2的Filter,然后把所有的请求先交于Struts的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.java类去做过滤处理。
3、而这个类只是一个普通的Filter方法通过调用Struts的各个配置去初始化。
4、初始化完成后一旦有action请求都会经过StrutsPrepareAndExecuteFilter的doFilter过滤。
5、doFilter中的ActionMapping去映射对应的Action。
6、ExecuteOperations

2013072423291864880.png

源码、配置和访问截图:

2013072423293638349.png

0×02、Struts2中ActionContext、ValueStack、Ognl


在学习Struts命令执行之前必须得知道什么是OGNL、ActionContext、ValueStack。在前面已经强调过很多次容器的概念了。这地方不敢再扯远了,不然就再也扯回不来了。大概理解:tomcat之类的是个大箱子,里面装了很多小箱子,小箱子里面装了很多小东西。而Struts2其实就是在把很多东西进行包装,要取小东西的时候直接从struts2包装好的箱子里面去拿就行了。

ActionContext对象:

Struts1的Action必须依赖于web容器,他的extecute方法会自动获得HttpServletRequest、HttpServletResponse对象,从而可以跟web容器进行交互。

Struts2的Action不用依赖于web容器,本身只是一个普通的java类而已。但是在web开发中我们往往需要获得request、session、application等对象。这时候,可以通过ActionContext来处理。

ActionContext正如其名,是Action执行的上下文。他内部有个map属性,它存放了Action执行时需要用到的对象。

在每次执行Action之前都会创建新的ActionContext对象,通过ActionContext获取的session、request、application并不是真正的HttpServletRequest、HttpServletResponse、ServletContext对象,而是将这三个对象里面的值重新包装成了map对象。这样的封装,我们及获取了我们需要的值,同时避免了跟Web容器直接打交道,实现了完全的解耦。

测试代码:

public class TestActionContextAction extends ActionSupport{
    private String uname;
    public String execute() throws Exception {
        ActionContext ac = ActionContext.getContext();
        System.out.println(ac);    //在此处定义断点
        return this.SUCCESS;
    }
    //get和set方法省略!
}

我们设置断点,debug进去,跟踪ac对象的值。发现他有个table属性,该属性内部包含一个map属性,该map中又有多个map属性,他们分别是:

request、session、application、action、attr、parameters等。

同时,我们跟踪request进去,发现属性attribute又是一个table,再进去发现一个名字叫做”struts.valueStack”属性。内容如下:

2013072423331472319.png

OgnlValueStack可以简单看做List,里面还放了Action对象的引用,通过它可以得到该Action对象的引用。

下图说明了几个对象的关系:

2013072423333795056.jpg

1.  ActionContext、Action本身和HttpServletRequest对象没有关系。但是为了能够使用EL表达式、JSTL直接操作他们的属性。会有一个拦截器将ActionContext、Action中的属性通过类似request.setAttribute()方法置入request中(webwork2.1之前的做法)。这样,我们也可以通过:${requestScope.uname}即可访问到ActionContext和Action中的属性。

注:struts2后,使用装饰器模式来实现上述功能。

Action的实例,总是放到value stack中。因为Action放在stack中,而stack是root(根对象),所以对Action中的属性的访问就可以省略#标记。

获取Web容器信息:

在上面我GETSHELL或者是输出回显的时候就必须获取到容器中的请求和响应对象。而在Struts2中通过ActionContext可以获得session、request、application,但他们并不是真正的HttpServletRequest、HttpServletResponse、ServletContext对象,而是将这三个对象里面的值重新包装成了map对象。 Struts框架通过他们来和真正的web容器对象交互。

获得session:ac.getSession().put("s", "ss");
获得request:Map m = ac.get("request");
获得application: ac.getApplication();

获取HttpServletRequest、HttpServletResponse、ServletContext:

有时,我们需要真正的HttpServletRequest、HttpServletResponse、ServletContext对象,怎么办? 我们可以通过ServletActionContext类来得到相关对象,代码如下:

HttpServletRequest req = ServletActionContext.*getRequest*();
ServletActionContext.*getRequest*().getSession();
ServletActionContext.*getServletContext*();

Struts2 OGNL:

OGNL全称是Object-Graph  Navigation  Language(对象图形导航语言),Ognl同时也是Struts2默认的表达式语言。每一次Struts2的命令执行漏洞都是通过OGNL去执行的。在写这文档之前,乌云的drops已有可够参考的Ognl文章了http://drops.wooyun.org/papers/340。这里只是简单提下。

1、能够访问对象的普通方法
2、能够访问类的静态属性和静态方法
3、强大的操作集合类对象的能力
4、支持赋值操作和表达式串联
5、访问OGNL上下文和ActionContext

Ognl并不是Struts专用,我们一样可以在普通的类里面一样可以使用Ognl,比如用Ognl去访问一个普通对象中的属性:

2013072423341292194.png

在上面已经列举出了Ognl可以调用静态方法,比如表达式使用表达式去调用runtime执行命令执行:

@