目录
(一) SSH 框架简介
(二) Java SSH 框架审计技巧
(一) SSH 框架简介
上个月介绍了 SSM 框架,即 Spring MVC 、 Spring 和 MyBatis 。接下来介绍 Java Web曾经开发的 SSH 框架,即 Struts2 、 Spring 和 Hibernate 。
(二) Java SSH 框架审计技巧
Struts2 是一个 MVC 框架,在之前分析 的 SSM 中与之对应的是 Spring MVC , 那么审计 Struts2 与审计 Spring MVC 究竟有什么不同?接下来我们就从一个 SSH 的Demo 入手进行讲解。我们将前面的 SSM 的 Demo 进行重写,方便两个框架之间进行比较,从而加深理解,项目目录结构如图 2-1 所示
如前所述,在有 web.xml 的情况下,审计一个项目时首先需要查看该文件,以便对整个项目有一个初步的了解。
web.xml 文件中,第一项配置表明了 Spring 配置文件的所在位置,第二项配置是一个 Filter ,这里明显不同于 SSM 中 web.xml 的配置,本质上都是 Tomcat 通过加载 web.xml 文件读取其中的信息来判断将前端的请求交由谁进行处理。 Spring MVC 的选择是配置一个 Servlet ,而 Struts2 的选择是配置一个 Filter 。而且师傅还会发现,在配置 Spring MVC 的 DispatcherServlet 时, Spring 配置文件(也就是applicationContext.xml 位置)是直接通过配置参数传入的,而这里则是通过配置一个context-param。
该文件中主要配置了项目所需的各种 bean ,这里可以清楚地看到使用的是 c3p0的连接池。接着是配置 sessionFactory ,并将连接池作为参数传入,同时作为参数传输的还有一个 hibernate 的总配置文件,以及一个 hibernate 的映射文件。接下来是配置每个 Action 的 bean 对象。
该配置文件中配置了 Sturts2 中最核心的部分,即所谓的 Action。
这里配置的每一个 Action 都有其对应的请求 URI 和处理该请求的 Class,以及所对应的方法。我们先从 allBook 这个 action 开始讲解,该功能用于首页所有书籍的展示。allBook action 对应的class 的全限定类名是com.sshProject.action.QueryAllBookAction。class 属性后面还有一个 method 属性,该属性的作用就是执行指定的方法,默认值为“execute”,当不为该属性赋值时,默认执行 Action 的“execute”方法。
分析完 Action 标签中的常见属性,下一步就是追踪 QueryAllBookAction 这个类,来详细观察其中的内容。根据 result 的标签的配置, struts2 会执行 QueryAllBookAction类的 execute 方法,该方法的实现过程如图 2 -5 所示。
如果只看 execute 方法的内容,可能会不太清楚其中的一些变量是如何获取,QueryAllBookAction 类的剩余部分如图 2-6 所示
该接口内只有一个方法,目的是获取 request 对象中的全部 attributes 的一个 map对象。如果想要获取整个 request 对象,则需要实现 ServletRequestAware,该接口内容如图 2 -8 所示
该接口中针对常用的增、删、改、查各定义对应的抽象方法,并由 BooksServiceImpl 来具体负责实现。在 BooksServiceImpl 中找到 queryAllBook 方法,如图 2-10 所示
这里要讲到 Spring 的依赖注入,BooksServiceImpl 类提供了 bookManagerDao 变量的 setter 方法,然后使用 Spring 的依赖注入在 BooksServiceImpl 类实例化时通过读取配置信息后调用 setter 方法将值注入 bookManagerDao 变量中。这里提到了读取配置文件,接下来查看该项目的 Spring 配置文件,即 applicationContext.xml 中的配置信息,如图 2-12所示:
首先是导入了 jdbc 的配置文件,并配置了连接池和 SessionFactory 。然后配置了bookManagerDao 和 bookService 两个 bean ,并将 bookManagerDao 注入 bookService ,Spring 在启动时会读取 applicationContext.xml 并根据其中配置的 bean 的顺序将其逐个进行实例化,同时对每个 bean 中指定的属性进行注入。 Spring 依赖注入的方式有很多种,比如说注解,这里介绍的通过配置 xml 然后通过 setter 方法进行注入只是其中一种。
这里进行了一次查询操作,并将查询的结果封装进一个 list 对象中进行返回。以上就是 SSH 框架处理一个用户请求的大致流程,生产环境中的业务比较复杂,会对各种参数进行合法性校验,但是整体的审计思路不会改变,就是按照程序执行的流程,关注程序每一步对传入参数的操作。
结合之前的表单提交的一个图书的 id ,大概可知此处是通过传入的图书 id 在后台数据库中进行查询。根据之前的观察已知 bookService 变量指向的是一个BooksServiceImpl 对象,所以找到该类中的 queryBookById 方法,该方法的具体内容如图 2-17 所示。
同样根据之前的观察结果,可以发现 bookManagerDao 变量指向的是一个BookManagerDao 对象。在 BookManagerDao 类中找到 queryBookById 方法,如图 2 -18 所示
通过这一段的审计,不难发现图书的 id 参数是由前端传入的,最终拼接进了 SQL语句中并代入数据库中进行查询。在这整个流程中程序并没有对 id 参数进行任何校验,因此很有可能产生 SQL 注入漏洞。
代码审计的思路就是要关注参数是否是前端传入,参数是否可控,在对这个参数处理的过程中是否有针对性地对参数的合法性进行校验,如果同时存在以上 3 个问题,则很可能会存在漏洞。以该 SQL 注入漏洞为例,常用的防御 SQL 注入的手段有两种:一种是通 Filter进行过滤,另一种是使用预编译进行参数化查询,这两种方式各有优缺点,也有各自的应用场景。
根据图 2-11 中的代码可见,传递进来的参数会先被转化成小写,然后和 basdstr 中定义的 SQL 语句进行比对,如果比对成功则返回 flase,返回到 doFilter 方法中就会终止程序继续执行,并重定向至 error.jsp 页面。
除使用上述过滤方式来实现防止 SQL 注入外,在审计过程中还有很重要的一点就是预编译,除可以使用原生的 SQL 语句外, Hibernate 本身还自带一个名为 HQL的面向对象的查询语言,该语言并不被后台数据库所识别,所以在执行 HQL 语句时,Hibernate需要将 HQL 翻译成 SQL 语句后交由后台数据库进行查询操作。将原生 HQL语句改写成 SQL 语句,可以很便捷地在众多不同的数据库中进行移植,只需要修改配置而不必再对 HQL 语句进行任何改写。但是要注意的一点就是 HQL 是面向对象的查询语句,只支持查询操作,对于增、删、改等操作是不支持的。
POJO 类的每个属性都与表中的字段进行一一映射,这样 HQL 才能用类似于操作对象属性的方式进行指定数据查询。与 SQL 语句相似, HQL 也存在注入问题,但是限制颇多,以下列举一些 HQL 注入的限制。
- 无法查询未进行映射的表。
- 在模型关系不明确的情况下无法使用“UNION”进行查询。
- HQL 表名、列名对大小写敏感,查询时使用的列名大小写必须与映射类的属性一致。
- 不能使用*、#、--。
- 没有延时函数。